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_error(index, "IndexID mismatch with file %s", path); + mail_index_data_free(data); + return FALSE; + } + + return TRUE; +} + +static const char *init_data_file(MailIndex *index, int fd, + const char *temppath) +{ + MailIndexDataHeader hdr; + const char *realpath; + + /* write header */ + memset(&hdr, 0, sizeof(hdr)); + hdr.indexid = index->indexid; + + if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + index_set_error(index, "Error writing to temp index data " + "%s: %m", temppath); + return NULL; + } + + /* move temp file into .data file, deleting old one + if it already exists */ + realpath = t_strconcat(index->filepath, ".data", NULL); + if (rename(temppath, realpath) == -1) { + index_set_error(index, "rename(%s, %s) failed: %m", + temppath, realpath); + (void)unlink(temppath); + return NULL; + } + + return realpath; +} + +int mail_index_data_create(MailIndex *index) +{ + MailIndexData *data; + const char *temppath, *realpath; + int fd; + + fd = mail_index_create_temp_file(index, &temppath); + if (fd == -1) + return FALSE; + + realpath = init_data_file(index, fd, temppath); + if (realpath == NULL) { + (void)close(fd); + (void)unlink(temppath); + return FALSE; + } + + data = i_new(MailIndexData, 1); + data->index = index; + data->fd = fd; + data->filepath = i_strdup(realpath); + data->dirty_mmap = TRUE; + + index->data = data; + return TRUE; +} + +void mail_index_data_free(MailIndexData *data) +{ + data->index->data = NULL; + + if (data->mmap_base != NULL) { + munmap(data->mmap_base, data->mmap_length); + data->mmap_base = NULL; + } + + (void)close(data->fd); + i_free(data->filepath); + i_free(data); +} + +int mail_index_data_reset(MailIndexData *data) +{ + MailIndexDataHeader hdr; + + if (ftruncate(data->fd, sizeof(MailIndexDataHeader)) == -1) { + index_set_error(data->index, "ftruncate() failed for data file " + "%s: %m", data->filepath); + return FALSE; + } + + memset(&hdr, 0, sizeof(hdr)); + hdr.indexid = data->index->indexid; + hdr.deleted_space = 0; + + if (lseek(data->fd, 0, SEEK_SET) == (off_t)-1) { + index_set_error(data->index, "lseek() failed for data file " + "%s: %m", data->filepath); + return FALSE; + } + + if (write(data->fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + index_set_error(data->index, "write() failed for data file " + "%s: %m", data->filepath); + return FALSE; + } + + return TRUE; +} + +void mail_index_data_new_data_notify(MailIndexData *data) +{ + data->dirty_mmap = TRUE; +} + +off_t mail_index_data_append(MailIndexData *data, void *buffer, size_t size) +{ + off_t pos; + + i_assert((size & (MEM_ALIGN_SIZE-1)) == 0); + + pos = lseek(data->fd, 0, SEEK_END); + if (pos == (off_t)-1) { + index_set_error(data->index, "lseek() failed with file %s: %m", + data->filepath); + return (off_t)-1; + } + + if ((size_t) write(data->fd, buffer, size) != size) { + index_set_error(data->index, "Error appending to file %s: %m", + data->filepath); + return (off_t)-1; + } + + mail_index_data_new_data_notify(data); + return pos; +} + +int mail_index_data_add_deleted_space(MailIndexData *data, + unsigned int data_size) +{ + MailIndexDataHeader *hdr; + unsigned int max_del_space; + + i_assert(data->index->lock_type == MAIL_LOCK_EXCLUSIVE); + + if (!mmap_update(data, 0, 0)) + return FALSE; + + hdr = data->mmap_base; + hdr->deleted_space += data_size; + + /* see if we've reached the max. deleted space in file */ + if (data->mmap_length >= COMPRESS_MIN_SIZE) { + max_del_space = data->mmap_length / 100 * COMPRESS_PERCENTAGE; + if ((size_t) hdr->deleted_space >= max_del_space) + data->index->set_flags |= MAIL_INDEX_FLAG_COMPRESS_DATA; + } + return TRUE; +} + +int mail_index_data_sync_file(MailIndexData *data) +{ + if (data->mmap_base != NULL) { + if (msync(data->mmap_base, data->mmap_length, MS_SYNC) == -1) { + index_set_error(data->index, "msync() failed for " + "%s: %m", data->filepath); + return FALSE; + } + } + + if (fsync(data->fd) == -1) { + index_set_error(data->index, "fsync() failed for %s: %m", + data->filepath); + return FALSE; + } + + return TRUE; +} + +MailIndexDataRecord * +mail_index_data_lookup(MailIndexData *data, MailIndexRecord *index_rec, + MailField field) +{ + MailIndexDataRecord *rec; + size_t pos, max_pos; + + if (index_rec->data_position == 0) { + index_reset_error(data->index); + return NULL; + } + + if (!mmap_update(data, index_rec->data_position, index_rec->data_size)) + return NULL; + + max_pos = (size_t) index_rec->data_position + index_rec->data_size; + if (max_pos > data->mmap_length) { + INDEX_MARK_CORRUPTED(data->index); + index_set_error(data->index, "Error in data file %s: " + "Given data size larger than file size " + "(%lu > %lu)", data->filepath, + (unsigned long) max_pos, + (unsigned long) data->mmap_length); + return NULL; + } + + pos = index_rec->data_position; + do { + rec = (MailIndexDataRecord *) ((char *) data->mmap_base + pos); + + if (pos + rec->full_field_size > max_pos) { + INDEX_MARK_CORRUPTED(data->index); + index_set_error(data->index, "Error in data file %s: " + "Field size points outside file " + "(%lu + %u > %lu)", data->filepath, + (unsigned long) pos, + rec->full_field_size, + (unsigned long) data->mmap_length); + break; + } + + if (rec->field == field) { + /* match */ + return rec; + } else if (rec->field < field) { + /* jump to next record */ + pos += DATA_RECORD_SIZE(rec); + } else { + /* the fields are sorted by field type, so it's not + possible the wanted field could come after this. */ + break; + } + } while (pos < max_pos); + + return NULL; +} + +MailIndexDataRecord * +mail_index_data_next(MailIndexData *data, MailIndexRecord *index_rec, + MailIndexDataRecord *rec) +{ + size_t pos, max_pos; + + if (rec == NULL) + return NULL; + + /* get position to next record */ + pos = (size_t) DATA_FILE_POSITION(data, rec) + DATA_RECORD_SIZE(rec); + max_pos = index_rec->data_position + index_rec->data_size; + + /* make sure it's within range */ + if (pos >= max_pos) + return NULL; + + rec = (MailIndexDataRecord *) ((char *) data->mmap_base + pos); + if (pos + rec->full_field_size > max_pos) { + INDEX_MARK_CORRUPTED(data->index); + index_set_error(data->index, "Error in data file %s: " + "Field size points outside file " + "(%lu + %u > %lu)", data->filepath, + (unsigned long) pos, + rec->full_field_size, + (unsigned long) data->mmap_length); + return NULL; + } + + return rec; +} + +int mail_index_data_record_verify(MailIndexData *data, MailIndexDataRecord *rec) +{ + int i; + + /* make sure the data actually contains \0 */ + for (i = rec->full_field_size-1; i >= 0; i--) { + if (rec->data[i] == '\0') { + /* yes, everything ok */ + return TRUE; + } + } + + INDEX_MARK_CORRUPTED(data->index); + index_set_error(data->index, "Error in data file %s: " + "Missing \\0 with field %u (%lu)", + data->filepath, rec->field, + (unsigned long) DATA_FILE_POSITION(data, rec)); + return FALSE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-data.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,41 @@ +#ifndef __MAIL_INDEX_DATA_H +#define __MAIL_INDEX_DATA_H + +int mail_index_data_open(MailIndex *index); +int mail_index_data_create(MailIndex *index); +void mail_index_data_free(MailIndexData *data); + +/* Truncate the data file and update it's indexid */ +int mail_index_data_reset(MailIndexData *data); + +/* Needs to be called whenever new messages are added. File must never + be shrinked while it's open. */ +void mail_index_data_new_data_notify(MailIndexData *data); + +/* Append new data at the end of the file. Returns the position in file + where the data begins, or (off_t)-1 if error occured. */ +off_t mail_index_data_append(MailIndexData *data, void *buffer, size_t size); + +/* Increase header->deleted_space field */ +int mail_index_data_add_deleted_space(MailIndexData *data, + unsigned int data_size); + +/* Synchronize the data into disk */ +int mail_index_data_sync_file(MailIndexData *data); + +/* Looks up a field from data file. Returns NULL if not found or + if error occured. */ +MailIndexDataRecord * +mail_index_data_lookup(MailIndexData *data, MailIndexRecord *index_rec, + MailField field); + +/* Returns the next record in data file, or NULL if there's no more. */ +MailIndexDataRecord * +mail_index_data_next(MailIndexData *data, MailIndexRecord *index_rec, + MailIndexDataRecord *rec); + +/* Returns TRUE if rec->data is a valid \0-terminated string */ +int mail_index_data_record_verify(MailIndexData *data, + MailIndexDataRecord *rec); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-fsck.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,81 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mail-index.h" + +int mail_index_fsck(MailIndex *index) +{ + /* we verify only the fields in the header. other problems will be + noticed and fixed while reading the messages. */ + MailIndexHeader *hdr; + MailIndexRecord *rec, *end_rec; + unsigned int max_uid; + off_t pos; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + if (!mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + hdr = index->header; + + hdr->first_hole_position = 0; + hdr->first_hole_records = 0; + + hdr->messages_count = 0; + hdr->seen_messages_count = 0; + hdr->deleted_messages_count = 0; + + hdr->first_unseen_uid_lowwater = 0; + hdr->first_deleted_uid_lowwater = 0; + + rec = (MailIndexRecord *) ((char *) index->mmap_base + + sizeof(MailIndexHeader)); + end_rec = (MailIndexRecord *) ((char *) index->mmap_base + + index->mmap_length); + + max_uid = 0; + for (; rec < end_rec; rec++) { + if (rec->uid == 0) { + /* expunged message */ + pos = INDEX_FILE_POSITION(index, rec); + if (hdr->first_hole_position == 0) { + hdr->first_hole_position = pos; + hdr->first_hole_records = 1; + } else if (hdr->first_hole_position + + (hdr->first_hole_records * + sizeof(MailIndexRecord)) == (size_t) pos) { + /* hole continues */ + hdr->first_hole_records++; + } + continue; + } + + if (rec->uid < max_uid) { + i_error("fsck %s: UIDs are not ordered (%u < %u)", + index->filepath, rec->uid, max_uid); + return mail_index_rebuild_all(index); + } + max_uid = rec->uid; + + if (rec->msg_flags & MAIL_SEEN) + hdr->seen_messages_count++; + else if (hdr->first_unseen_uid_lowwater) + hdr->first_unseen_uid_lowwater = rec->uid; + + if (rec->msg_flags & MAIL_DELETED) { + if (hdr->first_deleted_uid_lowwater == 0) + hdr->first_deleted_uid_lowwater = rec->uid; + hdr->deleted_messages_count++; + } + hdr->messages_count++; + } + + if (hdr->next_uid <= max_uid) + hdr->next_uid = max_uid+1; + if (hdr->last_nonrecent_uid >= hdr->next_uid) + hdr->last_nonrecent_uid = hdr->next_uid-1; + + /* FSCK flag is removed automatically by set_lock() */ + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-update.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,410 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "rfc822-date.h" +#include "rfc822-tokenize.h" +#include "message-parser.h" +#include "message-size.h" +#include "imap-envelope.h" +#include "imap-bodystructure.h" +#include "mail-index.h" +#include "mail-index-data.h" + +struct _MailIndexUpdate { + Pool pool; + + MailIndex *index; + MailIndexRecord *rec; + + unsigned int updated_fields; + char *fields[FIELD_TYPE_MAX_BITS]; + unsigned int field_sizes[FIELD_TYPE_MAX_BITS]; + unsigned int field_extra_sizes[FIELD_TYPE_MAX_BITS]; +}; + +MailIndexUpdate *mail_index_update_begin(MailIndex *index, MailIndexRecord *rec) +{ + Pool pool; + MailIndexUpdate *update; + + i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE); + + pool = pool_create("MailIndexUpdate", 1024, FALSE); + + update = p_new(pool, MailIndexUpdate, 1); + update->pool = pool; + update->index = index; + update->rec = rec; + return update; +} + +static int mail_field_get_index(MailField field) +{ + unsigned int i, mask; + + for (i = 0, mask = 1; i < FIELD_TYPE_MAX_BITS; i++, mask <<= 1) { + if (field == mask) + return i; + } + + return -1; +} + +static int have_new_fields(MailIndexUpdate *update) +{ + MailField field; + + if (update->rec->cached_fields == 0) { + /* new record */ + return TRUE; + } + + for (field = 1; field != FIELD_TYPE_LAST; field <<= 1) { + if ((update->updated_fields & field) && + (update->rec->cached_fields & field) == 0) + return TRUE; + } + + return FALSE; +} + +static int have_too_large_fields(MailIndexUpdate *update) +{ + MailIndexDataRecord *rec; + int index; + + /* start from the first data field - it's required to exist */ + rec = mail_index_data_lookup(update->index->data, update->rec, 1); + while (rec != NULL) { + if (rec->field & update->updated_fields) { + /* field was changed */ + index = mail_field_get_index(rec->field); + i_assert(index >= 0); + + if (update->field_sizes[index] >= rec->full_field_size) + return TRUE; + } + rec = mail_index_data_next(update->index->data, + update->rec, rec); + } + + return FALSE; +} + +/* Append all the data at the end of the data file and update + the index's data position */ +static int update_by_append(MailIndexUpdate *update) +{ + MailIndexDataRecord *rec, *destrec; + MailField field; + off_t fpos; + void *mem; + unsigned int max_size, pos; + int i; + + /* allocate the old size + also the new size of all changed or added + fields. this is more than required, but it's much easier than + calculating the exact size. */ + max_size = update->rec->data_size; + for (i = 0; i < FIELD_TYPE_MAX_BITS; i++) { + max_size += sizeof(MailIndexDataRecord)-sizeof(rec->data) + + update->field_sizes[i] + + update->field_extra_sizes[i] + MEM_ALIGN_SIZE-1; + } + + mem = p_malloc(update->pool, max_size); + pos = 0; + + rec = mail_index_data_lookup(update->index->data, update->rec, 1); + for (i = 0, field = 1; field != FIELD_TYPE_LAST; i++, field <<= 1) { + destrec = (MailIndexDataRecord *) ((char *) mem + pos); + + if (update->fields[i] != NULL) { + /* value was modified - use it */ + destrec->full_field_size = update->field_sizes[i] + + update->field_extra_sizes[i]; + memcpy(destrec->data, update->fields[i], + update->field_sizes[i]); + } else if (rec != NULL) { + /* use the old value */ + destrec->full_field_size = rec->full_field_size; + memcpy(destrec->data, rec->data, rec->full_field_size); + } else { + /* the field doesn't exist, jump to next */ + continue; + } + + /* memory alignment fix */ + destrec->full_field_size = MEM_ALIGN(destrec->full_field_size); + + destrec->field = field; + pos += DATA_RECORD_SIZE(destrec); + + if (rec != NULL && rec->field == field) { + rec = mail_index_data_next(update->index->data, + update->rec, rec); + } + } + + i_assert(pos <= max_size); + + /* append the data at the end of the data file */ + fpos = mail_index_data_append(update->index->data, mem, pos); + if (fpos == (off_t)-1) + return FALSE; + + /* update index file position - it's mmap()ed so it'll be writte + into disk when index is unlocked. */ + update->rec->data_position = fpos; + update->rec->data_size = pos; + return TRUE; +} + +/* Replace the modified fields in the file - assumes there's enough + space to do it */ +static void update_by_replace(MailIndexUpdate *update) +{ + MailIndexDataRecord *rec; + int index; + + /* start from the first data field - it's required to exist */ + rec = mail_index_data_lookup(update->index->data, update->rec, 1); + while (rec != NULL) { + if (rec->field & update->updated_fields) { + /* field was changed */ + index = mail_field_get_index(rec->field); + i_assert(index >= 0); + + i_assert(update->field_sizes[index] < + rec->full_field_size); + + strcpy(rec->data, update->fields[index]); + } + rec = mail_index_data_next(update->index->data, + update->rec, rec); + } +} + +int mail_index_update_end(MailIndexUpdate *update) +{ + int failed; + + i_assert(update->index->lock_type == MAIL_LOCK_EXCLUSIVE); + + /* if any of the fields were newly added, or have grown larger + than their old max. size, we need to move the record to end + of file. */ + if (have_new_fields(update) || have_too_large_fields(update)) + failed = !update_by_append(update); + else { + update_by_replace(update); + failed = FALSE; + } + + if (!failed) { + /* update cached fields mask */ + update->rec->cached_fields |= update->updated_fields; + } + + pool_unref(update->pool); + return !failed; +} + +void mail_index_update_field(MailIndexUpdate *update, MailField field, + const char *value, unsigned int extra_space) +{ + unsigned int size; + int index; + + index = mail_field_get_index(field); + i_assert(index >= 0); + + size = strlen(value)+1; + + update->updated_fields |= field; + update->field_sizes[index] = size; + update->field_extra_sizes[index] = extra_space; + update->fields[index] = p_malloc(update->pool, size); + memcpy(update->fields[index], value, size); +} + +static MailField mail_header_get_field(const char *str, unsigned int len) +{ + if (len == 10 && strncasecmp(str, "Message-ID", 10) == 0) + return FIELD_TYPE_MESSAGEID; + if (len == 7 && strncasecmp(str, "Subject", 7) == 0) + return FIELD_TYPE_SUBJECT; + if (len == 4 && strncasecmp(str, "From", 4) == 0) + return FIELD_TYPE_FROM; + if (len == 2 && strncasecmp(str, "To", 2) == 0) + return FIELD_TYPE_TO; + if (len == 2 && strncasecmp(str, "Cc", 2) == 0) + return FIELD_TYPE_CC; + if (len == 3 && strncasecmp(str, "Bcc", 3) == 0) + return FIELD_TYPE_BCC; + + return 0; +} + +static const char *field_get_value(const char *value, unsigned int len) +{ + char *ret, *p; + unsigned int i; + + ret = t_malloc(len+1); + + /* compress the long headers (remove \r?\n before space or tab) */ + for (i = 0, p = ret; i < len; i++) { + if (value[i] == '\r' && i+1 != len && value[i+1] == '\n') + i++; + + if (value[i] == '\n') { + i_assert(IS_LWSP(value[i+1])); + } else { + *p++ = value[i]; + } + } + *p = '\0'; + + return ret; +} + +typedef struct { + MailIndexUpdate *update; + Pool envelope_pool; + MessagePartEnvelopeData *envelope; + + MessageHeaderFunc header_func; + void *user_data; +} HeaderUpdateData; + +static void update_header_func(MessagePart *part, + const char *name, unsigned int name_len, + const char *value, unsigned int value_len, + void *user_data) +{ + HeaderUpdateData *data = user_data; + MailField field; + const char *str; + + if (part != NULL && part->parent != NULL) + return; + + if (data->header_func != NULL) { + data->header_func(part, name, name_len, + value, value_len, data->user_data); + } + + if (name_len == 4 && strncasecmp(name, "Date", 4) == 0) { + /* date is stored into index record itself */ + str = field_get_value(value, value_len); + if (!rfc822_parse_date(str, &data->update->rec->sent_date)) + data->update->rec->sent_date = ioloop_time; + return; + } + + /* see if we can do anything with this field */ + field = mail_header_get_field(name, name_len); + if (field != 0) { + /* do we want to store this? */ + if (data->update->index->header->cache_fields & field) { + str = field_get_value(value, value_len); + data->update->index->update_field(data->update, + field, str, 0); + } + } + + if (data->update->index->header->cache_fields & FIELD_TYPE_ENVELOPE) { + if (data->envelope_pool == NULL) { + data->envelope_pool = pool_create("index envelope", + 2048, FALSE); + } + imap_envelope_parse_header(data->envelope_pool, + &data->envelope, + t_strndup(name, name_len), + value, value_len); + } +} + +void mail_index_update_headers(MailIndexUpdate *update, + const char *msg, size_t size, + MessageHeaderFunc header_func, void *user_data) +{ + HeaderUpdateData data; + MailField cache_fields; + MessagePart *part; + MessageSize hdr_size, body_size; + Pool pool; + const char *value; + + data.update = update; + data.envelope_pool = NULL; + data.envelope = NULL; + data.header_func = header_func; + data.user_data = user_data; + + cache_fields = update->index->header->cache_fields; + if ((cache_fields & (FIELD_TYPE_BODY|FIELD_TYPE_BODYSTRUCTURE)) != 0) { + /* for body / bodystructure, we need need to + fully parse the message */ + pool = pool_create("index message parser", 2048, FALSE); + part = message_parse(pool, msg, size, + update_header_func, &data); + + /* update our sizes */ + update->rec->header_size = part->header_size.physical_size; + update->rec->body_size = part->body_size.physical_size; + update->rec->full_virtual_size = + part->header_size.virtual_size + + part->body_size.virtual_size; + + if (cache_fields & FIELD_TYPE_BODY) { + t_push(); + value = imap_part_get_bodystructure(pool, &part, + msg, size, FALSE); + update->index->update_field(update, FIELD_TYPE_BODY, + value, 0); + t_pop(); + } + + if (cache_fields & FIELD_TYPE_BODYSTRUCTURE) { + t_push(); + value = imap_part_get_bodystructure(pool, &part, + msg, size, TRUE); + update->index->update_field(update, + FIELD_TYPE_BODYSTRUCTURE, + value, 0); + t_pop(); + } + + pool_unref(pool); + } else { + message_parse_header(NULL, msg, size, &hdr_size, + update_header_func, &data); + + update->rec->header_size = hdr_size.physical_size; + update->rec->body_size = size - hdr_size.physical_size; + + if (update->rec->full_virtual_size == 0) { + /* we need to calculate virtual size of the + body as well */ + message_get_body_size(msg + hdr_size.physical_size, + size - hdr_size.physical_size, + &body_size); + + update->rec->full_virtual_size = + hdr_size.virtual_size + body_size.virtual_size; + } + } + + if (data.envelope != NULL) { + t_push(); + value = imap_envelope_get_part_data(data.envelope); + update->index->update_field(update, FIELD_TYPE_ENVELOPE, + value, 0); + t_pop(); + + pool_unref(data.envelope_pool); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-util.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,59 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "hostpid.h" +#include "mail-index.h" +#include "mail-index-util.h" + +#include <unistd.h> +#include <fcntl.h> + +void index_set_error(MailIndex *index, const char *fmt, ...) +{ + va_list va; + + i_free(index->error); + + if (fmt == NULL) + index->error = NULL; + else { + va_start(va, fmt); + index->error = i_strdup_vprintf(fmt, va); + va_end(va); + + i_error("%s", index->error); + } +} + +void index_reset_error(MailIndex *index) +{ + if (index->error != NULL) { + i_free(index->error); + index->error = NULL; + } +} + +int mail_index_create_temp_file(MailIndex *index, const char **path) +{ + int fd; + + hostpid_init(); + + /* use ".temp.host.pid" as temporary file name. unlink() it first, + just to be sure it's not symlinked somewhere for some reason.. */ + *path = t_strconcat(index->dir, "/.temp.", + my_hostname, ".", my_pid, NULL); + (void)unlink(*path); + + /* usage of O_EXCL isn't exactly needed since the path should be + trusted, but it shouldn't hurt either - if creating file fails + because of it, it's because something must be wrong (race + condition). also, might not won't work through NFS but that + can't be helped. */ + fd = open(*path, O_RDWR | O_CREAT | O_EXCL, 0660); + if (fd == -1) + index_set_error(index, "Can't create temp index %s: %m", *path); + + return fd; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-util.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,15 @@ +#ifndef __MAIL_INDEX_UTIL_H +#define __MAIL_INDEX_UTIL_H + +/* Set the current error message */ +void index_set_error(MailIndex *index, const char *fmt, ...) + __attr_format__(2, 3); + +/* Reset the current error */ +void index_reset_error(MailIndex *index); + +/* Create temporary file into index's directory. Returns opened file handle + and sets *path to the full path of the created file. */ +int mail_index_create_temp_file(MailIndex *index, const char **path); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,1139 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "hostpid.h" +#include "mmap-util.h" +#include "mail-index.h" +#include "mail-index-data.h" +#include "mail-index-util.h" +#include "mail-hash.h" +#include "mail-lockdir.h" +#include "mail-modifylog.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <utime.h> + +static int mmap_update(MailIndex *index) +{ + unsigned int extra; + + if (!index->dirty_mmap) { + index->header = (MailIndexHeader *) index->mmap_base; + return TRUE; + } + + if (index->mmap_base != NULL) + (void)munmap(index->mmap_base, index->mmap_length); + + index->mmap_base = mmap_rw_file(index->fd, &index->mmap_length); + if (index->mmap_base == MAP_FAILED) { + index->mmap_base = NULL; + index_set_error(index, "index: mmap() failed with file %s: %m", + index->filepath); + return FALSE; + } + + if (index->mmap_length < sizeof(MailIndexHeader)) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "truncated index file %s", + index->filepath); + return FALSE; + } + + extra = (index->mmap_length - sizeof(MailIndexHeader)) % + sizeof(MailIndexRecord); + + if (extra != 0) { + /* partial write or corrupted - + truncate the file to valid length */ + index->mmap_length -= extra; + (void)ftruncate(index->fd, (off_t) index->mmap_length); + } + + index->header = (MailIndexHeader *) index->mmap_base; + index->dirty_mmap = FALSE; + return TRUE; +} + +void mail_index_close(MailIndex *index) +{ + index->set_flags = 0; + index->set_cache_fields = 0; + + index->opened = FALSE; + index->updating = FALSE; + index->inconsistent = FALSE; + index->dirty_mmap = TRUE; + + index->lock_type = MAIL_LOCK_UNLOCK; + index->header = NULL; + + if (index->fd != -1) { + (void)close(index->fd); + index->fd = -1; + } + + if (index->filepath != NULL) { + i_free(index->filepath); + index->filepath = NULL; + } + + if (index->mmap_base != NULL) { + (void)munmap(index->mmap_base, index->mmap_length); + index->mmap_base = NULL; + } + + if (index->data != NULL) { + mail_index_data_free(index->data); + index->data = NULL; + } + + if (index->hash != NULL) { + mail_hash_free(index->hash); + index->hash = NULL; + } + + if (index->modifylog != NULL) { + mail_modifylog_free(index->modifylog); + index->modifylog = NULL; + } + + if (index->error != NULL) { + i_free(index->error); + index->error = NULL; + } +} + +int mail_index_sync_file(MailIndex *index) +{ + struct utimbuf ut; + int failed; + + if (!mail_index_data_sync_file(index->data)) + return FALSE; + + if (index->mmap_base != NULL) { + if (msync(index->mmap_base, index->mmap_length, MS_SYNC) == -1) { + index_set_error(index, "msync() failed for %s: %m", + index->filepath); + return FALSE; + } + } + + failed = FALSE; + if (!mail_hash_sync_file(index->hash)) + failed = TRUE; + if (!mail_modifylog_sync_file(index->modifylog)) + failed = TRUE; + + /* keep index's modify stamp same as the sync file's stamp */ + ut.actime = ioloop_time; + ut.modtime = index->file_sync_stamp; + if (utime(index->filepath, &ut) == -1) { + index_set_error(index, "utime() failed for %s: %m", + index->filepath); + return FALSE; + } + + if (fsync(index->fd) == -1) { + index_set_error(index, "fsync() failed for %s: %m", + index->filepath); + return FALSE; + } + + return !failed; +} + +int mail_index_rebuild_all(MailIndex *index) +{ + if (!index->rebuild(index)) + return FALSE; + + if (!mail_hash_rebuild(index->hash)) + return FALSE; + + return TRUE; +} + +static void mail_index_update_header_changes(MailIndex *index) +{ + i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE); + + if (index->set_flags != 0) { + index->header->flags |= index->set_flags; + index->set_flags = 0; + } + + if (index->set_cache_fields != 0) { + index->header->cache_fields = index->set_cache_fields; + index->set_cache_fields = 0; + } +} + +#define MAIL_LOCK_TO_FLOCK(lock_type) \ + ((lock_type) == MAIL_LOCK_UNLOCK ? F_UNLCK : \ + (lock_type) == MAIL_LOCK_SHARED ? F_RDLCK : F_WRLCK) + +int mail_index_try_lock(MailIndex *index, MailLockType lock_type) +{ + struct flock fl; + + if (index->lock_type == lock_type) + return TRUE; + + /* lock whole file */ + fl.l_type = MAIL_LOCK_TO_FLOCK(lock_type); + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + if (fcntl(index->fd, F_SETLK, &fl) == -1) { + if (errno != EINTR && errno != EACCES) { + index_set_error(index, "fcntl(F_SETLKW, %d) " + "failed for file %s: %m", fl.l_type, + index->filepath); + } + return FALSE; + } + + return TRUE; +} + +int mail_index_set_lock(MailIndex *index, MailLockType lock_type) +{ + /* yeah, this function is a bit messy. besides locking, it keeps + the index synced and in a good shape. */ + MailLockType old_lock_type; + struct flock fl; + int ret; + + if (index->inconsistent) { + /* index is in inconsistent state and nothing else than + free() is allowed for it. */ + return FALSE; + } + + if (index->lock_type == lock_type) + return TRUE; + + /* shared -> exclusive isn't allowed */ + i_assert(lock_type != MAIL_LOCK_EXCLUSIVE || + index->lock_type != MAIL_LOCK_SHARED); + + if (index->lock_type == MAIL_LOCK_EXCLUSIVE) { + /* releasing exclusive lock */ + index->header->flags &= ~MAIL_INDEX_FLAG_FSCK; + + mail_index_update_header_changes(index); + + /* sync mmaped memory */ + (void)mail_index_sync_file(index); + } + + if (lock_type != MAIL_LOCK_UNLOCK && + index->lock_type == MAIL_LOCK_UNLOCK && !index->updating) { + /* unlock -> lock */ + index->updating = TRUE; + (void)index->sync(index); + + ret = mail_index_set_lock(index, lock_type); + index->updating = FALSE; + return ret; + } + + /* lock whole file */ + fl.l_type = MAIL_LOCK_TO_FLOCK(lock_type); + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + while (fcntl(index->fd, F_SETLKW, &fl) == -1) { + if (errno != EINTR) { + index_set_error(index, "fcntl(F_SETLKW, %d) " + "failed for file %s: %m", fl.l_type, + index->filepath); + return FALSE; + } + } + + if (lock_type == MAIL_LOCK_UNLOCK) { + /* reset last_lookup so rebuilds don't try to use it */ + index->last_lookup = NULL; + } + + old_lock_type = index->lock_type; + index->lock_type = lock_type; + + if (lock_type != MAIL_LOCK_UNLOCK) { + /* we're always mmap()ed when we're locked */ + if (!mmap_update(index)) { + (void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK); + return FALSE; + } + + if (index->indexid != index->header->indexid) { + /* index was rebuilt, there's no way we can maintain + consistency */ + index_set_error(index, "Warning: Inconsistency - Index " + "%s was rebuilt while we had it open", + index->filepath); + index->inconsistent = TRUE; + return FALSE; + } + } else if (old_lock_type == MAIL_LOCK_SHARED) { + /* releasing shared lock */ + unsigned int old_flags, old_cache; + + old_flags = index->header->flags; + old_cache = index->header->cache_fields; + + if ((old_flags | index->set_flags) != old_flags || + (old_cache | index->set_cache_fields) != old_cache) { + /* need to update the header */ + index->updating = TRUE; + if (mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE)) + mail_index_update_header_changes(index); + index->updating = FALSE; + + return mail_index_set_lock(index, MAIL_LOCK_UNLOCK); + } + } + + if (lock_type == MAIL_LOCK_EXCLUSIVE) { + /* while holding exclusive lock, keep the FSCK flag on. + when the lock is released, the FSCK flag will also be + removed. */ + index->header->flags |= MAIL_INDEX_FLAG_FSCK; + if (msync(index->mmap_base, sizeof(MailIndexHeader), + MS_SYNC) == -1) { + index_set_error(index, "msync() failed for %s: %m", + index->filepath); + (void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK); + return FALSE; + } + if (fsync(index->fd) == -1) { + index_set_error(index, "fsync() failed for %s: %m", + index->filepath); + (void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK); + return FALSE; + } + } + + if (index->header != NULL && !index->updating && + (index->header->flags & MAIL_INDEX_FLAG_REBUILD) != 0) { + /* index is corrupted, rebuild it */ + index->updating = TRUE; + + if (lock_type == MAIL_LOCK_SHARED) + (void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK); + + if (!mail_index_rebuild_all(index)) + return FALSE; + + ret = mail_index_set_lock(index, lock_type); + index->updating = FALSE; + return ret; + } + + if (lock_type == MAIL_LOCK_UNLOCK) { + /* reset header so it's not used while being unlocked */ + index->last_lookup = NULL; + } + + return TRUE; +} + +static int read_and_verify_header(int fd, MailIndexHeader *hdr) +{ + /* read the header */ + if (lseek(fd, 0, SEEK_SET) != 0) + return FALSE; + + if (read(fd, hdr, sizeof(MailIndexHeader)) != sizeof(MailIndexHeader)) + return FALSE; + + /* check the compatibility */ + if (hdr->compat_data[0] != MAIL_INDEX_COMPAT_FLAGS || + hdr->compat_data[1] != sizeof(unsigned int) || + hdr->compat_data[2] != sizeof(time_t) || + hdr->compat_data[3] != sizeof(off_t)) + return FALSE; + + /* check the version */ + return hdr->version == MAIL_INDEX_VERSION; +} + +/* Returns TRUE if we're compatible with given index file */ +static int mail_is_compatible_index(const char *path) +{ + MailIndexHeader hdr; + int fd, compatible; + + fd = open(path, O_RDONLY); + if (fd == -1) + return FALSE; + + compatible = read_and_verify_header(fd, &hdr); + + (void)close(fd); + return compatible; +} + +/* Returns a file name of compatible index */ +static const char *mail_find_index(MailIndex *index) +{ + DIR *dir; + struct dirent *d; + const char *name; + char path[1024]; + unsigned int len; + + /* first try the primary name */ + i_snprintf(path, sizeof(path), "%s/" INDEX_FILE_PREFIX, index->dir); + if (mail_is_compatible_index(path)) + return INDEX_FILE_PREFIX; + + dir = opendir(index->dir); + if (dir == NULL) { + /* path doesn't exist */ + index_set_error(index, "Can't open dir %s: %m", + index->dir); + return NULL; + } + + len = strlen(INDEX_FILE_PREFIX); + name = NULL; + while ((d = readdir(dir)) != NULL) { + if (strncmp(d->d_name, INDEX_FILE_PREFIX, len) == 0) { + /* index found, check if we're compatible */ + i_snprintf(path, sizeof(path), "%s/%s", + index->dir, d->d_name); + if (mail_is_compatible_index(path)) { + name = t_strdup(d->d_name); + break; + } + } + } + + (void)closedir(dir); + return name; +} + +static int mail_index_open_init(MailIndex *index, int update_recent, + MailIndexHeader *hdr) +{ + /* update \Recent message counters */ + if (update_recent && hdr->last_nonrecent_uid != hdr->next_uid-1) { + /* keep last_recent_uid to next_uid-1 */ + if (index->lock_type == MAIL_LOCK_SHARED) { + if (!index->set_lock(index, MAIL_LOCK_UNLOCK)) + return FALSE; + } + + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + index->first_recent_uid = index->header->last_nonrecent_uid+1; + index->header->last_nonrecent_uid = index->header->next_uid-1; + } else { + index->first_recent_uid = hdr->last_nonrecent_uid+1; + } + + if (hdr->next_uid >= INT_MAX-1024) { + /* UID values are getting too high, rebuild index */ + index->set_flags |= MAIL_INDEX_FLAG_REBUILD; + } + + return TRUE; +} + +static int mail_index_open_file(MailIndex *index, const char *filename, + int update_recent) +{ + MailIndexHeader hdr; + const char *path; + int fd, failed; + + /* the index file should already be checked that it exists and + we're compatible with it. */ + + path = t_strconcat(index->dir, "/", filename, NULL); + fd = open(path, O_RDWR); + if (fd == -1) { + index_set_error(index, "Can't open index %s: %m", path); + return FALSE; + } + + /* check the compatibility anyway just to be sure */ + if (!read_and_verify_header(fd, &hdr)) { + index_set_error(index, "Non-compatible index file %s", path); + return FALSE; + } + + if (index->fd != -1) + mail_index_close(index); + + index->fd = fd; + index->filepath = i_strdup(path); + index->indexid = hdr.indexid; + index->dirty_mmap = TRUE; + index->updating = TRUE; + + failed = TRUE; + do { + /* open/create the index files */ + if (!mail_index_data_open(index)) { + if ((index->set_flags & MAIL_INDEX_FLAG_REBUILD) == 0) + break; + + /* data file is corrupted, need to rebuild index */ + hdr.flags |= MAIL_INDEX_FLAG_REBUILD; + index->set_flags = 0; + + if (!mail_index_data_create(index)) + break; + } + if (!mail_hash_open_or_create(index)) + break; + if (!mail_modifylog_open_or_create(index)) + break; + + if (hdr.flags & MAIL_INDEX_FLAG_REBUILD) { + /* index is corrupted, rebuild */ + if (!mail_index_rebuild_all(index)) + return FALSE; + } + + if (hdr.flags & MAIL_INDEX_FLAG_FSCK) { + /* index needs fscking */ + if (!index->fsck(index)) + break; + } + + if (!index->sync(index)) + break; + if (!mail_index_open_init(index, update_recent, &hdr)) + break; + + failed = FALSE; + } while (FALSE); + + index->updating = FALSE; + + /* last_recent_uid update, hash or modifylog creation + may create locks */ + if (!index->set_lock(index, MAIL_LOCK_UNLOCK)) + failed = TRUE; + + if (failed) + mail_index_close(index); + + return !failed; +} + +void mail_index_init_header(MailIndexHeader *hdr) +{ + memset(hdr, 0, sizeof(MailIndexHeader)); + hdr->compat_data[0] = MAIL_INDEX_COMPAT_FLAGS; + hdr->compat_data[1] = sizeof(unsigned int); + hdr->compat_data[2] = sizeof(time_t); + hdr->compat_data[3] = sizeof(off_t); + hdr->version = MAIL_INDEX_VERSION; + hdr->indexid = ioloop_time; + + /* mark the index being rebuilt - rebuild() removes this flag + when it succeeds */ + hdr->flags = MAIL_INDEX_FLAG_REBUILD; + + /* set the fields we always want to cache - currently nothing + except the location. many clients aren't interested about + any of the fields. */ + hdr->cache_fields = FIELD_TYPE_LOCATION; + + hdr->uid_validity = ioloop_time; + hdr->next_uid = 1; +} + +static int mail_index_create(MailIndex *index, int *dir_unlocked, + int update_recent) +{ + MailIndexHeader hdr; + const char *path; + char index_path[1024]; + int fd, len; + + *dir_unlocked = FALSE; + + /* first create the index into temporary file. */ + fd = mail_index_create_temp_file(index, &path); + if (fd == -1) + return FALSE; + + /* fill the header */ + mail_index_init_header(&hdr); + + /* write header */ + if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + index_set_error(index, "Error writing to temp index %s: %m", + path); + (void)close(fd); + (void)unlink(path); + return FALSE; + } + + /* move the temp index into the real one. we also need to figure + out what to call ourself on the way. */ + len = i_snprintf(index_path, sizeof(index_path), + "%s/" INDEX_FILE_PREFIX, index->dir); + if (link(path, index_path) == 0) + (void)unlink(path); + else { + if (errno != EEXIST) { + /* fatal error */ + index_set_error(index, "link(%s, %s) failed: %m", + path, index_path); + (void)close(fd); + (void)unlink(path); + return FALSE; + } + + /* fallback to index.hostname - we require each system to + have a different hostname so it's safe to override + previous index as well */ + hostpid_init(); + i_snprintf(index_path + len, sizeof(index_path)-len, + "-%s", my_hostname); + + if (rename(path, index_path) == -1) { + index_set_error(index, "rename(%s, %s) failed: %m", + path, index_path); + (void)close(fd); + (void)unlink(path); + return FALSE; + } + + /* FIXME: race condition here! index may be opened before + it's rebuilt. maybe set it locked here, and make it require + shared lock when finding the indexes.. */ + } + + if (index->fd != -1) + mail_index_close(index); + + index->fd = fd; + index->filepath = i_strdup(index_path); + index->indexid = hdr.indexid; + index->updating = TRUE; + index->dirty_mmap = TRUE; + + /* lock the index file and unlock the directory */ + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) { + index->updating = FALSE; + return FALSE; + } + + if (mail_index_lock_dir(index, MAIL_LOCK_UNLOCK)) + *dir_unlocked = TRUE; + + /* create the data file, build the index and hash */ + if (!mail_index_data_create(index) || !index->rebuild(index) || + !mail_hash_create(index) || !mail_modifylog_create(index)) { + index->updating = FALSE; + mail_index_close(index); + return FALSE; + } + index->updating = FALSE; + + if (!mail_index_open_init(index, update_recent, index->header)) { + mail_index_close(index); + return FALSE; + } + + /* unlock finally */ + if (!index->set_lock(index, MAIL_LOCK_UNLOCK)) { + mail_index_close(index); + return FALSE; + } + + return TRUE; +} + +int mail_index_open(MailIndex *index, int update_recent) +{ + const char *name; + + i_assert(!index->opened); + + name = mail_find_index(index); + if (name == NULL) + return FALSE; + + if (!mail_index_open_file(index, name, update_recent)) + return FALSE; + + index->opened = TRUE; + return TRUE; +} + +int mail_index_open_or_create(MailIndex *index, int update_recent) +{ + const char *name; + int failed, dir_unlocked; + + i_assert(!index->opened); + + /* first see if it's already there */ + name = mail_find_index(index); + if (name != NULL && mail_index_open_file(index, name, update_recent)) { + index->opened = TRUE; + return TRUE; + } + + /* index wasn't found or it was broken. get exclusive lock and check + again, just to make sure we don't end up having two index files + due to race condition with another process. */ + if (!mail_index_lock_dir(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + name = mail_find_index(index); + if (name == NULL || !mail_index_open_file(index, name, update_recent)) { + /* create/rebuild index */ + failed = !mail_index_create(index, &dir_unlocked, + update_recent); + } else { + dir_unlocked = FALSE; + failed = FALSE; + } + + if (!dir_unlocked && !mail_index_lock_dir(index, MAIL_LOCK_UNLOCK)) + return FALSE; + + if (failed) + return FALSE; + + index->opened = TRUE; + return TRUE; +} + +static MailIndexRecord *mail_index_lookup_mapped(MailIndex *index, + unsigned int lookup_seq) +{ + MailIndexHeader *hdr; + MailIndexRecord *rec, *last_rec; + unsigned int seq; + off_t seekpos; + + if (lookup_seq == index->last_lookup_seq && + index->last_lookup != NULL && index->last_lookup->uid != 0) { + /* wanted the same record as last time */ + return index->last_lookup; + } + + hdr = index->mmap_base; + rec = (MailIndexRecord *) ((char *) index->mmap_base + + sizeof(MailIndexHeader)); + + seekpos = sizeof(MailIndexHeader) + + (off_t)(lookup_seq-1) * sizeof(MailIndexRecord); + if ((size_t) seekpos > index->mmap_length - sizeof(MailIndexRecord)) { + /* out of range */ + return NULL; + } + + if (hdr->first_hole_position == 0 || + hdr->first_hole_position > seekpos) { + /* easy, it's just at the expected index */ + rec += lookup_seq-1; + if (rec->uid == 0) { + index_set_error(index, "Error in index file %s: " + "first_hole_position wasn't updated " + "properly", index->filepath); + INDEX_MARK_CORRUPTED(index); + return NULL; + } + return rec; + } + + /* we need to walk through the index to get to wanted position */ + last_rec = rec + (index->mmap_length-sizeof(MailIndexHeader)) / + sizeof(MailIndexRecord); + + if (lookup_seq > index->last_lookup_seq && index->last_lookup != NULL) { + /* we want to lookup data after last lookup - + this helps us some */ + rec = index->last_lookup; + seq = index->last_lookup_seq; + } else { + /* some mails are deleted, jump after the first known hole + and start counting non-deleted messages.. */ + i_assert(hdr->first_hole_records > 0); + + seq = INDEX_POSITION_INDEX(hdr->first_hole_position + 1) + 1; + rec += seq-1 + hdr->first_hole_records; + } + + if (rec == last_rec) + return NULL; + + while (seq < lookup_seq || rec->uid == 0) { + if (rec->uid != 0) + seq++; + + if (rec == last_rec) { + /* out of range */ + return NULL; + } + rec++; + } + + return rec; +} + +MailIndexHeader *mail_index_get_header(MailIndex *index) +{ + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + return index->header; +} + +MailIndexRecord *mail_index_lookup(MailIndex *index, unsigned int seq) +{ + i_assert(seq > 0); + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + if (!mmap_update(index)) + return NULL; + + index->last_lookup = mail_index_lookup_mapped(index, seq); + index->last_lookup_seq = seq; + return index->last_lookup; +} + +MailIndexRecord *mail_index_next(MailIndex *index, MailIndexRecord *rec) +{ + MailIndexRecord *end_rec; + + i_assert(!index->dirty_mmap); + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + if (rec == NULL) + return NULL; + + /* go to the next non-deleted record */ + end_rec = (MailIndexRecord *) ((char *) index->mmap_base + + index->mmap_length); + while (++rec < end_rec) { + if (rec->uid != 0) + return rec; + } + + return NULL; +} + +MailIndexRecord *mail_index_lookup_uid_range(MailIndex *index, + unsigned int first_uid, + unsigned int last_uid) +{ + MailIndexRecord *rec, *end_rec; + unsigned int uid, last_try_uid; + off_t pos; + + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + i_assert(first_uid > 0 && last_uid > 0); + + if (first_uid > last_uid) + return NULL; + + if (!mmap_update(index)) + return NULL; + + /* try the few first with hash lookups */ + last_try_uid = last_uid - first_uid < 10 ? last_uid : first_uid + 4; + for (uid = first_uid; uid <= last_try_uid; uid++) { + pos = mail_hash_lookup_uid(index->hash, uid); + if (pos != 0) { + return (MailIndexRecord *) + ((char *) index->mmap_base + pos); + } + } + + if (last_try_uid == last_uid) + return NULL; + + /* fallback to looking through the whole index - this shouldn't be + needed often, so don't bother trying anything too fancy. */ + rec = (MailIndexRecord *) ((char *) index->mmap_base + + sizeof(MailIndexHeader)); + end_rec = (MailIndexRecord *) ((char *) index->mmap_base + + index->mmap_length); + while (rec < end_rec) { + if (rec->uid != 0) { + if (rec->uid > last_uid) + return NULL; + + if (rec->uid >= first_uid) + return rec; + } + rec++; + } + + return NULL; +} + +const char *mail_index_lookup_field(MailIndex *index, MailIndexRecord *rec, + MailField field) +{ + MailIndexDataRecord *datarec; + + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + /* first check if the field even could be in the file */ + if ((rec->cached_fields & field) != field) { + /* no, but make sure the future records will have it. + we don't immediately mark the index to cache this field + for old messages as some clients never ask the info again */ + index->set_cache_fields |= field; + return NULL; + } + + datarec = mail_index_data_lookup(index->data, rec, field); + if (datarec != NULL) { + if (mail_index_data_record_verify(index->data, datarec)) + return datarec->data; + + /* index is corrupted, it will be rebuilt */ + } else { + /* field doesn't exist, make sure it willl be added later + when there's time */ + index->set_flags |= MAIL_INDEX_FLAG_CACHE_FIELDS; + } + + return NULL; +} + +unsigned int mail_index_get_sequence(MailIndex *index, MailIndexRecord *rec) +{ + MailIndexRecord *seekrec; + unsigned int seq; + + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + if (rec == index->last_lookup) { + /* same as last lookup sequence - too easy */ + return index->last_lookup_seq; + } + + if (index->header->first_hole_position == 0) { + /* easy, it's just at the expected index */ + return INDEX_POSITION_INDEX( + INDEX_FILE_POSITION(index, rec)) + 1; + } + + seekrec = (MailIndexRecord *) ((char *) index->mmap_base + + index->header->first_hole_position); + if (rec < seekrec) { + /* record before first hole */ + return INDEX_POSITION_INDEX( + INDEX_FILE_POSITION(index, rec)) + 1; + } + + /* we know the sequence after the first hole - skip to there and + start browsing the records until ours is found */ + seq = INDEX_POSITION_INDEX(INDEX_FILE_POSITION(index, seekrec))+1; + seekrec += index->header->first_hole_records; + + for (; seekrec != rec; seekrec++) { + if (seekrec->uid != 0) + seq++; + } + + return seq; +} + +static void update_first_hole_records(MailIndex *index) +{ + MailIndexRecord *rec, *end_rec; + + /* see if first_hole_records can be grown */ + rec = (MailIndexRecord *) ((char *) index->mmap_base + + index->header->first_hole_position) + + index->header->first_hole_records; + end_rec = (MailIndexRecord *) ((char *) index->mmap_base + + index->mmap_length); + while (rec != end_rec && rec->uid == 0) { + index->header->first_hole_records++; + rec++; + } +} + +static void index_mark_flag_changes(MailIndex *index, MailIndexRecord *rec, + MailFlags old_flags, MailFlags new_flags) +{ + if ((old_flags & MAIL_SEEN) == 0 && (new_flags & MAIL_SEEN)) { + /* unseen -> seen */ + index->header->seen_messages_count++; + } else if ((old_flags & MAIL_SEEN) && (new_flags & MAIL_SEEN) == 0) { + /* seen -> unseen */ + if (index->header->seen_messages_count == + index->header->messages_count) { + /* this is the first unseen message */ + index->header->first_unseen_uid_lowwater = rec->uid; + } else if (rec->uid < index->header->first_unseen_uid_lowwater) + index->header->first_unseen_uid_lowwater = rec->uid; + + index->header->seen_messages_count--; + } else if ((old_flags & MAIL_DELETED) == 0 && + (new_flags & MAIL_DELETED)) { + /* undeleted -> deleted */ + index->header->deleted_messages_count++; + + if (index->header->deleted_messages_count == 1) { + /* this is the first deleted message */ + index->header->first_deleted_uid_lowwater = rec->uid; + } else if (rec->uid < index->header->first_deleted_uid_lowwater) + index->header->first_deleted_uid_lowwater = rec->uid; + } else if ((old_flags & MAIL_DELETED) && + (new_flags & MAIL_DELETED) == 0) { + /* deleted -> undeleted */ + index->header->deleted_messages_count--; + } +} + +int mail_index_expunge(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, int external_change) +{ + MailIndexHeader *hdr; + off_t pos; + + i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE); + i_assert(rec->uid != 0); + + if (seq != 0) { + if (!mail_modifylog_add_expunge(index->modifylog, seq, + rec->uid, external_change)) + return FALSE; + } + + mail_hash_update(index->hash, rec->uid, 0); + + /* setting UID to 0 is enough for deleting the mail from index */ + rec->uid = 0; + + /* update last_lookup_seq */ + if (seq != 0) { + /* note that last_lookup can be left to point to + invalid record so that next() works properly */ + if (seq == index->last_lookup_seq) + index->last_lookup = NULL; + else if (seq < index->last_lookup_seq) + index->last_lookup_seq--; + } + + hdr = index->header; + + /* update first hole */ + pos = INDEX_FILE_POSITION(index, rec); + if (hdr->first_hole_position == 0) { + /* first deleted message in index */ + hdr->first_hole_position = pos; + hdr->first_hole_records = 1; + } else if (hdr->first_hole_position - + sizeof(MailIndexRecord) == (size_t) pos) { + /* deleted the previous record before hole */ + hdr->first_hole_position -= sizeof(MailIndexRecord); + hdr->first_hole_records++; + } else if (hdr->first_hole_position + + (hdr->first_hole_records * + sizeof(MailIndexRecord)) == (size_t) pos) { + /* deleted the next record after hole */ + hdr->first_hole_records++; + update_first_hole_records(index); + } else { + /* second hole coming to index file, the index now needs to + be compressed to keep high performance */ + index->set_flags |= MAIL_INDEX_FLAG_COMPRESS; + + if (hdr->first_hole_position > pos) { + /* new hole before the old hole */ + hdr->first_hole_position = pos; + hdr->first_hole_records = 1; + } + } + + /* update message counts */ + hdr->messages_count--; + index_mark_flag_changes(index, rec, rec->msg_flags, 0); + + /* update deleted_space in data file */ + (void)mail_index_data_add_deleted_space(index->data, rec->data_size); + + return TRUE; +} + +int mail_index_update_flags(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, MailFlags flags, + int external_change) +{ + i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE); + i_assert(seq != 0); + + if (flags == rec->msg_flags) + return TRUE; /* no changes */ + + index_mark_flag_changes(index, rec, rec->msg_flags, flags); + + rec->msg_flags = flags; + return mail_modifylog_add_flags(index->modifylog, seq, + rec->uid, external_change); +} + +int mail_index_append(MailIndex *index, MailIndexRecord **rec) +{ + off_t pos; + + i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE); + + (*rec)->uid = index->header->next_uid++; + + pos = lseek(index->fd, 0, SEEK_END); + if (pos == (off_t)-1) { + index_set_error(index, "lseek() failed with file %s: %m", + index->filepath); + return FALSE; + } + + if (write(index->fd, *rec, sizeof(MailIndexRecord)) != + sizeof(MailIndexRecord)) { + index_set_error(index, "Error appending to file %s: %m", + index->filepath); + return FALSE; + } + + index->header->messages_count++; + index_mark_flag_changes(index, *rec, 0, (*rec)->msg_flags); + + if (index->hash != NULL) + mail_hash_update(index->hash, (*rec)->uid, pos); + + index->dirty_mmap = TRUE; + if (!mmap_update(index)) + return FALSE; + + *rec = (MailIndexRecord *) ((char *) index->mmap_base + pos); + return TRUE; +} + +const char *mail_index_get_last_error(MailIndex *index) +{ + return index->error; +} + +int mail_index_is_inconsistency_error(MailIndex *index) +{ + return index->inconsistent; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,356 @@ +#ifndef __MAIL_INDEX_H +#define __MAIL_INDEX_H + +#include "message-parser.h" +#include "imap-util.h" + +#define MAIL_INDEX_VERSION 1 + +#define INDEX_FILE_PREFIX ".imap.index" + +enum { + MAIL_INDEX_COMPAT_LITTLE_ENDIAN = 0x01 +}; + +enum { + /* Rebuild flag is set while index is being rebuilt or when + some error is noticed in the index file. If this flag is set, + the index shouldn't be used before rebuilding it. */ + MAIL_INDEX_FLAG_REBUILD = 0x01, + MAIL_INDEX_FLAG_FSCK = 0x02, + MAIL_INDEX_FLAG_CACHE_FIELDS = 0x04, + MAIL_INDEX_FLAG_COMPRESS = 0x08, + MAIL_INDEX_FLAG_COMPRESS_DATA = 0x10, + MAIL_INDEX_FLAG_REBUILD_HASH = 0x20 +}; + +typedef enum { + /* First MUST become a field that ALWAYS exists. This is because some + code which goes through all fields does it by calling + lookup_field(.., .., 1) and next() after that. If the first field + didn't exist, nothing would be found. + + Location field is a good first field anyway, it's the one most + often needed. With maildir format, it's the file name and with + mbox format it's the file position as a string. */ + FIELD_TYPE_LOCATION = 0x0001, + FIELD_TYPE_ENVELOPE = 0x0002, + FIELD_TYPE_BODY = 0x0004, + FIELD_TYPE_BODYSTRUCTURE = 0x0008, + FIELD_TYPE_MESSAGEID = 0x0010, + FIELD_TYPE_FROM = 0x0020, + FIELD_TYPE_TO = 0x0040, + FIELD_TYPE_CC = 0x0080, + FIELD_TYPE_BCC = 0x0100, + FIELD_TYPE_SUBJECT = 0x0200, + + FIELD_TYPE_LAST = 0x0400, + FIELD_TYPE_MAX_BITS = 10 +} MailField; + +#define IS_HEADER_FIELD(field) \ + (((field) & (FIELD_TYPE_MESSAGEID | FIELD_TYPE_FROM | FIELD_TYPE_TO | \ + FIELD_TYPE_CC | FIELD_TYPE_BCC | FIELD_TYPE_SUBJECT)) != 0) + +typedef enum { + MAIL_LOCK_UNLOCK = 0, + MAIL_LOCK_SHARED, + MAIL_LOCK_EXCLUSIVE +} MailLockType; + +typedef struct _MailIndex MailIndex; +typedef struct _MailIndexData MailIndexData; +typedef struct _MailHash MailHash; +typedef struct _MailModifyLog MailModifyLog; + +typedef struct _MailIndexHeader MailIndexHeader; +typedef struct _MailIndexDataHeader MailIndexDataHeader; + +typedef struct _MailIndexRecord MailIndexRecord; +typedef struct _MailIndexDataRecord MailIndexDataRecord; + +typedef struct _MailIndexUpdate MailIndexUpdate; + +struct _MailIndexHeader { + unsigned char compat_data[4]; + /* 0 = flags, + 1 = sizeof(unsigned int), + 2 = sizeof(time_t), + 3 = sizeof(off_t) */ + + unsigned int version; + unsigned int indexid; + + unsigned int flags; + unsigned int cache_fields; + + off_t first_hole_position; + unsigned int first_hole_records; + + unsigned int uid_validity; + unsigned int next_uid; + + unsigned int messages_count; + unsigned int seen_messages_count; + unsigned int deleted_messages_count; + unsigned int last_nonrecent_uid; + + /* these UIDs may not exist and may not even be unseen */ + unsigned int first_unseen_uid_lowwater; + unsigned int first_deleted_uid_lowwater; + + unsigned int reserved_for_future_usage[5]; +}; + +struct _MailIndexDataHeader { + unsigned int indexid; + off_t deleted_space; +}; + +struct _MailIndexRecord { + unsigned int uid; + unsigned int msg_flags; /* MailFlags */ + time_t internal_date; + time_t sent_date; + + off_t data_position; + unsigned int data_size; + + unsigned int cached_fields; + unsigned int header_size; + unsigned int body_size; + unsigned int full_virtual_size; +}; + +#define MSG_HAS_VALID_CRLF_DATA(rec) \ + ((rec)->header_size + (rec)->body_size == (rec)->full_virtual_size) + +struct _MailIndexDataRecord { + unsigned int field; /* MailField */ + unsigned int full_field_size; + char data[MEM_ALIGN_SIZE]; /* variable size */ +}; + +#define DATA_RECORD_SIZE(rec) \ + (sizeof(MailIndexDataRecord) - MEM_ALIGN_SIZE + (rec)->full_field_size) + +struct _MailIndex { + int (*open)(MailIndex *index, int update_recent); + int (*open_or_create)(MailIndex *index, int update_recent); + + /* Free index from memory. */ + void (*free)(MailIndex *index); + + /* Lock/unlock index. May block. Note that unlocking must not + reset error from get_last_error() as unlocking can be done as + a cleanup after some other function failed. Index is always + mmap()ed after set_lock() succeeds. + + Trying to change a shared lock into exclusive lock is a fatal + error, since it may create a deadlock. Even though operating + system should detect it and fail, it's not a good idea to even + let it happen. Better ways to do this would be to a) mark the + data to be updated later, b) use try_lock() if the update is + preferred but not required, c) unlock + lock again, but make + sure that won't create race conditions */ + int (*set_lock)(MailIndex *index, MailLockType lock_type); + + /* Try locking the index. Returns TRUE if the lock was got and + FALSE if lock isn't possible to get currently or some other error + occured. Never blocks. */ + int (*try_lock)(MailIndex *index, MailLockType lock_type); + + /* Rebuild the whole index. Note that this changes the indexid + so all the other files must also be rebuilt after this call. + Index MUST NOT have shared lock, exclusive lock or no lock at all + is fine. Note that this function may leave the index exclusively + locked. */ + int (*rebuild)(MailIndex *index); + + /* Verify that the index is valid. If anything invalid is found, + rebuild() is called. Same locking issues as with rebuild(). */ + int (*fsck)(MailIndex *index); + + /* Synchronize the index with the mailbox. Same locking issues as + with rebuild(). */ + int (*sync)(MailIndex *index); + + /* Returns the index header (never fails). The index needs to be + locked before calling this function, and must be kept locked as + long as you keep using the returned structure. */ + MailIndexHeader *(*get_header)(MailIndex *index); + + /* sequence -> data lookup. The index needs to be locked before calling + this function, and must be kept locked as long as you keep using + the returned structure. */ + MailIndexRecord *(*lookup)(MailIndex *index, unsigned int seq); + + /* Return the next record after specified record, or NULL if it was + last record. The index must be locked all the time between + lookup() and last next() call. */ + MailIndexRecord *(*next)(MailIndex *index, MailIndexRecord *rec); + + /* First first existing UID in range. */ + MailIndexRecord *(*lookup_uid_range)(MailIndex *index, + unsigned int first_uid, + unsigned int last_uid); + + /* Find field from specified record, or NULL if it's not in index. */ + const char *(*lookup_field)(MailIndex *index, MailIndexRecord *rec, + MailField field); + + /* Returns sequence for given message. */ + unsigned int (*get_sequence)(MailIndex *index, MailIndexRecord *rec); + + /* Open mail file lseek()ed to beginning position and return the + file handle. */ + int (*open_mail)(MailIndex *index, MailIndexRecord *rec, + off_t *offset, size_t *size); + + /* Expunge a mail from index. Hash and modifylog is also updated. The + index must be exclusively locked before calling this function. + If seq is 0, the modify log isn't updated. This is useful if + after append() something goes wrong and you wish to delete the + mail immediately. If external_change is TRUE, the modify log is + always written. + + Note that the sequence numbers also update immediately after this + call, so if you want to delete messages 1..4 just call this + function 4 times with seq being 1. */ + int (*expunge)(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, int external_change); + + /* Update mail flags. The index must be exclusively locked before + calling this function. This shouldn't be called in the middle of + update_begin() as it may modify location field. */ + int (*update_flags)(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, MailFlags flags, + int external_change); + + /* Append a new record to index. The index must be exclusively + locked before calling this function. The record pointer is + updated to the mmap()ed record. rec->uid field is updated by this + function, nothing else is touched. */ + int (*append)(MailIndex *index, MailIndexRecord **rec); + + /* Updating fields happens by calling update_begin(), one or more + update_field()s and finally update_end() which does the actual + updating. The index must be exclusively locked all this time. + update_begin() and update_field() functions cannot fail. + + The extra_space parameter for update_field() specifies the amount + of extra empty space we should leave after the value, so that if + the field grows in future it could be expanded without copying it + to end of file. When the field already exists, the extra_space + is ignored. + + The files may not actually be updated until after you've unlocked + the file. */ + MailIndexUpdate *(*update_begin)(MailIndex *index, + MailIndexRecord *rec); + int (*update_end)(MailIndexUpdate *update); + + void (*update_field)(MailIndexUpdate *update, MailField field, + const char *value, unsigned int extra_space); + + /* Returns last error message */ + const char *(*get_last_error)(MailIndex *index); + + /* Returns TRUE if index is now in inconsistent state with the + previous known state, meaning that the message IDs etc. may + have changed - only way to recover this would be to fully close + the mailbox and reopen it. With IMAP connection this would mean + a forced disconnection since we can't do forced CLOSE. */ + int (*is_inconsistency_error)(MailIndex *index); + +/* private: */ + MailIndexData *data; + MailHash *hash; + MailModifyLog *modifylog; + + char *dir; /* directory where to place the index files */ + char *filepath; /* index file path */ + unsigned int indexid; + + char *mbox_path; /* mbox-specific path to the actual mbox file */ + off_t mbox_size; /* last synced size of mbox file */ + int mbox_locks; + + int fd; /* opened index file */ + char *error; /* last error message */ + + void *mmap_base; + size_t mmap_length; + + MailLockType lock_type; + + MailIndexHeader *header; + MailIndexRecord *last_lookup; + unsigned int last_lookup_seq; + unsigned int first_recent_uid; + + unsigned int modifylog_id; + time_t file_sync_stamp; + + /* these fields are OR'ed to the fields in index header once we + get around grabbing exclusive lock */ + unsigned int set_flags; + unsigned int set_cache_fields; + + unsigned int opened:1; + unsigned int updating:1; + unsigned int inconsistent:1; + unsigned int dirty_mmap:1; +}; + +/* defaults - same as above but prefixed with mail_index_. */ +int mail_index_open(MailIndex *index, int update_recent); +int mail_index_open_or_create(MailIndex *index, int update_recent); +int mail_index_set_lock(MailIndex *index, MailLockType lock_type); +int mail_index_try_lock(MailIndex *index, MailLockType lock_type); +int mail_index_fsck(MailIndex *index); +MailIndexHeader *mail_index_get_header(MailIndex *index); +MailIndexRecord *mail_index_lookup(MailIndex *index, unsigned int seq); +MailIndexRecord *mail_index_next(MailIndex *index, MailIndexRecord *rec); +MailIndexRecord *mail_index_lookup_uid_range(MailIndex *index, + unsigned int first_uid, + unsigned int last_uid); +const char *mail_index_lookup_field(MailIndex *index, MailIndexRecord *rec, + MailField field); +unsigned int mail_index_get_sequence(MailIndex *index, MailIndexRecord *rec); +int mail_index_expunge(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, int external_change); +int mail_index_update_flags(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, MailFlags flags, + int external_change); +int mail_index_append(MailIndex *index, MailIndexRecord **rec); +MailIndexUpdate *mail_index_update_begin(MailIndex *index, + MailIndexRecord *rec); +int mail_index_update_end(MailIndexUpdate *update); +void mail_index_update_field(MailIndexUpdate *update, MailField field, + const char *value, unsigned int extra_space); +const char *mail_index_get_last_error(MailIndex *index); +int mail_index_is_inconsistency_error(MailIndex *index); + +/* INTERNAL: */ +void mail_index_init_header(MailIndexHeader *hdr); +void mail_index_close(MailIndex *index); +int mail_index_rebuild_all(MailIndex *index); +int mail_index_sync_file(MailIndex *index); +void mail_index_update_headers(MailIndexUpdate *update, + const char *msg, size_t size, + MessageHeaderFunc header_func, void *user_data); + +/* off_t to index file for given record */ +#define INDEX_FILE_POSITION(index, ptr) \ + ((off_t) ((char *) (ptr) - (char *) ((index)->mmap_base))) + +/* index number for off_t position */ +#define INDEX_POSITION_INDEX(pos) \ + (((pos) - sizeof(MailIndexHeader)) / sizeof(MailIndexRecord)) + +/* mark the index corrupted */ +#define INDEX_MARK_CORRUPTED(index) \ + STMT_START { (index)->set_flags |= MAIL_INDEX_FLAG_REBUILD; } STMT_END + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-lockdir.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,158 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "hostpid.h" +#include "unlink-lockfiles.h" +#include "mail-index.h" +#include "mail-index-util.h" +#include "mail-lockdir.h" + +#include <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#define DIRLOCK_FILE_PREFIX ".imap.dirlock" + +/* 0.1 .. 0.2msec */ +#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000) + +/* The dirlock should be used only while creating the index file. After the + header is written, the file itself should be locked and dirlock dropped + before index is built. So, this value shouldn't be very large, probably + even a few seconds would more than enough but we'll use a safe 10 seconds + by default. */ +#define MAX_LOCK_WAIT_SECONDS 10 + +/* Non-local locks have a life time of 30 minutes, just to be sure that + small clock differences won't break things. */ +#define NFS_LOCK_TIMEOUT (60*30) + +static int mail_index_cleanup_dir_locks(const char *dir) +{ + const char *hostprefix, *path; + struct stat st; + + hostprefix = t_strconcat(DIRLOCK_FILE_PREFIX ".", + my_hostname, ".", NULL); + + unlink_lockfiles(dir, hostprefix, DIRLOCK_FILE_PREFIX ".", + time(NULL) - NFS_LOCK_TIMEOUT); + + /* if hard link count has dropped to 1, we've unlocked the file */ + path = t_strconcat(dir, "/" DIRLOCK_FILE_PREFIX, NULL); + if (stat(path, &st) == 0 && st.st_nlink == 1) { + /* only itself, safe to delete */ + (void)unlink(path); + return TRUE; + } + + return FALSE; +} + +static int mail_index_unlock_dir(MailIndex *index, const char *path, + const char *lockpath) +{ + struct stat st, lockst; + + if (stat(lockpath, &st) != 0) { + index_set_error(index, "stat() failed for lock file %s: %m", + lockpath); + return FALSE; + } + + if (st.st_nlink > 1) { + /* make sure we're really the one who's locked it */ + if (stat(path, &lockst) != 0) { + index_set_error(index, "stat() failed for lock file " + "%s: %m", path); + return FALSE; + } + + if (st.st_dev != lockst.st_dev || + st.st_ino != lockst.st_ino) { + index_set_error(index, "Unlocking file %s failed: " + "we're not the lock owner " + "(%lu,%lu vs %lu,%lu)", lockpath, + (unsigned long) st.st_dev, + (unsigned long) st.st_ino, + (unsigned long) lockst.st_dev, + (unsigned long) lockst.st_ino); + return FALSE; + } + } + + /* first unlink the actual lock file */ + if (unlink(lockpath) == -1) { + index_set_error(index, "unlink() failed for lock file %s: %m", + lockpath); + return FALSE; + } + + (void)unlink(path); + return TRUE; +} + +int mail_index_lock_dir(MailIndex *index, MailLockType lock_type) +{ + struct stat st; + const char *path, *lockpath; + int fd, orig_errno, first; + time_t max_wait_time; + + i_assert(lock_type == MAIL_LOCK_EXCLUSIVE || + lock_type == MAIL_LOCK_UNLOCK); + + hostpid_init(); + + /* use .dirlock.host.pid as our lock indicator file and + .dirlock as the real lock */ + path = t_strconcat(index->dir, "/" DIRLOCK_FILE_PREFIX ".", + my_hostname, ".", my_pid, NULL); + lockpath = t_strconcat(index->dir, "/" DIRLOCK_FILE_PREFIX, NULL); + + if (lock_type == MAIL_LOCK_UNLOCK) + return mail_index_unlock_dir(index, path, lockpath); + + (void)unlink(path); + fd = open(path, O_RDWR | O_CREAT | O_EXCL, 0660); + if (fd == -1) { + index_set_error(index, "Can't create lock file %s: %m", path); + return FALSE; + } + + /* try to link the file into lock file. */ + first = TRUE; max_wait_time = time(NULL) + MAX_LOCK_WAIT_SECONDS; + while (link(path, lockpath) == -1) { + if (errno != EEXIST) { + orig_errno = errno; + + /* NFS may die and link() fail even if it really + was created */ + if (stat(path, &st) == 0 && st.st_nlink == 2) + break; + + index_set_error(index, "link(%s, %s) lock failed: %m", + path, lockpath); + return FALSE; + } + + if (first) { + /* cleanup lock files once */ + first = FALSE; + if (mail_index_cleanup_dir_locks(index->dir)) + continue; /* lock was deleted, try again */ + } + + if (time(NULL) > max_wait_time) { + index_set_error(index, "Timeout waiting lock in " + "directory %s", index->dir); + return FALSE; + } + + usleep(LOCK_RANDOM_USLEEP_TIME); + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-lockdir.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,7 @@ +#ifndef __MAIL_LOCKDIR_H +#define __MAIL_LOCKDIR_H + +/* Exclusively lock whole directory where index is located. */ +int mail_index_lock_dir(MailIndex *index, MailLockType lock_type); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-messageset.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,311 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mail-index.h" +#include "mail-index-util.h" +#include "mail-hash.h" +#include "mail-modifylog.h" +#include "mail-messageset.h" + +static unsigned int get_next_number(const char **str) +{ + unsigned int num; + + num = 0; + while (**str != '\0') { + if (**str < '0' || **str > '9') + break; + + num = num*10 + **str - '0'; + (*str)++; + } + + return num; +} + +static int mail_index_foreach(MailIndex *index, + unsigned int seq, unsigned int seq2, + MsgsetForeachFunc func, void *user_data) +{ + MailIndexRecord *rec; + const unsigned int *expunges; + unsigned int expunges_before; + int expunges_found; + + if (seq > seq2) { + /* Second sequence can't be smaller than first - we could swap + them but I think it's a bug in client if it does this, + and better complain about it immediately than later let + them wonder why it doesn't work with other imapds.. */ + index_set_error(index, "Invalid messageset range: %u > %u", + seq, seq2); + return -1; + } + + /* get list of expunged messages in our range. the expunges_before + can be used to calculate the current real sequence position */ + expunges = mail_modifylog_seq_get_expunges(index->modifylog, seq, seq2, + &expunges_before); + i_assert(expunges_before < seq); + expunges_found = *expunges != '\0'; + + /* get the first non-expunged message. note that if all messages + were expunged in the range, this points outside wanted range. */ + rec = index->lookup(index, seq - expunges_before); + for (; rec != NULL; seq++) { + /* skip expunged sequences */ + i_assert(rec->uid != 0); + + while (*expunges != 0 && *expunges < rec->uid) { + expunges++; + seq++; + } + i_assert(*expunges != rec->uid); + + if (seq > seq2) + break; + + if (!func(index, rec, seq, user_data)) + return 0; + + rec = index->next(index, rec); + } + + if (rec == NULL && index->get_last_error(index) != NULL) { + /* error occured */ + return -1; + } + + return !expunges_found && seq > seq2 ? 1 : 2; +} + +int mail_index_messageset_foreach(MailIndex *index, const char *messageset, + unsigned int messages_count, + MsgsetForeachFunc func, void *user_data) +{ + const char *input; + unsigned int seq, seq2; + int ret, all_found; + + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + if (messages_count == 0) { + /* no messages in mailbox */ + return 1; + } + + all_found = TRUE; + input = messageset; + while (*input != '\0') { + if (*input == '*') { + /* last message */ + seq = messages_count; + input++; + } else { + seq = get_next_number(&input); + if (seq == 0) { + index_set_error(index, "Invalid messageset: " + "%s", messageset); + return -1; + } + } + + if (*input != ':') + seq2 = seq; + else { + /* first:last range */ + input++; + + if (*input != '*') { + seq2 = get_next_number(&input); + if (seq2 == 0) { + index_set_error(index, "Invalid " + "messageset: %s", + messageset); + return -1; + } + + if (seq2 > messages_count) { + /* too large .. ignore silently */ + seq2 = messages_count; + } + } else { + seq2 = messages_count; + input++; + } + } + + if (*input == ',') + input++; + else if (*input != '\0') { + index_set_error(index, "Unexpected char '%c' " + "with messageset: %s", + *input, messageset); + return -1; + } + + if (seq > messages_count) { + /* too large .. ignore silently */ + } else { + ret = mail_index_foreach(index, seq, seq2, + func, user_data); + if (ret <= 0) + return ret; + if (ret == 2) + all_found = FALSE; + } + } + + return all_found ? 1 : 2; +} + +static int mail_index_uid_foreach(MailIndex *index, + unsigned int uid, unsigned int uid2, + unsigned int max_sequence, + MsgsetForeachFunc func, void *user_data) +{ + MailIndexRecord *rec; + off_t pos; + const unsigned int *expunges; + unsigned int seq; + int expunges_found; + + if (uid > uid2) { + /* not allowed - see mail_index_foreach() */ + index_set_error(index, "Invalid uidset range: %u > %u", + uid, uid2); + return -1; + } + + /* get list of expunged messages in our range. */ + expunges = mail_modifylog_uid_get_expunges(index->modifylog, uid, uid2); + expunges_found = *expunges != '\0'; + + /* skip expunged messages at the beginning */ + while (*expunges == uid) { + expunges++; + + if (++uid == uid2) { + /* all were expunged */ + return 2; + } + } + + /* since we skipped the known expunged messages at the beginning + and our UIDs are contiguously allocated, the first hash lookup + _should_ work.. */ + pos = mail_hash_lookup_uid(index->hash, uid); + if (pos != 0) { + rec = (MailIndexRecord *) ((char *) index->mmap_base + pos); + } else { + /* ..however if for any reason it doesn't, + still handle it properly */ + if (uid == uid2) + return 2; + + rec = index->lookup_uid_range(index, uid+1, uid2); + if (rec == NULL) + return 2; + } + + seq = index->get_sequence(index, rec); + while (rec != NULL && rec->uid <= uid2 && seq <= max_sequence) { + uid = rec->uid; + while (*expunges != 0 && *expunges < rec->uid) { + expunges++; + seq++; + } + i_assert(*expunges != rec->uid); + + if (!func(index, rec, seq, user_data)) + return 0; + + seq++; + rec = index->next(index, rec); + } + + if (rec == NULL && index->get_last_error(index) != NULL) { + /* error occured */ + return -1; + } + + return !expunges_found && uid == uid2 ? 1 : 2; +} + +int mail_index_uidset_foreach(MailIndex *index, const char *uidset, + unsigned int messages_count, + MsgsetForeachFunc func, void *user_data) +{ + MailIndexRecord *rec; + const char *input; + unsigned int uid, uid2; + int ret, all_found; + + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + if (messages_count == 0) { + /* no messages in mailbox */ + return 1; + } + + all_found = TRUE; + input = uidset; + while (*input != '\0') { + if (*input == '*') { + /* last message */ + rec = index->lookup(index, messages_count); + uid = rec == NULL ? 0 : rec->uid; + input++; + } else { + uid = get_next_number(&input); + if (uid == 0) { + index_set_error(index, "Invalid uidset: %s", + uidset); + return -1; + } + } + + if (*input != ':') + uid2 = uid; + else { + /* first:last range */ + input++; + + if (*input != '*') { + uid2 = get_next_number(&input); + if (uid2 == 0) { + index_set_error(index, + "Invalid uidset: %s", + uidset); + return -1; + } + } else { + uid2 = index->header->next_uid-1; + input++; + } + } + + if (*input == ',') + input++; + else if (*input != '\0') { + index_set_error(index, + "Unexpected char '%c' with uidset: %s", + *input, uidset); + return -1; + } + + if (uid >= index->header->next_uid) { + /* too large .. ignore silently */ + } else { + ret = mail_index_uid_foreach(index, uid, uid2, + messages_count, + func, user_data); + if (ret <= 0) + return ret; + if (ret == 2) + all_found = FALSE; + } + } + + return all_found ? 1 : 2; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-messageset.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,21 @@ +#ifndef __MAIL_MESSAGESET_H +#define __MAIL_MESSAGESET_H + +#include "mail-index.h" + +/* If FALSE is returned, the loop is stopped. */ +typedef int (*MsgsetForeachFunc)(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, void *user_data); + +/* Returns -1 if error occured, 0 if foreach-func returned FALSE, + 1 if everything was ok or 2 if some of the given sequences were expunged */ +int mail_index_messageset_foreach(MailIndex *index, const char *messageset, + unsigned int messages_count, + MsgsetForeachFunc func, void *user_data); + +/* Like messageset_foreach() but for UIDs. */ +int mail_index_uidset_foreach(MailIndex *index, const char *uidset, + unsigned int messages_count, + MsgsetForeachFunc func, void *user_data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-modifylog.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,674 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mmap-util.h" +#include "mail-index.h" +#include "mail-index-util.h" +#include "mail-modifylog.h" + +#include <stdlib.h> +#include <fcntl.h> + +/* Maximum size for modify log (isn't exact) */ +#define MAX_MODIFYLOG_SIZE 10240 + +#define MODIFYLOG_FILE_POSITION(log, ptr) \ + ((int) ((char *) (ptr) - (char *) (log)->mmap_base)) + +struct _MailModifyLog { + MailIndex *index; + + int fd; + char *filepath; + + void *mmap_base; + size_t mmap_length; + + ModifyLogHeader *header; + size_t synced_position; + unsigned int synced_id, mmaped_id; + + unsigned int modified:1; + unsigned int dirty_mmap:1; + unsigned int second_log:1; +}; + +static int file_lock(int fd, int wait_lock, int lock_type) +{ + struct flock fl; + + fl.l_type = lock_type; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + if (fcntl(fd, wait_lock ? F_SETLKW : F_SETLK, &fl) == -1) { + if (errno == EACCES) + return 0; + return -1; + } + + return 1; +} + +/* Returns 1 = ok, 0 = failed to get the lock, -1 = error */ +static int mail_modifylog_try_lock(MailModifyLog *log, int lock_type) +{ + int ret; + + ret = file_lock(log->fd, FALSE, lock_type); + if (ret == -1) { + index_set_error(log->index, "fcntl() failed with file %s: %m", + log->filepath); + } + + return ret; +} + +static int mail_modifylog_wait_lock(MailModifyLog *log) +{ + if (file_lock(log->fd, TRUE, F_RDLCK) < 1) { + index_set_error(log->index, "fcntl() failed with file %s: %m", + log->filepath); + return FALSE; + } + + return TRUE; +} + +/* returns 1 = yes, 0 = no, -1 = error */ +static int mail_modifylog_have_other_users(MailModifyLog *log) +{ + int ret; + + /* try grabbing exclusive lock */ + ret = mail_modifylog_try_lock(log, F_WRLCK); + if (ret == -1) + return -1; + + /* revert back to shared lock */ + switch (mail_modifylog_try_lock(log, F_WRLCK)) { + case 0: + /* shouldn't happen */ + index_set_error(log->index, "fcntl(F_WRLCK -> F_RDLCK) " + "failed with file %s", log->filepath); + /* fall through */ + case -1: + return -1; + } + + return ret == 0 ? 1 : 0; +} + +static int mmap_update(MailModifyLog *log) +{ + unsigned int extra; + + if (!log->dirty_mmap && log->mmaped_id == log->header->sync_id) + return TRUE; + + if (log->mmap_base != NULL) + (void)munmap(log->mmap_base, log->mmap_length); + + log->mmap_base = mmap_rw_file(log->fd, &log->mmap_length); + if (log->mmap_base == MAP_FAILED) { + log->mmap_base = NULL; + log->header = NULL; + index_set_error(log->index, + "modify log: mmap() failed with file %s: %m", + log->filepath); + return FALSE; + } + + if (log->mmap_length < sizeof(ModifyLogHeader)) { + /* FIXME: we could do better.. */ + (void)unlink(log->filepath); + i_assert(0); + } + + extra = (log->mmap_length - sizeof(ModifyLogHeader)) % + sizeof(ModifyLogRecord); + + if (extra != 0) { + /* partial write or corrupted - + truncate the file to valid length */ + log->mmap_length -= extra; + (void)ftruncate(log->fd, (off_t) log->mmap_length); + } + + log->dirty_mmap = FALSE; + log->header = log->mmap_base; + log->mmaped_id = log->header->sync_id; + return TRUE; +} + +static MailModifyLog *mail_modifylog_new(MailIndex *index) +{ + MailModifyLog *log; + + log = i_new(MailModifyLog, 1); + log->fd = -1; + log->index = index; + log->dirty_mmap = TRUE; + + index->modifylog = log; + return log; +} + +static void mail_modifylog_close(MailModifyLog *log) +{ + log->dirty_mmap = TRUE; + + if (log->mmap_base != NULL) { + munmap(log->mmap_base, log->mmap_length); + log->mmap_base = NULL; + } + + if (log->fd != -1) { + (void)close(log->fd); + log->fd = -1; + } + + i_free(log->filepath); +} + +static int mail_modifylog_init_fd(MailModifyLog *log, int fd, + const char *path) +{ + ModifyLogHeader hdr; + + /* write header */ + memset(&hdr, 0, sizeof(hdr)); + hdr.indexid = log->index->indexid; + + if (write(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + index_set_error(log->index, "write() failed for modify " + "log %s: %m", path); + return FALSE; + } + + if (ftruncate(fd, sizeof(hdr)) == -1) { + index_set_error(log->index, "ftruncate() failed for modify " + "log %s: %m", path); + return FALSE; + } + + return TRUE; +} + +static int mail_modifylog_open_and_init_file(MailModifyLog *log, + const char *path) +{ + int fd, ret; + + fd = open(path, O_RDWR | O_CREAT, 0660); + if (fd == -1) { + index_set_error(log->index, "Error opening modify log " + "file %s: %m", path); + return FALSE; + } + + ret = file_lock(fd, FALSE, F_WRLCK); + if (ret == -1) { + index_set_error(log->index, "Error locking modify log " + "file %s: %m", path); + } + + if (ret == 1 && mail_modifylog_init_fd(log, fd, path)) { + mail_modifylog_close(log); + + log->fd = fd; + log->filepath = i_strdup(path); + return TRUE; + } + + (void)close(fd); + return FALSE; +} + +int mail_modifylog_create(MailIndex *index) +{ + MailModifyLog *log; + const char *path; + + i_assert(index->lock_type == MAIL_LOCK_EXCLUSIVE); + + log = mail_modifylog_new(index); + + path = t_strconcat(log->index->filepath, ".log", NULL); + if (!mail_modifylog_open_and_init_file(log, path) || + !mail_modifylog_wait_lock(log) || + !mmap_update(log)) { + /* fatal failure */ + mail_modifylog_free(log); + return FALSE; + } + + log->synced_id = log->header->sync_id; + log->synced_position = log->mmap_length; + return TRUE; +} + +/* Returns 1 = ok, 0 = full, -1 = error */ +static int mail_modifylog_open_and_verify(MailModifyLog *log, const char *path) +{ + ModifyLogHeader hdr; + int fd, ret; + + fd = open(path, O_RDWR); + if (fd == -1) { + if (errno != ENOENT) { + index_set_error(log->index, "Can't open modify log " + "file %s: %m", path); + } + return -1; + } + + ret = 1; + if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr)) { + index_set_error(log->index, "read() failed when for modify " + "log file %s: %m", path); + ret = -1; + } + + if (ret != -1 && hdr.indexid != log->index->indexid) { + index_set_error(log->index, "IndexID mismatch for modify log " + "file %s", path); + ret = -1; + } + + if (ret != -1 && hdr.sync_id == SYNC_ID_FULL) { + /* full */ + ret = 0; + } + + if (ret == 1) { + log->fd = fd; + log->filepath = i_strdup(path); + } else { + (void)close(fd); + } + + return ret; +} + +static int mail_modifylog_find_or_create(MailModifyLog *log) +{ + const char *path1, *path2; + int i; + + for (i = 0; i < 2; i++) { + /* first try <index>.log */ + path1 = t_strconcat(log->index->filepath, ".log", NULL); + if (mail_modifylog_open_and_verify(log, path1) == 1) + return TRUE; + + /* then <index>.log.2 */ + path2 = t_strconcat(log->index->filepath, ".log.2", NULL); + if (mail_modifylog_open_and_verify(log, path2) == 1) + return TRUE; + + /* try creating/reusing them */ + if (mail_modifylog_open_and_init_file(log, path1)) + return TRUE; + + if (mail_modifylog_open_and_init_file(log, path2)) + return TRUE; + + /* maybe the file was just switched, check the logs again */ + } + + index_set_error(log->index, "We could neither use nor create " + "the modify log for index %s", log->index->filepath); + return FALSE; +} + +int mail_modifylog_open_or_create(MailIndex *index) +{ + MailModifyLog *log; + + log = mail_modifylog_new(index); + + if (!mail_modifylog_find_or_create(log) || + !mail_modifylog_wait_lock(log) || + !mmap_update(log)) { + /* fatal failure */ + mail_modifylog_free(log); + return FALSE; + } + + log->synced_id = log->header->sync_id; + log->synced_position = log->mmap_length; + return TRUE; +} + +void mail_modifylog_free(MailModifyLog *log) +{ + log->index->modifylog = NULL; + + mail_modifylog_close(log); + i_free(log); +} + +int mail_modifylog_sync_file(MailModifyLog *log) +{ + if (!log->modified) + return TRUE; + + if (log->mmap_base != NULL) { + if (msync(log->mmap_base, log->mmap_length, MS_SYNC) == -1) { + index_set_error(log->index, "msync() failed for %s: %m", + log->filepath); + return FALSE; + } + } + + if (fsync(log->fd) == -1) { + index_set_error(log->index, "fsync() failed for %s: %m", + log->filepath); + return FALSE; + } + + log->modified = FALSE; + return TRUE; +} + +static int mail_modifylog_append(MailModifyLog *log, ModifyLogRecord *rec, + int external_change) +{ + i_assert(log->index->lock_type == MAIL_LOCK_EXCLUSIVE); + i_assert(rec->seq != 0); + i_assert(rec->uid != 0); + + if (!external_change) { + switch (mail_modifylog_have_other_users(log)) { + case 0: + /* we're the only one having this log open, + no need for modify log. */ + return TRUE; + case -1: + return FALSE; + } + } + + if (lseek(log->fd, 0, SEEK_END) == (off_t)-1) { + index_set_error(log->index, "lseek() failed with file %s: %m", + log->filepath); + return FALSE; + } + + if (write(log->fd, rec, sizeof(ModifyLogRecord)) != + sizeof(ModifyLogRecord)) { + index_set_error(log->index, "Error appending to file %s: %m", + log->filepath); + return FALSE; + } + + log->header->sync_id++; + log->modified = TRUE; + log->dirty_mmap = TRUE; + + if (!external_change) { + log->synced_id = log->header->sync_id; + log->synced_position += sizeof(ModifyLogRecord); + } + return TRUE; +} + +int mail_modifylog_add_expunge(MailModifyLog *log, unsigned int seq, + unsigned int uid, int external_change) +{ + ModifyLogRecord rec; + + /* expunges must not be added when log isn't synced */ + i_assert(external_change || log->synced_id == log->header->sync_id); + + rec.type = RECORD_TYPE_EXPUNGE; + rec.seq = seq; + rec.uid = uid; + return mail_modifylog_append(log, &rec, external_change); +} + +int mail_modifylog_add_flags(MailModifyLog *log, unsigned int seq, + unsigned int uid, int external_change) +{ + ModifyLogRecord rec; + + rec.type = RECORD_TYPE_FLAGS_CHANGED; + rec.seq = seq; + rec.uid = uid; + return mail_modifylog_append(log, &rec, external_change); +} + +ModifyLogRecord *mail_modifylog_get_nonsynced(MailModifyLog *log, + unsigned int *count) +{ + ModifyLogRecord *rec, *end_rec; + + i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK); + + *count = 0; + if (!mmap_update(log)) + return NULL; + + i_assert(log->synced_position <= log->mmap_length); + i_assert(log->synced_position >= sizeof(ModifyLogHeader)); + + rec = (ModifyLogRecord *) ((char *) log->mmap_base + + log->synced_position); + end_rec = (ModifyLogRecord *) ((char *) log->mmap_base + + log->mmap_length); + *count = (unsigned int) (end_rec - rec); + return rec; +} + +static int mail_modifylog_switch_file(MailModifyLog *log) +{ + MailIndex *index = log->index; + + mail_modifylog_free(log); + return mail_modifylog_open_or_create(index); +} + +static void mail_modifylog_try_switch_file(MailModifyLog *log) +{ + const char *path; + + path = t_strconcat(log->index->filepath, + log->second_log ? ".log" : ".log.2", NULL); + + if (mail_modifylog_open_and_init_file(log, path)) + log->header->sync_id = SYNC_ID_FULL; +} + +int mail_modifylog_mark_synced(MailModifyLog *log) +{ + i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK); + + if (log->header->sync_id == SYNC_ID_FULL) { + /* log file is full, switch to next one */ + return mail_modifylog_switch_file(log); + } + + if (log->synced_id == log->header->sync_id) { + /* we are already synced */ + return TRUE; + } + + log->synced_id = log->header->sync_id; + log->synced_position = log->mmap_length; + + log->modified = TRUE; + + if (log->mmap_length > MAX_MODIFYLOG_SIZE) { + /* if the other file isn't locked, switch to it */ + mail_modifylog_try_switch_file(log); + return TRUE; + } + + return TRUE; +} + +static int compare_uint(const void *p1, const void *p2) +{ + const unsigned int *u1 = p1; + const unsigned int *u2 = p2; + + return *u1 < *u2 ? -1 : *u1 > *u2 ? 1 : 0; +} + +const unsigned int * +mail_modifylog_seq_get_expunges(MailModifyLog *log, + unsigned int first_seq, + unsigned int last_seq, + unsigned int *expunges_before) +{ + ModifyLogRecord *rec, *end_rec; + unsigned int last_pos_seq, before, max_records, *arr, *expunges; + + i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK); + + *expunges_before = 0; + + if (!mmap_update(log)) + return NULL; + + /* find the first expunged message that affects our range */ + rec = (ModifyLogRecord *) ((char *) log->mmap_base + + log->synced_position); + end_rec = (ModifyLogRecord *) ((char *) log->mmap_base + + log->mmap_length); + + while (rec < end_rec) { + if (rec->type == RECORD_TYPE_EXPUNGE && rec->seq <= last_seq) + break; + rec++; + } + + if (rec >= end_rec) { + /* none found */ + expunges = t_malloc(sizeof(unsigned int)); + *expunges = 0; + return expunges; + } + + /* allocate memory for the returned array. the file size - synced + position should be quite near the amount of memory we need, unless + there's lots of FLAGS_CHANGED records which is why there's the + second check to make sure it's not unneededly large. */ + max_records = (log->mmap_length - MODIFYLOG_FILE_POSITION(log, rec)) / + sizeof(ModifyLogRecord); + if (max_records > last_seq - first_seq + 1) + max_records = last_seq - first_seq + 1; + + expunges = arr = t_malloc((max_records+1) * sizeof(unsigned int)); + + /* last_pos_seq is updated all the time to contain the last_seq + comparable to current record's seq. number */ + last_pos_seq = last_seq; + + before = 0; + for (; rec < end_rec; rec++) { + if (rec->type != RECORD_TYPE_EXPUNGE) + continue; + + if (rec->seq + before < first_seq) { + /* before our range */ + before++; + last_pos_seq--; + } else if (rec->seq <= last_pos_seq) { + /* within our range */ + last_pos_seq--; + + if (max_records-- == 0) { + /* log contains more data than it should + have - must be corrupted. */ + index_set_error(log->index, + "Modify log %s is corrupted", + log->filepath); + return NULL; + } + + *arr++ = rec->uid; + } + } + *arr = 0; + + /* sort the UID array, not including the terminating 0 */ + qsort(expunges, (unsigned int) (arr - expunges), sizeof(unsigned int), + compare_uint); + + *expunges_before = before; + return expunges; +} + +const unsigned int * +mail_modifylog_uid_get_expunges(MailModifyLog *log, + unsigned int first_uid, + unsigned int last_uid) +{ + /* pretty much copy&pasted from sequence code above .. + kind of annoying */ + ModifyLogRecord *rec, *end_rec; + unsigned int before, max_records, *arr, *expunges; + + i_assert(log->index->lock_type != MAIL_LOCK_UNLOCK); + + if (!mmap_update(log)) + return NULL; + + /* find the first expunged message that affects our range */ + rec = (ModifyLogRecord *) ((char *) log->mmap_base + + log->synced_position); + end_rec = (ModifyLogRecord *) ((char *) log->mmap_base + + log->mmap_length); + + while (rec < end_rec) { + if (rec->type == RECORD_TYPE_EXPUNGE && rec->uid <= last_uid) + break; + rec++; + } + + if (rec >= end_rec) { + /* none found */ + expunges = t_malloc(sizeof(unsigned int)); + *expunges = 0; + return expunges; + } + + /* allocate memory for the returned array. the file size - synced + position should be quite near the amount of memory we need, unless + there's lots of FLAGS_CHANGED records which is why there's the + second check to make sure it's not unneededly large. */ + max_records = (log->mmap_length - MODIFYLOG_FILE_POSITION(log, rec)) / + sizeof(ModifyLogRecord); + if (max_records > last_uid - first_uid + 1) + max_records = last_uid - first_uid + 1; + + expunges = arr = t_malloc((max_records+1) * sizeof(unsigned int)); + + before = 0; + while (rec < end_rec) { + if (rec->type == RECORD_TYPE_EXPUNGE && + rec->uid >= first_uid && rec->uid <= last_uid) { + /* within our range */ + if (max_records-- == 0) { + /* log contains more data than it should + have - must be corrupted. */ + index_set_error(log->index, + "Modify log %s is corrupted", + log->filepath); + return NULL; + } + *arr++ = rec->uid; + } + rec++; + } + *arr = 0; + + /* sort the UID array, not including the terminating 0 */ + qsort(expunges, (unsigned int) (arr - expunges), sizeof(unsigned int), + compare_uint); + + return expunges; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-modifylog.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,67 @@ +#ifndef __MAIL_MODIFYLOG_H +#define __MAIL_MODIFYLOG_H + +typedef enum { + RECORD_TYPE_EXPUNGE, + RECORD_TYPE_FLAGS_CHANGED +} ModifyLogRecordType; + +typedef struct _ModifyLogHeader ModifyLogHeader; +typedef struct _ModifyLogRecord ModifyLogRecord; + +/* if sync_id has this value, the log file is full and should be + deleted or reused. */ +#define SYNC_ID_FULL ((unsigned int)-1) + +struct _ModifyLogHeader { + unsigned int indexid; + unsigned int sync_id; +}; + +struct _ModifyLogRecord { + unsigned int type; + unsigned int seq; + unsigned int uid; +}; + +/* NOTE: All these functions require the index file to be locked. */ + +int mail_modifylog_create(MailIndex *index); +int mail_modifylog_open_or_create(MailIndex *index); +void mail_modifylog_free(MailModifyLog *log); + +/* Append EXPUGE or FLAGS entry to modify log. Index must be exclusively + locked before calling these functions, and modifylog must have been + marked synced within the same lock. */ +int mail_modifylog_add_expunge(MailModifyLog *log, unsigned int seq, + unsigned int uid, int external_change); +int mail_modifylog_add_flags(MailModifyLog *log, unsigned int seq, + unsigned int uid, int external_change); + +/* Synchronize the data into disk */ +int mail_modifylog_sync_file(MailModifyLog *log); + +/* Returns the nonsynced log entries. count is set to number of log records. */ +ModifyLogRecord *mail_modifylog_get_nonsynced(MailModifyLog *log, + unsigned int *count); + +/* Marks the modify log as being synced with in-memory state. */ +int mail_modifylog_mark_synced(MailModifyLog *log); + +/* Finds expunged messages for the given sequence range, and number of + expunged messages before the range. Returns sorted 0-terminated list of + expunged UIDs, or NULL if error occured. */ +const unsigned int * +mail_modifylog_seq_get_expunges(MailModifyLog *log, + unsigned int first_seq, + unsigned int last_seq, + unsigned int *expunges_before); + +/* Returns sorted 0-terminated list of expunged UIDs in given range, + or NULL if error occured. */ +const unsigned int * +mail_modifylog_uid_get_expunges(MailModifyLog *log, + unsigned int first_uid, + unsigned int last_uid); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,18 @@ +noinst_LIBRARIES = libstorage_index_maildir.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index + +libstorage_index_maildir_a_SOURCES = \ + maildir-index.c \ + maildir-build.c \ + maildir-open.c \ + maildir-rebuild.c \ + maildir-sync.c \ + maildir-update.c + +noinst_HEADERS = \ + maildir-index.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-build.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,173 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "maildir-index.h" +#include "mail-index-data.h" +#include "mail-index-util.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <sys/stat.h> + +static MailIndexRecord * +mail_index_record_append(MailIndex *index, time_t internal_date) +{ + MailIndexRecord trec, *rec; + + memset(&trec, 0, sizeof(MailIndexRecord)); + trec.internal_date = internal_date; + + rec = &trec; + if (!index->append(index, &rec)) + return NULL; + + return rec; +} + +static int maildir_index_append_fd(MailIndex *index, int fd, const char *path, + const char *fname) +{ + MailIndexRecord *rec; + MailIndexUpdate *update; + struct stat st; + int failed; + + i_assert(path != NULL); + i_assert(fname != NULL); + + /* check that file size is somewhat reasonable */ + if (fstat(fd, &st) == -1) { + index_set_error(index, "fstat() failed with file %s: %m", path); + return FALSE; + } + + if (st.st_size < 10) { + /* This cannot be a mail file - delete it */ + index_set_error(index, + "Invalid size %lu with mail in %s - deleted", + (unsigned long) st.st_size, path); + (void)unlink(path); + return TRUE; + } + + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + /* append the file into index */ + rec = mail_index_record_append(index, st.st_mtime); + if (rec == NULL) + return FALSE; + + update = index->update_begin(index, rec); + + /* set the location */ + index->update_field(update, FIELD_TYPE_LOCATION, fname, + MAILDIR_LOCATION_EXTRA_SPACE); + + /* parse the header and update record's fields */ + failed = !maildir_record_update(index, update, fd, path); + + if (!index->update_end(update) || failed) { + /* failed - delete the record */ + (void)index->expunge(index, rec, 0, FALSE); + return FALSE; + } + + return TRUE; +} + +int maildir_index_append_file(MailIndex *index, const char *dir, + const char *fname) +{ + const char *path; + int fd, ret; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + i_assert(dir != NULL); + i_assert(fname != NULL); + + path = t_strconcat(dir, "/", fname, NULL); + fd = open(path, O_RDONLY); + if (fd == -1) { + /* open() failed - treat it as error unless the error was + "file doesn't exist" in which case someone just managed + to delete it before we saw it */ + if (errno == EEXIST) + return TRUE; + + index_set_error(index, "Error opening mail file %s: %m", path); + return FALSE; + } + + ret = maildir_index_append_fd(index, fd, path, fname); + (void)close(fd); + return ret; +} + +int maildir_index_build_dir(MailIndex *index, const char *source_dir, + const char *dest_dir) +{ + DIR *dirp; + const char *final_dir; + struct dirent *d; + struct stat st; + char sourcepath[1024], destpath[1024]; + int failed; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + i_assert(source_dir != NULL); + + dirp = opendir(source_dir); + if (dirp == NULL) { + index_set_error(index, "Couldn't build index from %s: %m", + source_dir); + return FALSE; + } + + final_dir = dest_dir != NULL ? dest_dir : source_dir; + + failed = FALSE; + while (!failed && (d = readdir(dirp)) != NULL) { + if (d->d_name[0] == '.') + continue; + + if (dest_dir != NULL) { + /* move the file into dest_dir - abort everything if it + already exists, as that should never happen */ + i_snprintf(sourcepath, sizeof(sourcepath), "%s/%s", + source_dir, d->d_name); + i_snprintf(destpath, sizeof(destpath), "%s/%s", + dest_dir, d->d_name); + if (stat(destpath, &st) == 0) { + index_set_error(index, "Can't move mail %s to " + "%s: file already exists", + sourcepath, destpath); + failed = TRUE; + break; + } + + /* race condition here - ignore it as the chance of it + happening is pretty much zero */ + + if (rename(sourcepath, destpath) == -1) { + index_set_error(index, "maildir build: " + "rename(%s, %s) failed: %m", + sourcepath, destpath); + failed = TRUE; + break; + } + } + + t_push(); + failed = !maildir_index_append_file(index, final_dir, + d->d_name); + t_pop(); + } + + (void)closedir(dirp); + return !failed; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-index.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,182 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "maildir-index.h" +#include "mail-index-util.h" + +#include <stdio.h> + +static MailIndex maildir_index; + +const char *maildir_filename_set_flags(const char *fname, MailFlags flags) +{ + const char *info, *oldflags; + char *flags_buf, *p; + int i, nextflag; + + /* remove the old :info from file name, and get the old flags */ + info = strrchr(fname, ':'); + if (info != NULL && strrchr(fname, '/') > info) + info = NULL; + + oldflags = ""; + if (info != NULL) { + fname = t_strndup(fname, (unsigned int) (info-fname)); + if (info[1] == '2' && info[2] == ',') + oldflags = info+3; + } + + /* insert the new flags between old flags. flags must be sorted by + their ASCII code. unknown flags are kept. */ + flags_buf = t_malloc(MAIL_FLAGS_COUNT+strlen(oldflags)+1); + p = flags_buf; + + for (;;) { + /* skip all known flags */ + while (*oldflags == 'D' || *oldflags == 'F' || + *oldflags == 'R' || *oldflags == 'S' || + *oldflags == 'T' || + (*oldflags >= 'a' && *oldflags <= 'z')) + oldflags++; + + nextflag = *oldflags == '\0' ? 256 : + (unsigned char) *oldflags; + + if ((flags & MAIL_DRAFT) && nextflag > 'D') { + *p++ = 'D'; + flags &= ~MAIL_DRAFT; + } + if ((flags & MAIL_FLAGGED) && nextflag > 'F') { + *p++ = 'F'; + flags &= ~MAIL_FLAGGED; + } + if ((flags & MAIL_ANSWERED) && nextflag > 'R') { + *p++ = 'R'; + flags &= ~MAIL_ANSWERED; + } + if ((flags & MAIL_SEEN) && nextflag > 'S') { + *p++ = 'S'; + flags &= ~MAIL_SEEN; + } + if ((flags & MAIL_DELETED) && nextflag > 'T') { + *p++ = 'T'; + flags &= ~MAIL_DELETED; + } + + if ((flags & MAIL_CUSTOM_FLAGS_MASK) && nextflag > 'a') { + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { + if (flags & (1 << (i + MAIL_CUSTOM_FLAG_1_BIT))) + *p++ = 'a' + i; + } + flags &= ~MAIL_CUSTOM_FLAGS_MASK; + } + + if (*oldflags == '\0') + break; + + *p++ = *oldflags++; + } + + *p = '\0'; + + return t_strconcat(fname, ":2,", flags_buf, NULL); +} + +MailIndex *maildir_index_alloc(const char *dir) +{ + MailIndex *index; + int len; + + i_assert(dir != NULL); + + index = i_new(MailIndex, 1); + memcpy(index, &maildir_index, sizeof(MailIndex)); + + index->fd = -1; + index->dir = i_strdup(dir); + + len = strlen(index->dir); + if (index->dir[len-1] == '/') + index->dir[len-1] = '\0'; + + return (MailIndex *) index; +} + +static void maildir_index_free(MailIndex *index) +{ + mail_index_close(index); + i_free(index->dir); + i_free(index); +} + +static int maildir_index_update_flags(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, MailFlags flags, + int external_change) +{ + MailIndexUpdate *update; + const char *old_fname, *new_fname; + const char *old_path, *new_path; + + if (!mail_index_update_flags(index, rec, seq, flags, external_change)) + return FALSE; + + /* we need to update the flags in the file name */ + old_fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION); + if (old_fname == NULL) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "Corrupted index file %s: " + "Missing location field for record %u", + index->filepath, rec->uid); + return FALSE; + } + + new_fname = maildir_filename_set_flags(old_fname, flags); + + if (strcmp(old_fname, new_fname) != 0) { + old_path = t_strconcat(index->dir, "/cur/", old_fname, NULL); + new_path = t_strconcat(index->dir, "/cur/", new_fname, NULL); + + /* minor problem: new_path is overwritten if it exists.. */ + if (rename(old_path, new_path) == -1) { + index_set_error(index, "maildir flags update: " + "rename(%s, %s) failed: %m", + old_path, new_path); + return FALSE; + } + + /* update the filename in index */ + update = index->update_begin(index, rec); + index->update_field(update, FIELD_TYPE_LOCATION, new_fname, 0); + + if (!index->update_end(update)) + return FALSE; + } + + return TRUE; +} + +static MailIndex maildir_index = { + mail_index_open, + mail_index_open_or_create, + maildir_index_free, + mail_index_set_lock, + mail_index_try_lock, + maildir_index_rebuild, + mail_index_fsck, + maildir_index_sync, + mail_index_get_header, + mail_index_lookup, + mail_index_next, + mail_index_lookup_uid_range, + mail_index_lookup_field, + mail_index_get_sequence, + maildir_open_mail, + mail_index_expunge, + maildir_index_update_flags, + mail_index_append, + mail_index_update_begin, + mail_index_update_end, + mail_index_update_field, + mail_index_get_last_error, + mail_index_is_inconsistency_error +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-index.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,27 @@ +#ifndef __MAILDIR_INDEX_H +#define __MAILDIR_INDEX_H + +#include "mail-index.h" + +/* ":2,DFRST" - leave the 2 extra for other clients' additions */ +#define MAILDIR_LOCATION_EXTRA_SPACE 10 + +MailIndex *maildir_index_alloc(const char *dir); + +const char *maildir_filename_set_flags(const char *fname, MailFlags flags); + +int maildir_index_rebuild(MailIndex *index); +int maildir_index_sync(MailIndex *index); + +int maildir_index_append_file(MailIndex *index, const char *dir, + const char *fname); +int maildir_index_build_dir(MailIndex *index, const char *source_dir, + const char *dest_dir); + +int maildir_open_mail(MailIndex *index, MailIndexRecord *rec, + off_t *offset, size_t *size); + +int maildir_record_update(MailIndex *index, MailIndexUpdate *update, + int fd, const char *path); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-open.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,45 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "maildir-index.h" +#include "mail-index-util.h" + +#include <unistd.h> +#include <fcntl.h> + +int maildir_open_mail(MailIndex *index, MailIndexRecord *rec, + off_t *offset, size_t *size) +{ + off_t pos; + const char *fname, *path; + int fd; + + fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION); + if (fname == NULL) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "Corrupted index file %s: " + "Missing location field for record %u", + index->filepath, rec->uid); + return -1; + } + + path = t_strconcat(index->dir, "/cur/", fname, NULL); + + fd = open(path, O_RDONLY); + if (fd == -1) { + index_set_error(index, "Error opening mail file %s: %m", path); + return -1; + } + + pos = lseek(fd, 0, SEEK_END); + if (pos == (off_t)-1 || lseek(fd, 0, SEEK_SET) == (off_t)-1) { + index_set_error(index, "lseek() failed with mail file %s: %m", + path); + (void)close(fd); + return -1; + } + + *offset = 0; + *size = (size_t) pos; + return fd; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-rebuild.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,66 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "maildir-index.h" +#include "mail-index-data.h" +#include "mail-index-util.h" +#include "mail-hash.h" + +#include <unistd.h> +#include <sys/stat.h> +#include <sys/mman.h> + +int maildir_index_rebuild(MailIndex *index) +{ + struct stat st; + const char *cur_dir, *new_dir; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + if (!mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + /* reset the header */ + mail_index_init_header(index->header); + + /* update indexid */ + index->indexid = index->header->indexid; + + if (msync(index->mmap_base, sizeof(MailIndexHeader), MS_SYNC) == -1) + return FALSE; + + /* truncate the file first, so it won't contain + any invalid data even if we crash */ + if (ftruncate(index->fd, sizeof(MailIndexHeader)) == -1) { + index_set_error(index, "Can't truncate index file %s: %m", + index->filepath); + return FALSE; + } + + /* reset data file */ + if (!mail_index_data_reset(index->data)) + return FALSE; + + /* rebuild cur/ directory */ + cur_dir = t_strconcat(index->dir, "/cur", NULL); + if (!maildir_index_build_dir(index, cur_dir, NULL)) + return FALSE; + + /* also see if there's new mail */ + new_dir = t_strconcat(index->dir, "/new", NULL); + if (!maildir_index_build_dir(index, new_dir, cur_dir)) + return FALSE; + + /* update sync stamp */ + if (stat(cur_dir, &st) == -1) { + index_set_error(index, "fstat() failed for maildir %s: %m", + cur_dir); + return FALSE; + } + + index->file_sync_stamp = st.st_mtime; + + /* rebuild is complete - remove the flag */ + index->header->flags &= ~(MAIL_INDEX_FLAG_REBUILD|MAIL_INDEX_FLAG_FSCK); + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-sync.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,366 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "ioloop.h" +#include "hash.h" +#include "maildir-index.h" +#include "mail-index-util.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <utime.h> +#include <sys/stat.h> + +static int maildir_index_sync_file(MailIndex *index, + MailIndexRecord *rec, unsigned int seq, + const char *fname, const char *path, + int fname_changed, int file_changed) +{ + MailIndexUpdate *update; + MailFlags flags; + const char *info; + int fd, failed; + + i_assert(fname != NULL); + i_assert(path != NULL); + + flags = rec->msg_flags; + + info = strchr(fname, ':'); + if (info != NULL && info[1] == '2' && info[2] == ',') { + /* update flags */ + flags = 0; + for (info += 3; *info != '\0'; info++) { + switch (*info) { + case 'R': /* replied */ + flags |= MAIL_ANSWERED; + break; + case 'S': /* seen */ + flags |= MAIL_SEEN; + break; + case 'T': /* trashed */ + flags |= MAIL_DELETED; + break; + case 'D': /* draft */ + flags |= MAIL_DRAFT; + break; + case 'F': /* flagged */ + flags |= MAIL_FLAGGED; + break; + default: + if (*info >= 'a' && *info <= 'z') { + /* custom flag */ + flags |= 1 << (MAIL_CUSTOM_FLAG_1_BIT + + *info-'a'); + break; + } + + /* unknown flag - ignore */ + break; + } + } + } + + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + failed = FALSE; + update = index->update_begin(index, rec); + + if (fname_changed) + index->update_field(update, FIELD_TYPE_LOCATION, fname, 0); + if (file_changed) { + /* file itself changed - reload the header */ + fd = open(path, O_RDONLY); + if (fd == -1) { + index_set_error(index, "Error opening file %s: %m", + path); + failed = TRUE; + } else { + if (!maildir_record_update(index, update, fd, path)) + failed = TRUE; + (void)close(fd); + } + } + + if (!index->update_end(update)) + failed = TRUE; + + /* update flags after filename has been updated, so it can be + compared correctly */ + if (!failed && flags != rec->msg_flags) { + if (!index->update_flags(index, rec, seq, flags, TRUE)) + failed = TRUE; + } + + return !failed; +} + +static int maildir_index_sync_files(MailIndex *index, const char *dir, + HashTable *files, int check_content_changes) +{ + MailIndexRecord *rec; + struct stat st; + const char *fname, *value; + char str[1024], *p; + unsigned int seq; + int fname_changed, file_changed; + + i_assert(dir != NULL); + + rec = index->lookup(index, 1); + for (seq = 1; rec != NULL; rec = index->next(index, rec), seq++) { + fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION); + if (fname == NULL) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "Corrupted index file %s: " + "Missing location field for record %u", + index->filepath, rec->uid); + return FALSE; + } + + /* get the filename without the ":flags" part */ + strncpy(str, fname, sizeof(str)-1); str[sizeof(str)-1] = '\0'; + p = strchr(str, ':'); + if (p != NULL) *p = '\0'; + + value = hash_lookup(files, str); + hash_remove(files, str); + + if (value == NULL) { + /* mail is expunged */ + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + if (!index->expunge(index, rec, seq, TRUE)) + return FALSE; + continue; + } + + /* file still exists */ + i_snprintf(str, sizeof(str), "%s/%s", dir, value); + + if (!check_content_changes) + file_changed = FALSE; + else { + if (stat(str, &st) == -1) { + index_set_error(index, "stat() failed with " + "file %s: %m", str); + return FALSE; + } + + file_changed = rec->body_size + rec->header_size != + (size_t) st.st_size; + } + + /* changed - update */ + fname_changed = strcmp(value, fname) != 0; + if (fname_changed || file_changed) { + if (!maildir_index_sync_file(index, rec, seq, value, + str, fname_changed, + file_changed)) + return FALSE; + } + } + + return TRUE; +} + +typedef struct { + MailIndex *index; + const char *dir; + int failed; +} HashAppendData; + +static void maildir_index_hash_append_file(void *key __attr_unused__, + void *value, void *user_data) +{ + HashAppendData *data = user_data; + + if (!maildir_index_append_file(data->index, data->dir, value)) { + data->failed = TRUE; + hash_foreach_stop(); + } +} + +static int maildir_index_append_files(MailIndex *index, const char *dir, + HashTable *files) +{ + HashAppendData data; + + data.failed = FALSE; + data.index = index; + data.dir = dir; + hash_foreach(files, maildir_index_hash_append_file, &data); + + return !data.failed; +} + +static int maildir_index_sync_dir(MailIndex *index, const char *dir) +{ + Pool pool; + HashTable *files; + DIR *dirp; + struct dirent *d; + const char *key, *value, *p; + unsigned int count; + int failed, check_content_changes; + + i_assert(dir != NULL); + + /* get exclusive lock always, this way the index file's timestamp + is updated even if there's no changes, which is useful to make + sure the cur/ directory isn't scanned all the time when it's + timestamp has changed but hasn't had any other changes. */ + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + if (index->header->messages_count >= INT_MAX/32) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "Corrupted index file %s: Header " + "says %u messages", index->filepath, + index->header->messages_count); + return FALSE; + } + + /* we need to find out the new and the deleted files. do this by + first building a hash of what files really exist, then go through + the index and after updated/removed the index, remove the file + from hash, so finally the hash should contain only the new + files which will be added then. */ + dirp = opendir(dir); + if (dirp == NULL) { + index_set_error(index, "Couldn't sync index in dir %s: %m", + dir); + return FALSE; + } + + count = index->header->messages_count + 16; + pool = pool_create("Maildir sync", nearest_power(count*30), FALSE); + files = hash_create(pool, index->header->messages_count*2, str_hash, + (HashCompareFunc) strcmp); + + while ((d = readdir(dirp)) != NULL) { + if (d->d_name[0] == '.') + continue; + + /* hash key is the file name without the ":flags" part */ + p = strrchr(d->d_name, ':'); + if (p == d->d_name) + continue; + + value = p_strdup(pool, d->d_name); + key = p == NULL ? value : + p_strndup(pool, d->d_name, + (unsigned int) (p - d->d_name)); + hash_insert(files, key, value); + } + (void)closedir(dirp); + + /* Do we want to check changes in file contents? This slows down + things as we need to do extra stat() for all files. */ + check_content_changes = getenv("CHECK_CONTENT_CHANGES") != NULL; + + /* now walk through the index syncing and expunging existing mails */ + failed = !maildir_index_sync_files(index, dir, files, + check_content_changes); + + if (!failed) { + /* then add the new mails */ + failed = !maildir_index_append_files(index, dir, files); + } + + hash_destroy(files); + pool_unref(pool); + return !failed; +} + +int maildir_index_sync(MailIndex *index) +{ + struct stat sti, std; + struct utimbuf ut; + const char *cur_dir, *new_dir; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + if (fstat(index->fd, &sti) == -1) { + index_set_error(index, "fstat() failed with index file %s: %m", + index->filepath); + return FALSE; + } + + /* cur/ and new/ directories can have new mail - sync the cur/ first + so it'll be a bit bit faster since we haven't yet added the new + mail. */ + cur_dir = t_strconcat(index->dir, "/cur", NULL); + if (stat(cur_dir, &std) == -1) { + index_set_error(index, "fstat() failed for maildir %s: %m", + cur_dir); + return FALSE; + } + + if (std.st_mtime != sti.st_mtime) { + if (!maildir_index_sync_dir(index, cur_dir)) + return FALSE; + } + + /* move mail from new/ to cur/ */ + new_dir = t_strconcat(index->dir, "/new", NULL); + if (stat(new_dir, &std) == -1) { + index_set_error(index, "fstat() failed for maildir " + "%s: %m", new_dir); + return FALSE; + } + + if (std.st_mtime != sti.st_mtime) { + if (!maildir_index_build_dir(index, new_dir, cur_dir)) + return FALSE; + + /* set cur/ and new/ directory's timestamp into past to + make sure if someone adds new mail it the new/ dir's + timestamp isn't set to same as cur/ directory's. */ + ut.actime = ut.modtime = ioloop_time-60; + if (utime(cur_dir, &ut) == -1) { + index_set_error(index, "utime() failed for %s: %m", + cur_dir); + return FALSE; + } + if (utime(new_dir, &ut) == -1) { + index_set_error(index, "utime() failed for %s: %m", + new_dir); + return FALSE; + } + + /* it's possible that new mail came in just after we + scanned the directory. scan the directory again, this will + update the directory's timestamps so at next sync we'll + always check the new/ dir once more, but at least we can be + sure that no mail got lost. */ + if (!maildir_index_build_dir(index, new_dir, cur_dir)) + return FALSE; + } + + /* update sync stamp */ + if (stat(cur_dir, &std) == -1) { + index_set_error(index, "fstat() failed for maildir %s: %m", + cur_dir); + return FALSE; + } + index->file_sync_stamp = std.st_mtime; + + if (index->lock_type == MAIL_LOCK_UNLOCK) { + /* no changes, we need to update index's timestamp + ourself to get it changed */ + ut.actime = ioloop_time; + ut.modtime = index->file_sync_stamp; + if (utime(index->filepath, &ut) == -1) { + index_set_error(index, "utime() failed for %s: %m", + index->filepath); + return FALSE; + } + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/maildir/maildir-update.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,34 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mmap-util.h" +#include "maildir-index.h" +#include "mail-index-util.h" + +int maildir_record_update(MailIndex *index, MailIndexUpdate *update, + int fd, const char *path) +{ + void *mmap_base; + size_t mmap_length; + + i_assert(path != NULL); + + /* we need only the header which probably fits into one page, + so don't use MADV_SEQUENTIAL which would just read more than + is needed. */ + mmap_base = mmap_ro_file(fd, &mmap_length); + if (mmap_base == MAP_FAILED) { + index_set_error(index, "update: mmap() failed with file %s: %m", + path); + return FALSE; + } + + if (mmap_base == NULL) { + /* empty file */ + return TRUE; + } + + mail_index_update_headers(update, mmap_base, mmap_length, NULL, NULL); + (void)munmap(mmap_base, mmap_length); + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,20 @@ +noinst_LIBRARIES = libstorage_index_mbox.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index + +libstorage_index_mbox_a_SOURCES = \ + mbox-append.c \ + mbox-fsck.c \ + mbox-index.c \ + mbox-lock.c \ + mbox-open.c \ + mbox-rebuild.c \ + mbox-sync.c + +noinst_HEADERS = \ + mbox-index.h \ + mbox-lock.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-append.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,269 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mmap-util.h" +#include "ioloop.h" +#include "mbox-index.h" +#include "mail-index-util.h" + +#include <time.h> +#include <ctype.h> +#include <unistd.h> +#include <sys/mman.h> + +static const char *months[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static MailIndexRecord * +mail_index_record_append(MailIndex *index, time_t internal_date, + size_t full_virtual_size) +{ + MailIndexRecord trec, *rec; + + memset(&trec, 0, sizeof(MailIndexRecord)); + trec.internal_date = internal_date; + trec.full_virtual_size = full_virtual_size; + + rec = &trec; + if (!index->append(index, &rec)) + return NULL; + + return rec; +} + +static time_t from_line_parse_date(const char *msg, size_t size) +{ + const char *msg_end; + struct tm tm; + int i; + + /* From <sender> <date> <moreinfo> */ + if (strncmp(msg, "From ", 5) != 0) + return 0; + + msg_end = msg + size; + + /* skip sender */ + msg += 5; + while (*msg != ' ' && msg < msg_end) msg++; + while (*msg == ' ' && msg < msg_end) msg++; + + /* next 24 chars are the date in asctime() format, + eg. "Thu Nov 29 22:33:52 2001" */ + if (msg+24 > msg_end) + return 0; + + memset(&tm, 0, sizeof(tm)); + + /* skip weekday */ + msg += 4; + + /* month */ + for (i = 0; i < 12; i++) { + if (strncasecmp(months[i], msg, 3) == 0) { + tm.tm_mon = i; + break; + } + } + + if (i == 12 || msg[3] != ' ') + return 0; + msg += 4; + + /* day */ + if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ' ') + return 0; + tm.tm_mday = (msg[0]-'0') * 10 + (msg[1]-'0'); + msg += 3; + + /* hour */ + if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ':') + return 0; + tm.tm_hour = (msg[0]-'0') * 10 + (msg[1]-'0'); + msg += 3; + + /* minute */ + if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ':') + return 0; + tm.tm_min = (msg[0]-'0') * 10 + (msg[1]-'0'); + msg += 3; + + /* second */ + if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || msg[2] != ' ') + return 0; + tm.tm_sec = (msg[0]-'0') * 10 + (msg[1]-'0'); + msg += 3; + + /* year */ + if (!i_isdigit(msg[0]) || !i_isdigit(msg[1]) || + !i_isdigit(msg[2]) || !i_isdigit(msg[3])) + return 0; + tm.tm_year = (msg[0]-'0') * 1000 + (msg[1]-'0') * 100 + + (msg[2]-'0') * 10 + (msg[3]-'0') - 1900; + + tm.tm_isdst = -1; + return mktime(&tm); +} + +static void header_func(MessagePart *part __attr_unused__, + const char *name, unsigned int name_len, + const char *value, unsigned int value_len, + void *user_data) +{ + MailIndexRecord *rec = user_data; + + rec->msg_flags |= mbox_header_get_flags(name, name_len, + value, value_len); +} + +static int mbox_index_append_data(MailIndex *index, const char *msg, + off_t offset, size_t physical_size, + size_t virtual_size) +{ + MailIndexRecord *rec; + MailIndexUpdate *update; + time_t internal_date; + char location[MAX_INT_STRLEN]; + unsigned int i; + + internal_date = from_line_parse_date(msg, physical_size); + if (internal_date <= 0) + internal_date = ioloop_time; + + /* skip the From-line */ + for (i = 0; i < physical_size; i++) { + if (msg[i] == '\n') { + i++; + break; + } + } + + if (i == physical_size) + return FALSE; + + msg += i; + offset += i; + physical_size -= i; + virtual_size -= i; + if (i > 0 && msg[i-1] != '\r') + virtual_size--; + + rec = mail_index_record_append(index, internal_date, virtual_size); + if (rec == NULL) + return FALSE; + + update = index->update_begin(index, rec); + + /* location = offset to beginning of message */ + i_snprintf(location, sizeof(location), "%lu", (unsigned long) offset); + index->update_field(update, FIELD_TYPE_LOCATION, location, 0); + + /* parse the header and add cache wanted fields */ + mail_index_update_headers(update, msg, physical_size, header_func, rec); + + if (!index->update_end(update)) { + /* failed - delete the record */ + (void)index->expunge(index, rec, 0, FALSE); + return FALSE; + } + + return TRUE; +} + +int mbox_index_append_mmaped(MailIndex *index, const char *data, + size_t data_size, off_t start_offset) +{ + const char *data_start, *data_end, *start, *cr; + size_t size, vsize; + off_t pos; + int missing_cr_count; + + /* we should start with "From ". if we don't, something's messed up + and we should check the whole file instead. */ + if (strncmp(data, "From ", 5) != 0) { + index->set_flags |= MAIL_INDEX_FLAG_FSCK; + return FALSE; + } + + /* each message ends at "\nFrom ". first get the size of the message, + then parse it. calculate the missing CR count as well. */ + start = data; cr = NULL; missing_cr_count = 0; + + data_start = data; + data_end = data + data_size; + for (; data != data_end; data++) { + if (*data == '\r') + cr = data; + else if (*data == '\n') { + if (cr != data-1) + missing_cr_count++; + + if (data+6 < data_end && data[1] == 'F' && + data[2] == 'r' && data[3] == 'o' && + data[4] == 'm' && data[5] == ' ') { + /* end of message */ + pos = (off_t) (start - data_start) + + start_offset; + size = (size_t) (data - start) + 1; + vsize = size + missing_cr_count; + if (!mbox_index_append_data(index, start, pos, + size, vsize)) + return FALSE; + + missing_cr_count = 0; + start = data+1; + } + } + } + + /* last message */ + pos = (off_t) (start - data_start); + size = (size_t) (data - start); + vsize = size + missing_cr_count; + return mbox_index_append_data(index, start, pos, size, vsize); +} + +int mbox_index_append(MailIndex *index, int fd, const char *path) +{ + void *mmap_base; + size_t mmap_length; + off_t pos, end_pos; + int ret; + + /* get our current position */ + pos = lseek(fd, 0, SEEK_CUR); + + /* get the size of the file */ + end_pos = lseek(fd, 0, SEEK_END); + + if (pos == (off_t)-1 || end_pos == (off_t)-1) { + index_set_error(index, "lseek() failed with mbox file %s: %m", + path); + return FALSE; + } + + if (pos == end_pos) { + /* no new data */ + return TRUE; + } + + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + /* mmap() the file */ + mmap_length = end_pos-pos; + mmap_base = mmap(NULL, mmap_length, PROT_READ, MAP_SHARED, fd, pos); + if (mmap_base == MAP_FAILED) { + index_set_error(index, "mmap() failed with mbox file %s: %m", + path); + return FALSE; + } + + (void)madvise(mmap_base, mmap_length, MADV_SEQUENTIAL); + + ret = mbox_index_append_mmaped(index, mmap_base, mmap_length, pos); + (void)munmap(mmap_base, mmap_length); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-fsck.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,184 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mmap-util.h" +#include "message-parser.h" +#include "mbox-index.h" +#include "mbox-lock.h" +#include "mail-index-util.h" + +#include <fcntl.h> + +typedef struct { + const char *msgid; + MailFlags flags; +} HeaderData; + +static void header_func(MessagePart *part __attr_unused__, + const char *name, unsigned int name_len, + const char *value, unsigned int value_len, + void *user_data) +{ + HeaderData *data = user_data; + + if (name_len != 10 || strncasecmp(name, "Message-ID", 10) != 0) + return; + + data->msgid = t_strndup(value, value_len); + data->flags |= mbox_header_get_flags(name, name_len, value, value_len); +} + +static MailIndexRecord * +match_next_record(MailIndex *index, MailIndexRecord *rec, unsigned int *seq, + const char **data, const char *data_end) +{ + MessageSize hdr_size; + HeaderData hdr_data; + const char *rec_msgid, *data_next; + + /* skip the From-line */ + while (*data != data_end && **data != '\n') + (*data)++; + (*data)++; + + if (*data >= data_end) { + /* end of data */ + (void)index->expunge(index, rec, *seq, TRUE); + return rec; + } + + /* find the Message-ID from the header */ + memset(&hdr_data, 0, sizeof(hdr_data)); + message_parse_header(NULL, *data, (size_t) (data_end-*data), &hdr_size, + header_func, &hdr_data); + + do { + do { + /* message-id must match (or be non-existant) */ + rec_msgid = index->lookup_field(index, rec, + FIELD_TYPE_MESSAGEID); + if (hdr_data.msgid == NULL && rec_msgid != NULL) + break; + if (hdr_data.msgid != NULL && + (rec_msgid == NULL || + strcmp(hdr_data.msgid, rec_msgid) != 0)) + break; + + /* don't bother parsing the whole body, just make + sure it ends properly */ + data_next = *data + rec->header_size + rec->body_size; + if (data_next == data_end) { + /* last message */ + } else if (data_next+5 >= data_end || + strncmp(data_next-1, "\nFrom ", 6) != 0) + break; + + /* valid message, update flags */ + if ((rec->msg_flags & hdr_data.flags) != hdr_data.flags) + rec->msg_flags |= hdr_data.flags; + + *data = data_next; + return rec; + } while (0); + + /* try next message */ + (*seq)++; + (void)index->expunge(index, rec, *seq, TRUE); + rec = index->next(index, rec); + } while (rec != NULL); + + return NULL; +} + +static int mbox_index_fsck_mmap(MailIndex *index, const char *data, size_t size) +{ + MailIndexRecord *rec; + const char *data_end; + unsigned int seq; + + if (!index->set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + /* first make sure we start with a "From " line. */ + if (size <= 5 || strncmp(data, "From ", 5) != 0) { + index_set_error(index, "File isn't in mbox format: %s", + index->mbox_path); + return FALSE; + } + + /* we'll go through the mailbox and index in order matching the + messages by their size and Message-ID. old mails aren't remembered, + so we handle well only the cases when mail has been deleted. if + mails have been reordered (eg. sorted by someone) most of the mails + will show up as being new. if we really wanted to support that well, + we could save the message-ids into hash but I don't know if it's + worth the trouble. */ + + seq = 1; + rec = index->lookup(index, 1); + + data_end = data + size; + while (rec != NULL) { + rec = match_next_record(index, rec, &seq, &data, data_end); + if (rec == NULL) + break; + + seq++; + rec = index->next(index, rec); + } + + if (data == data_end) + return TRUE; + else { + return mbox_index_append_mmaped(index, data, + (size_t) (data_end-data), 0); + } +} + +int mbox_index_fsck(MailIndex *index) +{ + void *mmap_base; + size_t mmap_length; + int fd, failed; + + /* open the mbox file. we don't really need to open it read-write, + but fcntl() locking requires it. */ + fd = open(index->mbox_path, O_RDWR); + if (fd == -1) { + index_set_error(index, "Can't open mbox file %s: %m", + index->mbox_path); + return FALSE; + } + + mmap_base = mmap_ro_file(fd, &mmap_length); + if (mmap_base == MAP_FAILED) { + index_set_error(index, "mmap() failed with mbox file %s: %m", + index->mbox_path); + return FALSE; + } + (void)madvise(mmap_base, mmap_length, MADV_SEQUENTIAL); + + if (mmap_base == NULL) { + /* file is empty */ + (void)close(fd); + return TRUE; + } + + /* lock the mailbox so we can be sure no-one interrupts us. + we are trying to repair our index after all. */ + if (!mbox_lock(index, index->mbox_path, fd)) + failed = TRUE; + else { + failed = !mbox_index_fsck_mmap(index, mmap_base, mmap_length); + (void)mbox_unlock(index, index->mbox_path, fd); + } + + (void)munmap(mmap_base, mmap_length); + (void)close(fd); + + if (failed) + return FALSE; + + /* check the header */ + return mail_index_fsck(index); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-index.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,91 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mbox-index.h" +#include "mail-index-util.h" + +static MailIndex mbox_index; + +MailFlags mbox_header_get_flags(const char *name, unsigned int name_len, + const char *value, unsigned int value_len) +{ + MailFlags flags; + unsigned int i; + + flags = 0; + if ((name_len == 6 && strncasecmp(name, "Status", 6) == 0) || + (name_len == 8 && strncasecmp(name, "X-Status", 8) == 0)) { + for (i = 0; i < value_len; i++) { + switch (value[i]) { + case 'A': + flags |= MAIL_ANSWERED; + break; + case 'F': + flags |= MAIL_FLAGGED; + break; + case 'R': + flags |= MAIL_SEEN; + break; + case 'D': + flags |= MAIL_DELETED; + break; + } + } + } + + return flags; +} + +MailIndex *mbox_index_alloc(const char *dir, const char *mbox_path) +{ + MailIndex *index; + int len; + + i_assert(dir != NULL); + + index = i_new(MailIndex, 1); + memcpy(index, &mbox_index, sizeof(MailIndex)); + + index->fd = -1; + index->dir = i_strdup(dir); + + len = strlen(index->dir); + if (index->dir[len-1] == '/') + index->dir[len-1] = '\0'; + + index->mbox_path = i_strdup(mbox_path); + return (MailIndex *) index; +} + +static void mbox_index_free(MailIndex *index) +{ + mail_index_close(index); + i_free(index->dir); + i_free(index); +} + +static MailIndex mbox_index = { + mail_index_open, + mail_index_open_or_create, + mbox_index_free, + mail_index_set_lock, + mail_index_try_lock, + mbox_index_rebuild, + mbox_index_fsck, + mbox_index_sync, + mail_index_get_header, + mail_index_lookup, + mail_index_next, + mail_index_lookup_uid_range, + mail_index_lookup_field, + mail_index_get_sequence, + mbox_open_mail, + mail_index_expunge, + mail_index_update_flags, + mail_index_append, + mail_index_update_begin, + mail_index_update_end, + mail_index_update_field, + mail_index_get_last_error, + mail_index_is_inconsistency_error +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-index.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,21 @@ +#ifndef __MBOX_INDEX_H +#define __MBOX_INDEX_H + +#include "mail-index.h" + +MailIndex *mbox_index_alloc(const char *dir, const char *mbox_path); + +MailFlags mbox_header_get_flags(const char *name, unsigned int name_len, + const char *value, unsigned int value_len); + +int mbox_index_rebuild(MailIndex *index); +int mbox_index_sync(MailIndex *index); +int mbox_index_fsck(MailIndex *index); +int mbox_open_mail(MailIndex *index, MailIndexRecord *rec, + off_t *offset, size_t *size); + +int mbox_index_append(MailIndex *index, int fd, const char *path); +int mbox_index_append_mmaped(MailIndex *index, const char *data, + size_t data_size, off_t start_offset); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-lock.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,160 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mbox-index.h" +#include "mbox-lock.h" +#include "mail-index-util.h" + +#include <time.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#ifdef HAVE_FLOCK +# include <sys/file.h> +#endif + +#ifdef HAVE_FLOCK +# define USE_FLOCK +#endif + +/* 0.1 .. 0.2msec */ +#define LOCK_RANDOM_USLEEP_TIME (100000 + (unsigned int)rand() % 100000) + +/* abort trying to get lock after 30 seconds */ +#define MAX_LOCK_WAIT_SECONDS 30 + +/* remove lock after 10 mins */ +#define STALE_LOCK_TIMEOUT (60*10) + +#ifdef USE_FLOCK + +static int mbox_lock_flock(MailIndex *index, const char *path, int fd, int set) +{ + if (flock(fd, set ? LOCK_EX : LOCK_UN) == -1) { + index_set_error(index, "flock() mbox lock failed for file " + "%s: %m", path); + return FALSE; + } + + return TRUE; +} + +#else + +static int mbox_lock_fcntl(MailIndex *index, const char *path, int fd, int set) +{ + struct flock fl; + + fl.l_type = set ? F_WRLCK : F_UNLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + while (fcntl(fd, F_SETLKW, &fl) == -1) { + if (errno != EINTR) { + index_set_error(index, "fcntl() mbox lock " + "failed for file %s: %m", path); + return FALSE; + } + } + + return TRUE; +} +#endif + +static int mbox_lock_dotlock(MailIndex *index, const char *path, int set) +{ + struct stat st; + time_t now, max_wait_time; + int fd; + + path = t_strconcat(path, ".lock", NULL); + if (!set) { + if (unlink(path) == 0 || errno == ENOENT) + return TRUE; + + index_set_error(index, "unlink() failed for dotlock file " + "%s: %m", path); + return FALSE; + } + + /* don't bother with the temp files as we'd just leave them lying + around. besides, postfix also relies on O_EXCL working so we + might as well. */ + max_wait_time = time(NULL) + MAX_LOCK_WAIT_SECONDS; + do { + now = time(NULL); + + if (stat(path, &st) == 0) { + /* lock exists, see if it's too old */ + if (now > st.st_ctime + STALE_LOCK_TIMEOUT) { + if (unlink(path) == -1 && errno != ENOENT) { + index_set_error(index, "unlink() failed" + " for dotlock file " + "%s: %m", path); + break; + } + } + + usleep(LOCK_RANDOM_USLEEP_TIME); + continue; + } + + fd = open(path, O_WRONLY | O_EXCL | O_CREAT, 0); + if (fd >= 0) { + /* got it */ + (void)close(fd); + return TRUE; + } + + if (errno != EEXIST) { + index_set_error(index, "Can't create dotlock file " + "%s: %m", path); + break; + } + } while (now < max_wait_time); + + return FALSE; +} + +int mbox_lock(MailIndex *index, const char *path, int fd) +{ + i_assert(fd >= 0); + + if (++index->mbox_locks > 1) + return TRUE; + +#ifdef USE_FLOCK + if (!mbox_lock_flock(index, path, fd, TRUE)) + return FALSE; +#else + if (!mbox_lock_fcntl(index, path, fd, TRUE)) + return FALSE; +#endif + if (!mbox_lock_dotlock(index, path, TRUE)) + return FALSE; + + return TRUE; +} + +int mbox_unlock(MailIndex *index, const char *path, int fd) +{ + i_assert(fd >= 0); + + if (--index->mbox_locks > 0) + return TRUE; + +#ifdef USE_FLOCK + if (!mbox_lock_flock(index, path, fd, FALSE)) + return FALSE; +#else + if (!mbox_lock_fcntl(index, path, fd, FALSE)) + return FALSE; +#endif + if (!mbox_lock_dotlock(index, path, FALSE)) + return FALSE; + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-lock.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,7 @@ +#ifndef __MBOX_LOCK_H +#define __MBOX_LOCK_H + +int mbox_lock(MailIndex *index, const char *path, int fd); +int mbox_unlock(MailIndex *index, const char *path, int fd); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-open.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,77 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mbox-index.h" +#include "mail-index-util.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +int mbox_open_mail(MailIndex *index, MailIndexRecord *rec, + off_t *offset, size_t *size) +{ + const char *location; + off_t pos; + char buf[5]; + int fd, ret, ok; + + i_assert(index->lock_type != MAIL_LOCK_UNLOCK); + + location = index->lookup_field(index, rec, FIELD_TYPE_LOCATION); + if (location == NULL) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "Corrupted index file %s: " + "Missing location field for record %u", + index->filepath, rec->uid); + return -1; + } + + /* location = offset */ + *offset = (off_t)strtoul(location, NULL, 10); + *size = rec->header_size + rec->body_size; + + fd = open(index->mbox_path, O_RDONLY); + if (fd == -1) { + index_set_error(index, "Can't open mbox file %s: %m", + index->mbox_path); + return -1; + } + + pos = lseek(fd, *offset, SEEK_SET); + if (pos == (off_t)-1) { + index_set_error(index, "lseek() failed with mbox file %s: %m", + index->mbox_path); + (void)close(fd); + return -1; + } + + ok = FALSE; + if (pos == *offset) { + /* make sure message size is valid */ + pos = *offset + *size; + if (lseek(fd, pos, SEEK_SET) == pos) { + /* and check that we end with either EOF or to + beginning of next message */ + ret = read(fd, buf, 5); + if (ret == 0) + ok = TRUE; /* end of file */ + else if (ret == 5 && strncmp(buf, "From ", 5) == 0) + ok = TRUE; + } + } + + if (ok) { + if (lseek(fd, *offset, SEEK_SET) == *offset) + return fd; + + index_set_error(index, "lseek() failed with mbox file %s: %m", + index->mbox_path); + } else { + /* file has been updated, rescan it */ + index->set_flags |= MAIL_INDEX_FLAG_FSCK; + } + + (void)close(fd); + return -1; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-rebuild.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,85 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mbox-index.h" +#include "mbox-lock.h" +#include "mail-index-data.h" +#include "mail-index-util.h" +#include "mail-hash.h" + +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/mman.h> + +int mbox_index_rebuild(MailIndex *index) +{ + struct stat st; + int fd; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + if (!mail_index_set_lock(index, MAIL_LOCK_EXCLUSIVE)) + return FALSE; + + /* reset the header */ + mail_index_init_header(index->header); + + /* we require Message-ID to be cached */ + index->header->cache_fields |= FIELD_TYPE_MESSAGEID; + + /* update indexid */ + index->indexid = index->header->indexid; + + if (msync(index->mmap_base, sizeof(MailIndexHeader), MS_SYNC) == -1) + return FALSE; + + /* truncate the file first, so it won't contain + any invalid data even if we crash */ + if (ftruncate(index->fd, sizeof(MailIndexHeader)) == -1) { + index_set_error(index, "Can't truncate index file %s: %m", + index->filepath); + return FALSE; + } + + /* reset data file */ + if (!mail_index_data_reset(index->data)) + return FALSE; + + /* open the mbox file. we don't really need to open it read-write, + but fcntl() locking requires it. */ + fd = open(index->mbox_path, O_RDWR); + if (fd == -1) { + index_set_error(index, "Error opening mbox file %s: %m", + index->mbox_path); + return FALSE; + } + + /* lock the mailbox so we can be sure no-one interrupts us. */ + if (!mbox_lock(index, index->mbox_path, fd)) { + (void)close(fd); + return FALSE; + } + + if (!mbox_index_append(index, fd, index->mbox_path)) { + (void)mbox_unlock(index, index->mbox_path, fd); + (void)close(fd); + return FALSE; + } + + (void)mbox_unlock(index, index->mbox_path, fd); + (void)close(fd); + + /* update sync stamp */ + if (stat(index->mbox_path, &st) == -1) { + index_set_error(index, "fstat() failed for mbox file %s: %m", + index->mbox_path); + return FALSE; + } + + index->file_sync_stamp = st.st_mtime; + + /* rebuild is complete - remove the flag */ + index->header->flags &= ~(MAIL_INDEX_FLAG_REBUILD|MAIL_INDEX_FLAG_FSCK); + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mbox/mbox-sync.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,121 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mbox-index.h" +#include "mail-index-util.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +static size_t get_indexed_mbox_size(MailIndex *index) +{ + MailIndexRecord *rec, *prev; + const char *location; + size_t size; + + if (index->lock_type == MAIL_LOCK_UNLOCK) { + if (!mail_index_set_lock(index, MAIL_LOCK_SHARED)) + return 0; + } + + rec = index->header->messages_count == 0 ? NULL : + index->lookup(index, index->header->messages_count); + if (rec == NULL) { + rec = prev = index->lookup(index, 1); + while (rec != NULL) { + prev = rec; + rec = index->next(index, rec); + } + + rec = prev; + } + + size = 0; + if (rec != NULL) { + location = index->lookup_field(index, rec, FIELD_TYPE_LOCATION); + if (location == NULL) { + INDEX_MARK_CORRUPTED(index); + index_set_error(index, "Corrupted index file %s: " + "Missing location field for record %u", + index->filepath, rec->uid); + } else { + size = strtoul(location, NULL, 10) + + rec->header_size + rec->body_size; + } + } + + if (index->lock_type == MAIL_LOCK_SHARED) + (void)mail_index_set_lock(index, MAIL_LOCK_UNLOCK); + return size; +} + +static int mbox_check_new_mail(MailIndex *index) +{ + off_t pos; + int fd, ret; + + fd = open(index->mbox_path, O_RDONLY); + if (fd == -1) { + index_set_error(index, "Can't open mbox file %s: %m", + index->mbox_path); + return FALSE; + } + + pos = lseek(fd, index->mbox_size, SEEK_SET); + if (pos == (off_t)-1) { + index_set_error(index, "lseek() failed with mbox file %s: %m", + index->mbox_path); + (void)close(fd); + return FALSE; + } + + if (pos != index->mbox_size) { + /* someone just shrinked the file? */ + (void)close(fd); + return mbox_index_fsck(index); + } + + /* add the new data */ + ret = mbox_index_append(index, fd, index->mbox_path); + (void)close(fd); + + if (index->set_flags & MAIL_INDEX_FLAG_FSCK) { + /* it wasn't just new mail, reread the mbox */ + index->set_flags &= ~MAIL_INDEX_FLAG_FSCK; + return mbox_index_fsck(index); + } + + return ret; +} + +int mbox_index_sync(MailIndex *index) +{ + struct stat st; + + i_assert(index->lock_type != MAIL_LOCK_SHARED); + + if (stat(index->mbox_path, &st) == -1) { + index_set_error(index, "stat() failed with mbox file %s: %m", + index->mbox_path); + return FALSE; + } + + if (index->file_sync_stamp == st.st_mtime) + return TRUE; + + index->file_sync_stamp = st.st_mtime; + + if (index->mbox_size == 0 && st.st_size != 0) + index->mbox_size = get_indexed_mbox_size(index); + + /* file has been modified. */ + if (index->mbox_size > st.st_size) { + /* file was grown, hopefully just new mail */ + return mbox_check_new_mail(index); + } else { + /* something changed, scan through the whole mbox */ + return mbox_index_fsck(index); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,20 @@ +noinst_LIBRARIES = libmail.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib + +libmail_a_SOURCES = \ + message-parser.c \ + message-content-parser.c \ + message-size.c \ + rfc822-address.c \ + rfc822-date.c \ + rfc822-tokenize.c + +noinst_HEADERS = \ + message-parser.h \ + message-content-parser.h \ + message-size.h \ + rfc822-address.h \ + rfc822-date.h \ + rfc822-tokenize.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/message-content-parser.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,50 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "rfc822-tokenize.h" +#include "message-content-parser.h" + +int message_content_parse_header(const char *value, + ParseContentFunc func, + ParseContentParamFunc param_func, + void *user_data) +{ + const Rfc822Token *tokens; + int i, next, ntokens; + + tokens = rfc822_tokenize(value, &ntokens, NULL, NULL); + if (tokens == NULL) { + /* error */ + return FALSE; + } + + /* first ';' separates the parameters */ + for (i = 0; i < ntokens; i++) { + if (tokens[i].token == ';') + break; + } + + if (func != NULL) + func(tokens, i, user_data); + + if (param_func != NULL) { + /* parse the parameters */ + for (i++; i < ntokens; i = next) { + /* find the next ';' */ + for (next = i+1; next < ntokens; next++) { + if (tokens[next].token == ';') + break; + } + + if (i+2 < next && + tokens[i].token == 'A' && + tokens[i+1].token == '=') { + /* <atom> = <value> */ + param_func(tokens + i, tokens + i + 2, + next - (i+2), user_data); + } + } + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/message-content-parser.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,21 @@ +#ifndef __MESSAGE_CONTENT_PARSER_H +#define __MESSAGE_CONTENT_PARSER_H + +/* functions can safely store data into temporary memory pool, + ie. message_content_parse_header() is guaranteed not to call + t_push()/t_pop() */ + +/* Note that count can be 0 */ +typedef void (*ParseContentFunc)(const Rfc822Token *tokens, int count, + void *user_data); +/* name is always atom, value_count is always > 0 */ +typedef void (*ParseContentParamFunc)(const Rfc822Token *name, + const Rfc822Token *value, + int value_count, void *user_data); + +int message_content_parse_header(const char *value, + ParseContentFunc func, + ParseContentParamFunc param_func, + void *user_data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/message-parser.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,501 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "rfc822-tokenize.h" +#include "message-content-parser.h" +#include "message-parser.h" + +typedef struct _MessageBoundary { + struct _MessageBoundary *next; + + MessagePart *part; + const char *boundary; + unsigned int len; +} MessageBoundary; + +typedef struct { + Pool pool; + MessagePart *part; + + char *last_boundary; + char *last_content_type; + MessageBoundary *boundaries; + + MessageHeaderFunc func; + void *user_data; +} MessageParseData; + +static MessagePart *message_parse_part(const char *msg, size_t size, + MessageParseData *parse_data); +static MessagePart *message_parse_body(const char *msg, size_t size, + MessageBoundary *boundaries, + MessageSize *body_size); +static MessagePart *message_skip_boundary(const char *msg, size_t size, + MessageBoundary *boundaries, + MessageSize *boundary_size); + +static void message_size_add_part(MessageSize *dest, MessagePart *part) +{ + dest->physical_size += + part->header_size.physical_size + + part->body_size.physical_size; + dest->virtual_size += + part->header_size.virtual_size + + part->body_size.virtual_size; + dest->lines += part->header_size.lines + part->body_size.lines; +} + +static MessagePart *message_part_append(Pool pool, MessagePart *parent) +{ + MessagePart *part, **list; + + part = p_new(pool, MessagePart, 1); + part->parent = parent; + + list = &part->parent->children; + while (*list != NULL) + list = &(*list)->next; + + *list = part; + return part; +} + +static void parse_content_type(const Rfc822Token *tokens, int count, + void *user_data) +{ + MessageParseData *parse_data = user_data; + const char *str; + + if (tokens[0].token != 'A') + return; + + if (parse_data->last_content_type != NULL) + return; + + str = rfc822_tokens_get_value(tokens, count, FALSE); + parse_data->last_content_type = p_strdup(parse_data->pool, str); + + if (strcasecmp(str, "message/rfc822") == 0) + parse_data->part->message_rfc822 = TRUE; + else if (strncasecmp(str, "text/", 5) == 0) + parse_data->part->text = TRUE; + else if (strncasecmp(str, "multipart/", 10) == 0) { + parse_data->part->multipart = TRUE; + + if (strcasecmp(str+10, "digest") == 0) + parse_data->part->multipart_digest = TRUE; + } +} + +static void parse_content_type_param(const Rfc822Token *name, + const Rfc822Token *value, + int value_count, void *user_data) +{ + MessageParseData *parse_data = user_data; + const char *str; + + if (!parse_data->part->multipart || name->len != 8 || + strncasecmp(name->ptr, "boundary", 8) != 0) + return; + + if (parse_data->last_boundary == NULL) { + str = rfc822_tokens_get_value(value, value_count, FALSE); + parse_data->last_boundary = p_strdup(parse_data->pool, str); + } +} + +static void parse_header_field(MessagePart *part, + const char *name, unsigned int name_len, + const char *value, unsigned int value_len, + void *user_data) +{ + MessageParseData *parse_data = user_data; + + /* call the user-defined header parser */ + if (parse_data->func != NULL) { + parse_data->func(part, name, name_len, value, value_len, + parse_data->user_data); + } + + if (name_len == 12 && strncasecmp(name, "Content-Type", 12) == 0) { + /* we need to know the boundary */ + (void)message_content_parse_header(t_strndup(value, value_len), + parse_content_type, + parse_content_type_param, + parse_data); + } +} + +static MessagePart *message_parse_multipart(const char *msg, size_t size, + MessageParseData *parse_data) +{ + MessagePart *parent_part, *next_part, *part; + MessageBoundary *b; + off_t offset; + + /* multipart message. add new boundary */ + b = t_new(MessageBoundary, 1); + b->part = parse_data->part; + b->boundary = t_strdup(parse_data->last_boundary); + b->len = strlen(b->boundary); + + b->next = parse_data->boundaries; + parse_data->boundaries = b; + + /* reset fields */ + p_free_and_null(parse_data->pool, parse_data->last_boundary); + p_free_and_null(parse_data->pool, parse_data->last_content_type); + + /* skip the data before the first boundary */ + parent_part = parse_data->part; + next_part = message_skip_boundary(msg, size, parse_data->boundaries, + &parent_part->body_size); + + /* now, parse the parts */ + while (next_part == parent_part) { + /* new child */ + part = message_part_append(parse_data->pool, parent_part); + + /* set child position */ + memcpy(&part->pos, &parent_part->pos, sizeof(MessagePosition)); + part->pos.physical_pos += parent_part->body_size.physical_size + + parent_part->header_size.physical_size; + part->pos.virtual_pos += parent_part->body_size.virtual_size + + parent_part->header_size.virtual_size; + + offset = parent_part->body_size.physical_size; + parse_data->part = part; + next_part = message_parse_part(msg + offset, size - offset, + parse_data); + + /* update our size */ + message_size_add_part(&parent_part->body_size, part); + + if (next_part != parent_part) + break; + + /* skip the boundary */ + offset = parent_part->body_size.physical_size; + next_part = message_skip_boundary(msg + offset, size - offset, + parse_data->boundaries, + &parent_part->body_size); + } + + /* remove boundary */ + i_assert(parse_data->boundaries == b); + parse_data->boundaries = b->next; + return next_part; +} + +static MessagePart *message_parse_part(const char *msg, size_t size, + MessageParseData *parse_data) +{ + MessagePart *next_part, *part; + size_t hdr_size; + + message_parse_header(parse_data->part, msg, size, + &parse_data->part->header_size, + parse_header_field, parse_data); + + /* update message position/size */ + hdr_size = parse_data->part->header_size.physical_size; + msg += hdr_size; size -= hdr_size; + + if (parse_data->last_boundary != NULL) + return message_parse_multipart(msg, size, parse_data); + + if (parse_data->last_content_type == NULL) { + if (parse_data->part->parent != NULL && + parse_data->part->parent->multipart_digest) { + /* when there's no content-type specified and we're + below multipart/digest, the assume message/rfc822 + content-type */ + parse_data->part->message_rfc822 = TRUE; + } else { + /* otherwise we default to text/plain */ + parse_data->part->text = TRUE; + } + } + + p_free_and_null(parse_data->pool, parse_data->last_boundary); + p_free_and_null(parse_data->pool, parse_data->last_content_type); + + if (parse_data->part->message_rfc822) { + /* message/rfc822 part - the message body begins with + headers again, this works pretty much the same as + a single multipart/mixed item */ + part = message_part_append(parse_data->pool, parse_data->part); + + parse_data->part = part; + next_part = message_parse_part(msg, size, parse_data); + parse_data->part = part->parent; + + /* our body size is the size of header+body in message/rfc822 */ + message_size_add_part(&part->parent->body_size, part); + } else { + /* normal message, read until the next boundary */ + part = parse_data->part; + next_part = message_parse_body(msg, size, + parse_data->boundaries, + &part->body_size); + } + + return next_part; +} + +MessagePart *message_parse(Pool pool, const char *msg, size_t size, + MessageHeaderFunc func, void *user_data) +{ + MessagePart *part; + MessageParseData parse_data; + + memset(&parse_data, 0, sizeof(parse_data)); + parse_data.pool = pool; + parse_data.func = func; + parse_data.user_data = user_data; + parse_data.part = part = p_new(pool, MessagePart, 1); + + t_push(); + message_parse_part(msg, size, &parse_data); + t_pop(); + return part; +} + +void message_parse_header(MessagePart *part, const char *msg, size_t size, + MessageSize *hdr_size, + MessageHeaderFunc func, void *user_data) +{ + const char *msg_start, *msg_end, *cr, *last_lf; + const char *name, *value, *name_end, *value_end; + int missing_cr_count, stop; + + msg_start = msg; + msg_end = msg + size; + + missing_cr_count = 0; cr = NULL; + name = msg; name_end = value = last_lf = NULL; + + if (hdr_size != NULL) + hdr_size->lines = 0; + + stop = FALSE; + while (msg != msg_end && !stop) { + switch (*msg) { + case '\n': + if (hdr_size != NULL) + hdr_size->lines++; + + if (msg == msg_start || + (cr == msg_start && cr == msg-1)) { + /* no headers at all */ + if (cr != msg-1) + missing_cr_count++; + stop = TRUE; + break; + } else if (cr == msg-1) { + /* CR+LF */ + value_end = cr; + + if (last_lf == cr-1) { + /* LF+CR+LF -> end of headers */ + stop = TRUE; + break; + } + } else { + /* missing CR */ + missing_cr_count++; + value_end = msg; + + if (last_lf == msg-1) { + /* LF+LF -> end of headers */ + stop = TRUE; + break; + } + } + last_lf = msg; + + if (msg+1 != msg_end && IS_LWSP(msg[1])) { + /* long header continuing in next line */ + break; + } + + /* Ignore header lines missing ':' (value == NULL) */ + if (func != NULL && value != NULL) { + func(part, name, (unsigned int) (name_end-name), + value, (unsigned int) (value_end-value), + user_data); + } + + /* reset the data */ + name = msg+1; + name_end = NULL; + value = NULL; + break; + case '\r': + cr = msg; + break; + case ':': + if (value != NULL) + break; + name_end = msg; + + /* skip the ending whitespace for field */ + while (name_end != name && IS_LWSP(name_end[-1])) + name_end--; + + /* get beginning of field value */ + value = msg+1; + if (msg+1 != msg_end && IS_LWSP(msg[1])) + value++; + break; + } + + msg++; + } + + if (hdr_size != NULL) { + hdr_size->physical_size = (int) (msg - msg_start); + hdr_size->virtual_size = + hdr_size->physical_size + missing_cr_count; + } +} + +static MessageBoundary *boundary_find(MessageBoundary *boundaries, + const char *msg, unsigned int len) +{ + while (boundaries != NULL) { + if (boundaries->len <= len && + strncmp(boundaries->boundary, msg, boundaries->len) == 0) + return boundaries; + + boundaries = boundaries->next; + } + + return NULL; +} + +static MessagePart *message_parse_body(const char *msg, size_t size, + MessageBoundary *boundaries, + MessageSize *body_size) +{ + MessageBoundary *boundary; + const char *msg_start, *msg_end, *cr; + unsigned int missing_cr_count, len; + + msg_start = msg; + msg_end = msg + size; + + missing_cr_count = 0; cr = NULL; + + boundary = NULL; + while (msg != msg_end) { + if (*msg == '\r') + cr = msg; + else if (*msg == '\n') { + if (cr != msg-1) + missing_cr_count++; + body_size->lines++; + } else if (*msg == '-' && msg+2 < msg_end && msg[1] == '-' && + (msg == msg_start || msg[-1] == '\n')) { + /* "\n--", could be boundary */ + len = (unsigned int) (msg_end - (msg+2)); + + boundary = boundary_find(boundaries, msg+2, len); + if (boundary != NULL) { + /* boundary found, move the pointer + before the [CR]LF */ + if (msg != msg_start) { + msg--; + if (cr == msg-1) + msg--; + else + missing_cr_count--; + body_size->lines--; + } + break; + } + } + + msg++; + } + + len = (unsigned int) (msg - msg_start); + body_size->physical_size += len; + body_size->virtual_size += len + missing_cr_count; + + return boundary == NULL ? NULL : boundary->part; +} + +static MessagePart *message_skip_boundary(const char *msg, size_t size, + MessageBoundary *boundaries, + MessageSize *boundary_size) +{ + MessageBoundary *boundary; + const char *msg_start, *msg_end, *cr; + unsigned int len, missing_cr_count; + int end_boundary; + + /* first find and skip the boundary */ + msg_start = msg; + msg_end = msg + size; + + cr = NULL; missing_cr_count = 0; + + boundary = NULL; + while (msg != msg_end) { + if (*msg == '-' && msg+2 < msg_end && msg[1] == '-' && + (msg == msg_start || msg[-1] == '\n')) { + /* possible boundary */ + len = (unsigned int) (msg_end - (msg+2)); + boundary = boundary_find(boundaries, msg+2, len); + if (boundary != NULL) { + /* skip the boundary */ + msg += 2 + boundary->len; + break; + } + } else if (*msg == '\r') + cr = msg; + else if (*msg == '\n') { + if (cr != msg-1) + missing_cr_count++; + boundary_size->lines++; + } + msg++; + } + + len = (unsigned int) (msg - msg_start); + boundary_size->physical_size += len; + boundary_size->virtual_size += len + missing_cr_count; + + if (boundary == NULL) + return NULL; + + /* now read the boundary until we reach the end of line */ + msg_start = msg; + end_boundary = msg+2 <= msg_end && msg[0] == '-' && msg[1] == '-'; + while (msg != msg_end) { + if (*msg == '\r') + cr = msg; + else if (*msg == '\n') { + if (cr != msg-1) + boundary_size->virtual_size++; + boundary_size->lines++; + msg++; + break; + } + + msg++; + } + + len = (unsigned int) (msg - msg_start); + boundary_size->physical_size += len; + boundary_size->virtual_size += len; + + if (end_boundary) { + /* skip the footer */ + return message_parse_body(msg, (unsigned int) (msg_end-msg), + boundaries, boundary_size); + } + + return boundary->part; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/message-parser.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,55 @@ +#ifndef __MESSAGE_PARSER_H +#define __MESSAGE_PARSER_H + +typedef struct _MessagePart MessagePart; +typedef struct _MessagePosition MessagePosition; +typedef struct _MessageSize MessageSize; + +struct _MessagePosition { + off_t physical_pos; + off_t virtual_pos; +}; + +struct _MessageSize { + size_t physical_size; + size_t virtual_size; + unsigned int lines; +}; + +struct _MessagePart { + MessagePart *parent; + MessagePart *next; + MessagePart *children; + + MessagePosition pos; + MessageSize header_size; + MessageSize body_size; + + unsigned int multipart:1; + unsigned int multipart_digest:1; + unsigned int message_rfc822:1; + unsigned int text:1; /* content-type: text/.. */ + unsigned int binary:1; /* content-transfer-encoding: binary */ + + void *user_data; +}; + +/* NOTE: name and value aren't \0-terminated */ +typedef void (*MessageHeaderFunc)(MessagePart *part, + const char *name, unsigned int name_len, + const char *value, unsigned int value_len, + void *user_data); + +/* func is called for each field in message header. */ +MessagePart *message_parse(Pool pool, const char *msg, size_t size, + MessageHeaderFunc func, void *user_data); + +/* Call func for each field in message header. Fills the hdr_size. + part can be NULL, just make sure your header function works with it. + This function doesn't use temp. mempool so your header function may save + return values to it. */ +void message_parse_header(MessagePart *part, const char *msg, size_t size, + MessageSize *hdr_size, + MessageHeaderFunc func, void *user_data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/message-size.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,95 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "message-parser.h" +#include "message-size.h" + +void message_get_header_size(const char *msg, size_t size, MessageSize *hdr) +{ + const char *msg_start, *msg_end, *cr, *last_lf; + int missing_cr_count; + + hdr->lines = 0; + + msg_start = msg; + msg_end = msg + size; + + /* get header size */ + + cr = last_lf = NULL; missing_cr_count = 0; + while (msg != msg_end) { + if (*msg == '\r') + cr = msg; + else if (*msg == '\n') { + hdr->lines++; + + if (msg == msg_start || + (cr == msg_start && cr == msg-1)) { + /* no headers at all */ + if (cr != msg-1) + missing_cr_count++; + msg++; + break; + } + + if (cr == msg-1) { + /* CR+LF */ + if (last_lf == cr-1) { + /* LF+CR+LF -> end of headers */ + msg++; + break; + } + } else { + /* missing CR */ + missing_cr_count++; + + if (last_lf == msg-1) { + /* LF+LF -> end of headers */ + msg++; + break; + } + } + last_lf = msg; + } + + msg++; + } + + hdr->physical_size = (int) (msg-msg_start); + hdr->virtual_size = hdr->physical_size + missing_cr_count; +} + +void message_get_body_size(const char *msg, size_t size, MessageSize *body) +{ + const char *msg_start, *msg_end, *cr; + int missing_cr_count; + + msg_start = msg; + msg_end = msg + size; + + body->lines = 0; + + cr = NULL; missing_cr_count = 0; + while (msg != msg_end) { + if (*msg == '\r') + cr = msg; + else if (*msg == '\n') { + body->lines++; + + if (cr != msg-1) + missing_cr_count++; + } + + msg++; + } + + body->physical_size = (int) (msg-msg_start); + body->virtual_size = (int) (msg-msg_start) + missing_cr_count; +} + +void message_size_add(MessageSize *dest, MessageSize *src) +{ + dest->virtual_size += src->virtual_size; + dest->physical_size += src->physical_size; + dest->lines += src->lines; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/message-size.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,10 @@ +#ifndef __MESSAGE_SIZE_H +#define __MESSAGE_SIZE_H + +#include "message-parser.h" + +void message_get_header_size(const char *msg, size_t size, MessageSize *hdr); +void message_get_body_size(const char *msg, size_t size, MessageSize *body); +void message_size_add(MessageSize *dest, MessageSize *src); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/rfc822-address.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,215 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "temp-string.h" +#include "rfc822-tokenize.h" +#include "rfc822-address.h" + +static Rfc822Address *new_address(Pool pool, Rfc822Address ***next_addr) +{ + Rfc822Address *addr; + + addr = p_new(pool, Rfc822Address, 1); + + **next_addr = addr; + *next_addr = &addr->next; + + return addr; +} + +static int read_until(const Rfc822Token *tokens, const char *stop_tokens, + TempString *comment) +{ + int i, pos; + + /* find the stop token */ + for (i = 0; tokens[i].token != 0; i++) { + if (strchr(stop_tokens, tokens[i].token) != NULL) + break; + + if (tokens[i].token == '(') { + /* save comment */ + if (comment->len > 0) + t_string_append_c(comment, ' '); + pos = comment->len; + + t_string_append_n(comment, tokens[i].ptr, + tokens[i].len); + + string_remove_escapes(comment->str + pos); + comment->len = strlen(comment->str); + } + } + + return i; +} + +static void read_until_get(const Rfc822Token **tokens, const char *stop_tokens, + TempString *phrase, TempString *comment) +{ + const char *value; + int count; + + count = read_until(*tokens, stop_tokens, comment); + if (count > 0) { + value = rfc822_tokens_get_value(*tokens, count, FALSE); + t_string_append(phrase, value); + + *tokens += count; + } +} + +Rfc822Address *rfc822_address_parse(Pool pool, const char *str) +{ + Rfc822Address *first_addr, **next_addr, *addr; + TempString *mailbox, *domain, *route, *name, *comment, *next_phrase; + const Rfc822Token *tokens; + const char *list, *value; + int ingroup, stop, count, spaces; + + if (str == NULL || *str == '\0') + return NULL; + + first_addr = NULL; + next_addr = &first_addr; + + /* 1) name <@route:mailbox@domain>, ... + 2) mailbox@domain (name), ... + 3) group: name <box@domain>, box2@domain2 (name2), ... ;, ... + + ENVELOPE wants groups to be stored like (NIL, NIL, group, NIL), + ..., (NIL, NIL, NIL, NIL) + */ + tokens = rfc822_tokenize(str, NULL, NULL, NULL); + + t_push(); + mailbox = t_string_new(128); + domain = t_string_new(128); + route = t_string_new(128); + name = t_string_new(128); + comment = t_string_new(128); + + ingroup = FALSE; + list = ",@<:"; + + next_phrase = mailbox; stop = FALSE; + while (!stop) { + count = read_until(tokens, list, comment); + if (count > 0) { + /* put spaces around tokens if we're parsing name */ + spaces = tokens[count].token == '<' || + next_phrase == name; + if (spaces && next_phrase->len > 0) + t_string_append_c(next_phrase, ' '); + + value = rfc822_tokens_get_value(tokens, count, spaces); + t_string_append(next_phrase, value); + tokens += count; + } + + switch (tokens->token) { + case 0: + case ',': + case ';': + /* end of address */ + if (mailbox->len > 0 || domain->len > 0 || + route->len > 0 || name->len > 0) { + addr = new_address(pool, &next_addr); + addr->mailbox = p_strdup(pool, mailbox->str); + addr->domain = domain->len == 0 ? NULL : + p_strdup(pool, domain->str); + addr->route = route->len == 0 ? NULL : + p_strdup(pool, route->str); + addr->name = next_phrase == name ? + p_strdup(pool, name->str) : + p_strdup(pool, comment->str); + } + + if (ingroup && tokens->token == ';') { + /* end of group - add end of group marker */ + ingroup = FALSE; + (void)new_address(pool, &next_addr); + } + + if (tokens->token == 0) { + stop = TRUE; + break; + } + + list = ingroup ? ",@<;" : ",@<:"; + + t_string_truncate(mailbox, 0); + t_string_truncate(domain, 0); + t_string_truncate(route, 0); + t_string_truncate(name, 0); + t_string_truncate(comment, 0); + + tokens++; + next_phrase = mailbox; + break; + case '@': + /* domain part comes next */ + tokens++; + next_phrase = domain; + list = ingroup ? ",<;" : ",<"; + break; + case '<': + /* route-addr */ + tokens++; + + /* mailbox/domain name so far has actually + been the real name */ + t_string_append(name, mailbox->str); + if (domain->len > 0) { + t_string_append_c(name, '@'); + t_string_append(name, domain->str); + } + + t_string_truncate(mailbox, 0); + t_string_truncate(domain, 0); + + read_until_get(&tokens, "@>", mailbox, NULL); + if (tokens->token == '@' && mailbox->len == 0) { + /* route is given */ + tokens++; + read_until_get(&tokens, ":>", route, NULL); + if (tokens->token == ':') { + /* mailbox comes next */ + tokens++; + read_until_get(&tokens, "@>", + mailbox, NULL); + } + } + + if (tokens->token == '@') { + tokens++; + read_until_get(&tokens, ">", domain, NULL); + } + + if (tokens->token == '>') + tokens++; + + next_phrase = name; + list = ingroup ? ",;" : ","; + break; + case ':': + /* beginning of group */ + addr = new_address(pool, &next_addr); + addr->name = p_strdup(pool, mailbox->str); + + t_string_truncate(mailbox, 0); + tokens++; + + ingroup = TRUE; + list = ",@<;"; + break; + } + } + + if (ingroup) + (void)new_address(pool, &next_addr); + + t_pop(); + return first_addr; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/rfc822-address.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,14 @@ +#ifndef __RFC822_ADDRLIST_H +#define __RFC822_ADDRLIST_H + +typedef struct _Rfc822Address Rfc822Address; + +struct _Rfc822Address { + Rfc822Address *next; + + char *name, *route, *mailbox, *domain; +}; + +Rfc822Address *rfc822_address_parse(Pool pool, const char *str); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/rfc822-date.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,226 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "gmtoff.h" +#include "rfc822-date.h" +#include "rfc822-tokenize.h" + +#include <ctype.h> + +static const char *month_names[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static const char *weekday_names[] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static int parse_timezone(const char *str, unsigned int len) +{ + int offset; + char chr; + + if (len == 5 && (*str == '+' || *str == '-')) { + /* numeric offset */ + offset = (str[1]-'0') * 1000 + (str[2]-'0') * 100 + + (str[3]-'0') * 10 + (str[4]-'0'); + return *str == '+' ? offset : -offset; + } + + if (len == 1) { + /* military zone - handle them the correct way, not as + RFC822 says. RFC2822 though suggests that they'd be + considered as unspecified.. */ + chr = i_toupper(*str); + if (chr < 'J') + return (*str-'A'+1) * 60; + if (chr == 'J') + return 0; + if (chr <= 'M') + return (*str-'A') * 60; + if (chr < 'Z') + return ('M'-*str) * 60; + return 0; + } + + if (len == 2 && i_toupper(str[0]) == 'U' && i_toupper(str[1]) == 'T') { + /* UT - Universal Time */ + return 0; + } + + if (len == 3) { + /* GMT | [ECMP][DS]T */ + if (str[2] != 'T') + return 0; + + switch (i_toupper(*str)) { + case 'E': + offset = -5 * 60; + break; + case 'C': + offset = -6 * 60; + break; + case 'M': + offset = -7 * 60; + break; + case 'P': + offset = -8 * 60; + break; + default: + /* GMT and others */ + return 0; + } + + if (i_toupper(str[1]) == 'D') + return offset + 60; + if (i_toupper(str[1] == 'S')) + return offset; + } + + return 0; +} + +static const Rfc822Token *next_token(const Rfc822Token **tokens) +{ + const Rfc822Token *ret; + + if ((*tokens)->token == 0) + return NULL; + + ret = *tokens; + (*tokens)++; + return ret; +} + +int rfc822_parse_date(const char *str, time_t *time) +{ + struct tm tm; + const Rfc822Token *tokens, *tok; + unsigned int i; + int zone_offset; + + if (str == NULL || *str == '\0') + return FALSE; + + /* [weekday_name "," ] dd month_name [yy]yy hh:mi[:ss] timezone + + don't waste time checking it too properly, if there's errors we + either pick them up at mktime() or have an invalid timestamp, + which would happen anyway. + + we support comments here even while no-one ever uses them */ + + tokens = rfc822_tokenize(str, NULL, NULL, NULL); + + memset(&tm, 0, sizeof(tm)); + + /* skip the optional weekday */ + tok = next_token(&tokens); + if (tok != NULL && tok->token == 'A' && tok->len == 3) { + tok = next_token(&tokens); + if (tok == NULL || tok->token != ',') + return FALSE; + + tok = next_token(&tokens); + } + + /* dd */ + if (tok == NULL || tok->token != 'A' || tok->len != 2) + return FALSE; + tm.tm_mday = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0'; + + /* month name */ + tok = next_token(&tokens); + if (tok == NULL || tok->token != 'A' || tok->len != 3) + return FALSE; + + for (i = 0; i < 12; i++) { + if (strncasecmp(month_names[i], tok->ptr, 3) == 0) { + tm.tm_mon = i; + break; + } + } + if (i == 12) + return FALSE; + + /* [yy]yy */ + tok = next_token(&tokens); + if (tok == NULL || tok->token != 'A') + return FALSE; + + for (i = 0; i < tok->len; i++) + tm.tm_year = tm.tm_year * 10 + tok->ptr[i]-'0'; + + if (tok->len == 2) { + /* two digit year, assume 1970+ */ + if (tm.tm_year < 70) + tm.tm_year += 100; + } else if (tok->len != 4) { + /* y10k bug here */ + tm.tm_year -= 1900; + return FALSE; + } + + /* hh */ + tok = next_token(&tokens); + if (tok == NULL || tok->token != 'A' || tok->len != 2) + return FALSE; + tm.tm_hour = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0'; + + /* :mm */ + tok = next_token(&tokens); + if (tok == NULL || tok->token != ':') + return FALSE; + tok = next_token(&tokens); + if (tok == NULL || (tok->token != 'A' && tok->len != 2)) + return FALSE; + tm.tm_min = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0'; + + /* [:ss] */ + tok = next_token(&tokens); + if (tok != NULL && tok->token == ':') { + tok = next_token(&tokens); + if (tok == NULL || (tok->token != 'A' && tok->len != 2)) + return FALSE; + tm.tm_sec = (tok->ptr[0]-'0') * 10 + tok->ptr[1]-'0'; + } + + /* timezone */ + if (tok == NULL || tok->token != 'A') + return FALSE; + + zone_offset = parse_timezone(tok->ptr, tok->len); + + tm.tm_isdst = -1; + *time = mktime(&tm); + if (*time < 0) + return FALSE; + + *time -= zone_offset * 60; + return TRUE; +} + +const char *rfc822_to_date(time_t time) +{ + struct tm *tm; + int offset, negative; + + tm = localtime(&time); + offset = gmtoff(tm, time); + if (offset >= 0) + negative = 0; + else { + negative = 1; + offset = -offset; + } + offset /= 60; + + return t_strdup_printf("%s, %02d %s %04d %02d:%02d:%02d %c%02d%02d", + weekday_names[tm->tm_wday], + tm->tm_mday, + month_names[tm->tm_mon], + tm->tm_year+1900, + tm->tm_hour, tm->tm_min, tm->tm_sec, + negative ? '-' : '+', offset / 60, offset % 60); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/rfc822-date.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,7 @@ +#ifndef __RFC822_DATE +#define __RFC822_DATE + +int rfc822_parse_date(const char *str, time_t *time); +const char *rfc822_to_date(time_t time); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/rfc822-tokenize.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,300 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "rfc822-tokenize.h" + +#define INITIAL_COUNT 4 + +#define PARSE_ERROR() \ + STMT_START { \ + if (error_func != NULL && \ + !error_func(str, (int) (p-str), '\0', user_data)) \ + return NULL; \ + } STMT_END + +#define PARSE_ERROR_MISSING(c) \ + STMT_START { \ + if (error_func != NULL && \ + !error_func(str, (int) (p-str), c, user_data)) \ + return NULL; \ + } STMT_END + +static Rfc822Token *alloc_token(Rfc822Token **tokens, int *pos, int type) +{ + Rfc822Token *token; + + if (*pos+1 >= INITIAL_COUNT) + *tokens = t_buffer_reget_type(*tokens, Rfc822Token, *pos + 2); + + token = (*tokens) + *pos; + (*pos)++; + + token->token = type; + token->ptr = NULL; + token->len = 0; + return token; +} + +const Rfc822Token *rfc822_tokenize(const char *str, int *tokens_count, + Rfc822TokenizeErrorFunc error_func, + void *user_data) +{ + Rfc822Token *first_token, *token; + const char *p, *last_atom; + int level, in_bracket, pos; + + first_token = t_buffer_get_type(Rfc822Token, INITIAL_COUNT); + pos = 0; + + token = NULL; + last_atom = NULL; + + in_bracket = FALSE; + for (p = str; *p != '\0'; p++) { + switch (*p) { + case ' ': + case '\t': + case '\r': + case '\n': + /* skip whitespace */ + break; + + /* RFC822 specials: */ + case '@': + case ',': + case ';': + case ':': + case '.': + /* RFC 2045 specials: */ + case '/': + case '?': + case '=': + token = alloc_token(&first_token, &pos, *p); + break; + + case '(': + /* (comment) - nesting is allowed */ + token = alloc_token(&first_token, &pos, '('); + token->ptr = ++p; + + level = 1; + for (; *p != '\0'; p++) { + if (*p == '\\' && p[1] != '\0') + p++; + else if (*p == '(') + level++; + else if (*p == ')') { + if (--level == 0) + break; + } + } + + if (level > 0) + PARSE_ERROR_MISSING(')'); + + token->len = (int) (p - token->ptr); + break; + + case '[': + /* domain literal - nesting isn't allowed */ + token = alloc_token(&first_token, &pos, '['); + token->ptr = ++p; + + for (; *p != '\0' && *p != ']'; p++) { + if (*p == '\\' && p[1] != '\0') + p++; + else if (*p == '[') { + /* nesting not allowed, but + continue anyway */ + PARSE_ERROR(); + } + } + token->len = (int) (p - token->ptr); + + if (*p == '\0') + PARSE_ERROR_MISSING(']'); + break; + + case '"': + /* quoted string */ + token = alloc_token(&first_token, &pos, '"'); + token->ptr = ++p; + + for (; *p != '\0' && *p != '"'; p++) { + if (*p == '\\' && p[1] != '\0') + p++; + } + token->len = (int) (p - token->ptr); + + if (*p == '\0') + PARSE_ERROR_MISSING('"'); + break; + + case '<': + if (in_bracket) { + /* '<' cannot be nested */ + PARSE_ERROR(); + break; + } + + token = alloc_token(&first_token, &pos, '<'); + in_bracket = TRUE; + break; + case '>': + if (!in_bracket) { + /* missing '<' */ + PARSE_ERROR(); + break; + } + + token = alloc_token(&first_token, &pos, '>'); + in_bracket = FALSE; + break; + + case ')': + case ']': + case '\\': + PARSE_ERROR(); + break; + default: + /* atom */ + if (last_atom != p-1) { + token = alloc_token(&first_token, &pos, 'A'); + token->ptr = p; + } + + token->len++; + last_atom = p; + break; + } + + if (*p == '\0') + break; + } + + if (in_bracket && error_func != NULL) { + if (!error_func(str, (int) (p-str), '>', user_data)) + return NULL; + } + + if (tokens_count != NULL) + *tokens_count = pos; + + first_token[pos++].token = 0; + t_buffer_alloc(sizeof(Rfc822Token) * pos); + return first_token; +} + +const char *rfc822_tokens_get_value(const Rfc822Token *tokens, int count, + int space_separators) +{ + char *buf; + unsigned int i, len, buf_size; + + if (count <= 0) + return ""; + + buf_size = 256; + buf = t_buffer_get(buf_size); + + len = 0; + for (; count > 0; count--, tokens++) { + if (tokens->token == '(') + continue; /* skip comments */ + + /* +4 == ' ' '[' ']' '\0' */ + if (len + tokens->len+4 >= buf_size) { + buf_size = nearest_power(buf_size + tokens->len + 3); + buf = t_buffer_reget(buf, buf_size); + } + + if (space_separators && len > 0) + buf[len++] = ' '; + + switch (tokens->token) { + case '"': + case '[': + if (tokens->token == '[') + buf[len++] = '['; + + /* copy the string removing '\' chars */ + for (i = 0; i < tokens->len; i++) { + if (tokens->ptr[i] == '\\' && i+1 < tokens->len) + i++; + + buf[len++] = tokens->ptr[i]; + } + + if (tokens->token == '[') + buf[len++] = ']'; + break; + case 'A': + memcpy(buf+len, tokens->ptr, tokens->len); + len += tokens->len; + break; + default: + i_assert(tokens->token != 0); + buf[len++] = (char) tokens->token; + break; + } + } + + buf[len++] = '\0'; + t_buffer_alloc(len); + return buf; +} + +const char *rfc822_tokens_get_value_quoted(const Rfc822Token *tokens, + int count, int space_separators) +{ + char *buf; + unsigned int len, buf_size; + + if (count <= 0) + return "\"\""; + + buf_size = 256; + buf = t_buffer_get(buf_size); + buf[0] = '"'; len = 1; + + for (; count > 0; count--, tokens++) { + if (tokens->token == '(') + continue; /* skip comments */ + + /* +5 == ' ' '[' ']' '"' '\0' */ + if (len + tokens->len+5 >= buf_size) { + buf_size = nearest_power(buf_size + tokens->len + 3); + buf = t_buffer_reget(buf, buf_size); + } + + if (space_separators && len > 0) + buf[len++] = ' '; + + switch (tokens->token) { + case '"': + case '[': + if (tokens->token == '[') + buf[len++] = '['; + + memcpy(buf+len, tokens->ptr, tokens->len); + len += tokens->len; + + if (tokens->token == '[') + buf[len++] = ']'; + break; + case 'A': + memcpy(buf+len, tokens->ptr, tokens->len); + len += tokens->len; + break; + default: + i_assert(tokens->token != 0); + buf[len++] = (char) tokens->token; + break; + } + } + + buf[len++] = '"'; + buf[len++] = '\0'; + t_buffer_alloc(len); + return buf; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-mail/rfc822-tokenize.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,58 @@ +#ifndef __RFC822_TOKENIZE_H +#define __RFC822_TOKENIZE_H + +typedef struct _Rfc822Token Rfc822Token; + +#define IS_TOKEN_STRING(token) \ + ((token) == 'A' || (token) == '"' || (token) == '(' || (token) == '[')) + +#define IS_LWSP(c) \ + ((c) == ' ' || (c) == '\t') + +struct _Rfc822Token { + /* + 0 = last token + 'A' = atom + '"' = quoted string + '(' = comment + '[' = domain literal + + RFC822 specials: + + '<', '>', '@', ',', ';', ':', '\', '.' + + RFC2045 tspecials: + + '/', '?', '=' + */ + int token; + + /* - not including enclosing "", () or [] + - '\' isn't expanded + - [CR+]LF+LWSP (continued header) isn't removed */ + const char *ptr; + unsigned int len; +}; + +/* Parsing is aborted if returns FALSE. There's two kinds of errors: + + missing_char == '\0': unexpected character at str[pos] + missing_char != '\0': missing character */ +typedef int (*Rfc822TokenizeErrorFunc)(const char *str, int pos, + char missing_char, void *user_data); + +/* Tokenize the string. Returns NULL if string is empty. Memory for + returned array is allocated from temporary pool. You don't have to use + the tokens_count, since last token is always 0. */ +const Rfc822Token *rfc822_tokenize(const char *str, int *tokens_count, + Rfc822TokenizeErrorFunc error_func, + void *user_data); + +/* Returns the tokens as a string. */ +const char *rfc822_tokens_get_value(const Rfc822Token *tokens, int count, + int space_separators); +/* Returns the tokens as a "string". */ +const char *rfc822_tokens_get_value_quoted(const Rfc822Token *tokens, + int count, int space_separators); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,24 @@ +SUBDIRS = index subscription-file flags-file + +noinst_LIBRARIES = libstorage.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-imap + +real_sources = \ + mail-search.c \ + mail-storage.c + +libstorage_a_SOURCES = \ + $(real_sources) \ + mail-storage-register.c + +noinst_HEADERS = \ + mail-search.h \ + mail-storage.h + +DISTFILES = $(DIST_COMMON) $(real_sources) $(HEADERS) $(TEXINFOS) $(EXTRA_DIST) + +distclean-generic: + rm -f mail-storage-register.c
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/flags-file/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/flags-file/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,13 @@ +noinst_LIBRARIES = libstorage_flags_file.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-imap + +libstorage_flags_file_a_SOURCES = \ + flags-file.c + +noinst_HEADERS = \ + flags-file.h +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/flags-file/flags-file.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,507 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mmap-util.h" +#include "imap-util.h" +#include "flags-file.h" + +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> + +#define COUNTER_SIZE 4 +#define HEADER_SIZE (COUNTER_SIZE + 1) /* 0000\n */ + +struct _FlagsFile { + MailStorage *storage; + char *path; + int fd; + int lock_type; + + char sync_counter[COUNTER_SIZE]; + char *custom_flags[MAIL_CUSTOM_FLAGS_COUNT]; + int custom_flags_refcount; + + void *mmap_base; + size_t mmap_length; + + unsigned int syncing:1; +}; + +static int lock_file(FlagsFile *ff, int type); + +static int update_mmap(FlagsFile *ff) +{ + ff->mmap_base = mmap_rw_file(ff->fd, &ff->mmap_length); + if (ff->mmap_base == MAP_FAILED) { + ff->mmap_base = NULL; + mail_storage_set_critical(ff->storage, "mmap() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + (void)madvise(ff->mmap_base, ff->mmap_length, MADV_SEQUENTIAL); + return TRUE; +} + +static int flags_file_init(FlagsFile *ff) +{ + static char buf[HEADER_SIZE] = "0000\n"; + off_t pos; + + if (!lock_file(ff, F_WRLCK)) + return FALSE; + + /* make sure it's still empty after locking */ + pos = lseek(ff->fd, 0, SEEK_END); + if (pos != (off_t)-1 && pos < HEADER_SIZE) + pos = lseek(ff->fd, 0, SEEK_SET); + + if (pos == (off_t)-1) { + mail_storage_set_critical(ff->storage, "lseek() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + /* write the header - it's a 4 byte counter as hex */ + if (write(ff->fd, buf, HEADER_SIZE) != HEADER_SIZE) { + mail_storage_set_critical(ff->storage, "write() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + if (!lock_file(ff, F_UNLCK)) + return FALSE; + + return TRUE; +} + +static void flags_file_sync(FlagsFile *ff) +{ + char *data, *data_end, *line; + int i, num; + + memcpy(ff->sync_counter, ff->mmap_base, COUNTER_SIZE); + + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { + if (ff->custom_flags[i] != NULL) { + i_free(ff->custom_flags[i]); + ff->custom_flags[i] = NULL; + } + } + + data = ff->mmap_base; + data_end = data + ff->mmap_length; + + /* this loop skips the first line, which is the header */ + while (data != data_end) { + if (*data != '\n') { + data++; + continue; + } + + /* beginning of line, get the index */ + if (data+1 == data_end) + break; + data++; + + if (!i_isdigit(*data)) + continue; + + num = 0; + while (data != data_end && i_isdigit(*data)) { + num = num*10 + *data-'0'; + data++; + } + + if (num >= 0 && num < MAIL_CUSTOM_FLAGS_COUNT) { + /* get the name */ + if (data == data_end || *data != ' ') + continue; + + line = ++data; + while (data != data_end && *data != '\n') + data++; + + ff->custom_flags[num] = + i_strndup(line, (unsigned int) (data - line)); + } + } +} + +static int flags_file_check_sync(FlagsFile *ff) +{ + if (ff->custom_flags_refcount > 0) { + /* we've been locked from updates for now.. */ + return TRUE; + } + + if (ff->mmap_length != 0 && + memcmp(ff->sync_counter, ff->mmap_base, COUNTER_SIZE) == 0) + return TRUE; + + /* file modified, resync */ + if (!update_mmap(ff)) + return FALSE; + + if (ff->mmap_length < HEADER_SIZE) { + /* it's broken, rewrite header */ + if (ff->lock_type == F_RDLCK) + (void)lock_file(ff, F_UNLCK); + + if (!flags_file_init(ff)) + return FALSE; + + if (!update_mmap(ff)) + return FALSE; + } + + flags_file_sync(ff); + return TRUE; +} + +static int lock_file(FlagsFile *ff, int type) +{ + struct flock fl; + + if (ff->lock_type == type) + return TRUE; + + /* lock whole file */ + fl.l_type = type; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + while (fcntl(ff->fd, F_SETLKW, &fl) == -1) { + if (errno != EINTR) { + mail_storage_set_critical(ff->storage, "fcntl() failed " + "for flags file %s: %m", + ff->path); + return FALSE; + } + } + + ff->lock_type = type; + + if (type != F_UNLCK && !ff->syncing) { + ff->syncing = TRUE; + if (!flags_file_check_sync(ff)) { + ff->syncing = FALSE; + return FALSE; + } + + /* syncing may have changed locking, do it again */ + if (!lock_file(ff, type)) { + ff->syncing = FALSE; + return FALSE; + } + + ff->syncing = FALSE; + } + return TRUE; +} + +FlagsFile *flags_file_open_or_create(MailStorage *storage, const char *path) +{ + FlagsFile *ff; + int fd; + + fd = open(path, O_RDWR | O_CREAT, 0660); + if (fd == -1) { + mail_storage_set_critical(storage, "Can't open flags file " + "%s: %m", path); + return NULL; + } + + ff = i_new(FlagsFile, 1); + ff->storage = storage; + ff->path = i_strdup(path); + ff->fd = fd; + + if (!update_mmap(ff)) { + flags_file_destroy(ff); + return NULL; + } + + if (ff->mmap_length < HEADER_SIZE) { + /* we just created it, write the header */ + ff->syncing = TRUE; + if (!flags_file_init(ff) || !update_mmap(ff)) { + flags_file_destroy(ff); + return NULL; + } + ff->syncing = FALSE; + } + + flags_file_sync(ff); + return ff; +} + +void flags_file_destroy(FlagsFile *ff) +{ + int i; + + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) + i_free(ff->custom_flags[i]); + + (void)munmap(ff->mmap_base, ff->mmap_length); + (void)close(ff->fd); + + i_free(ff->path); + i_free(ff); +} + +static int flags_file_update_counter(FlagsFile *ff) +{ + int i; + + if (lseek(ff->fd, 0, SEEK_SET) == (off_t)-1) { + mail_storage_set_critical(ff->storage, "lseek() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + for (i = COUNTER_SIZE-1; i >= 0; i--) { + if (ff->sync_counter[i] == '9') { + ff->sync_counter[i] = 'A'; + break; + } + + if (ff->sync_counter[i] == 'F') { + /* digit wrapped, update next one */ + ff->sync_counter[i] = '0'; + } else { + ff->sync_counter[i]++; + break; + } + } + + if (write(ff->fd, ff->sync_counter, COUNTER_SIZE) != COUNTER_SIZE) { + mail_storage_set_critical(ff->storage, "write() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + return TRUE; +} + +static int flags_file_add(FlagsFile *ff, int idx, const char *name) +{ + const char *buf; + unsigned int len; + off_t pos; + + i_assert(idx < MAIL_CUSTOM_FLAGS_COUNT); + + /* first update the sync counter */ + if (!flags_file_update_counter(ff)) + return FALSE; + + /* add the flag */ + pos = lseek(ff->fd, 0, SEEK_END); + if (pos == (off_t)-1) { + mail_storage_set_critical(ff->storage, "lseek() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + if ((size_t) pos != ff->mmap_length) { + mail_storage_set_critical(ff->storage, "flags file %s was " + "changed by someone while we were" + "trying to modify it", ff->path); + return FALSE; + } + + buf = t_strdup_printf("\n%d %s\n", idx, name); + len = strlen(buf); + + if (((char *) ff->mmap_base)[ff->mmap_length-1] == '\n') { + /* don't add the \n prefix */ + buf++; + len--; + } + + if ((size_t) write(ff->fd, buf, len) != len) { + mail_storage_set_critical(ff->storage, "write() failed for " + "flags file %s: %m", ff->path); + return FALSE; + } + + if (!update_mmap(ff)) + return FALSE; + + return TRUE; +} + +static int flags_file_remove(FlagsFile *ff, int idx) +{ + char *data, *data_end, *line; + int num, pos, linelen; + + data = ff->mmap_base; + data_end = data + ff->mmap_length; + + while (data != data_end) { + if (*data != '\n') { + data++; + continue; + } + + /* beginning of line, get the index */ + if (data+1 == data_end) + break; + line = ++data; + + num = 0; + while (data != data_end && i_isdigit(*data)) { + num = num*10 + *data-'0'; + data++; + } + + if (num == idx) { + /* remove this line */ + while (data != data_end && data[-1] != '\n') + data++; + + linelen = (int) (data - line); + pos = (int) (data - (char *) ff->mmap_base); + memmove(line, data, ff->mmap_length - pos); + + ff->mmap_length -= linelen; + if (ftruncate(ff->fd, (off_t) ff->mmap_length) == -1) { + mail_storage_set_critical(ff->storage, + "ftruncate() failed for flags file " + "%s: %m", ff->path); + return FALSE; + } + + return TRUE; + } + } + + return FALSE; +} + +static int find_first_unused_flag(FlagsFile *ff) +{ + int i; + + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { + if (ff->custom_flags[i] == NULL) + return i; + } + + return -1; +} + +static void remove_unused_custom_flags(FlagsFile *ff, MailFlags used_flags) +{ + int i; + + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { + if ((used_flags & (1 << (i + MAIL_CUSTOM_FLAG_1_BIT))) == 0) { + i_free(ff->custom_flags[i]); + ff->custom_flags[i] = NULL; + + flags_file_remove(ff, i); + } + } +} + +static int get_flag_index(FlagsFile *ff, const char *flag, + MailFlags (*get_used_flags)(void *user_data), + void *user_data) +{ + int i, first_empty; + + /* check existing flags */ + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++) { + if (ff->custom_flags[i] == NULL) + continue; + + i_assert(ff->custom_flags[i] != '\0'); + if (strcasecmp(ff->custom_flags[i], flag) == 0) + return i; + } + + /* unlock + write lock, don't directly change from read -> write lock + to prevent deadlocking */ + if (!lock_file(ff, F_UNLCK) || !lock_file(ff, F_WRLCK)) + return -1; + + /* new flag, add it. first find the first free flag, note that + unlock+lock might have just changed it. */ + first_empty = find_first_unused_flag(ff); + if (first_empty == -1) { + /* all custom flags are used, see if some of them are unused */ + remove_unused_custom_flags(ff, get_used_flags(user_data)); + + first_empty = find_first_unused_flag(ff); + if (first_empty == -1) { + /* everything is in use */ + return -1; + } + } + + if (!flags_file_add(ff, first_empty, flag)) + return -1; + + ff->custom_flags[first_empty] = i_strdup(flag); + return first_empty; +} + +int flags_file_fix_custom_flags(FlagsFile *ff, MailFlags *flags, + const char *custom_flags[], + MailFlags (*get_used_flags)(void *user_data), + void *user_data) +{ + MailFlags oldflags, flag; + int i, idx; + + if ((*flags & MAIL_CUSTOM_FLAGS_MASK) == 0) + return TRUE; + + if (!lock_file(ff, F_RDLCK)) + return FALSE; + + oldflags = *flags; + *flags &= MAIL_SYSTEM_FLAGS_MASK; + + flag = MAIL_CUSTOM_FLAG_1; + for (i = 0; i < MAIL_CUSTOM_FLAGS_COUNT; i++, flag <<= 1) { + if (oldflags & flag) { + i_assert(custom_flags[i] != NULL && + *custom_flags[i] != '\0'); + + idx = get_flag_index(ff, custom_flags[i], + get_used_flags, user_data); + if (idx == -1) { + mail_storage_set_error(ff->storage, + "Maximum number of different custom " + "flags exceeded"); + (void)lock_file(ff, F_UNLCK); + return FALSE; + } + *flags |= 1 << (idx + MAIL_CUSTOM_FLAG_1_BIT); + } + } + + if (!lock_file(ff, F_UNLCK)) + return FALSE; + + return TRUE; +} + +const char **flags_file_list_get(FlagsFile *ff) +{ + ff->custom_flags_refcount++; + return (const char **) ff->custom_flags; +} + +void flags_file_list_unref(FlagsFile *ff) +{ + i_assert(ff->custom_flags_refcount > 0); + + ff->custom_flags_refcount--; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/flags-file/flags-file.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,27 @@ +#ifndef __FLAGS_FILE_H +#define __FLAGS_FILE_H + +#include "mail-storage.h" + +#define FLAGS_FILE_NAME ".customflags" + +typedef struct _FlagsFile FlagsFile; + +FlagsFile *flags_file_open_or_create(MailStorage *storage, const char *path); +void flags_file_destroy(FlagsFile *ff); + +/* Change custom flags so that they reflect the real flag numbers in + the file. get_used_flags is called when all flags are in use to figure + out which of them could be removed. */ +int flags_file_fix_custom_flags(FlagsFile *ff, MailFlags *flags, + const char *custom_flags[], + MailFlags (*get_used_flags)(void *user_data), + void *user_data); + +/* Returns a pointer to list of flags. */ +const char **flags_file_list_get(FlagsFile *ff); + +/* Call this after you've done with the flags list above */ +void flags_file_list_unref(FlagsFile *ff); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,26 @@ +SUBDIRS = maildir mbox + +noinst_LIBRARIES = libstorage_index.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-storage + +libstorage_index_a_SOURCES = \ + index-copy.c \ + index-expunge.c \ + index-fetch.c \ + index-fetch-section.c \ + index-save.c \ + index-search.c \ + index-status.c \ + index-storage.c \ + index-sync.c \ + index-update-flags.c + +noinst_HEADERS = \ + index-fetch.h \ + index-storage.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-copy.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,73 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "iobuffer.h" +#include "index-storage.h" +#include "mail-messageset.h" + +#include <unistd.h> + +typedef struct { + Mailbox *dest; + const char **custom_flags; +} CopyData; + +static int copy_func(MailIndex *index, MailIndexRecord *rec, + unsigned int seq __attr_unused__, void *user_data) +{ + CopyData *cd = user_data; + IOBuffer *buf; + off_t offset; + size_t size; + int fd, failed; + + fd = index->open_mail(index, rec, &offset, &size); + if (fd == -1) + return FALSE; + + /* save it in destination mailbox */ + buf = io_buffer_create_file(fd, default_pool, 4096); + failed = !cd->dest->save(cd->dest, rec->msg_flags, + cd->custom_flags, rec->internal_date, + buf, size); + + (void)close(fd); + return !failed; +} + +int index_storage_copy(Mailbox *box, Mailbox *destbox, + const char *messageset, int uidset) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + CopyData cd; + int failed; + + if (destbox->readonly) { + mail_storage_set_error(box->storage, + "Destination mailbox is read-only"); + return FALSE; + } + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED)) + return mail_storage_set_index_error(ibox); + + cd.custom_flags = flags_file_list_get(ibox->flagsfile); + cd.dest = destbox; + + if (uidset) { + failed = mail_index_uidset_foreach(ibox->index, messageset, + ibox->synced_messages_count, + copy_func, destbox) <= 0; + } else { + failed = mail_index_messageset_foreach(ibox->index, messageset, + ibox->synced_messages_count, copy_func, destbox) <= 0; + } + + flags_file_list_unref(ibox->flagsfile); + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || failed) + return mail_storage_set_index_error(ibox); + + return TRUE; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-expunge.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,54 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "index-storage.h" + +MailIndexRecord *index_expunge_seek_first(IndexMailbox *ibox, + unsigned int *seq) +{ + MailIndexHeader *hdr; + MailIndexRecord *rec; + + hdr = ibox->index->get_header(ibox->index); + if (hdr->deleted_messages_count == 0) + return NULL; + + /* find mails with DELETED flag and expunge them */ + if (hdr->first_deleted_uid_lowwater > 1) { + rec = ibox->index->lookup_uid_range(ibox->index, + hdr->first_deleted_uid_lowwater, hdr->next_uid-1); + if (rec == NULL) { + i_warning("index header's deleted_messages_count or " + "first_deleted_uid_lowwater is invalid."); + INDEX_MARK_CORRUPTED(ibox->index); + return NULL; + } else { + *seq = ibox->index->get_sequence(ibox->index, rec); + } + } else { + rec = ibox->index->lookup(ibox->index, 1); + *seq = 1; + } + + return rec; +} + +int index_storage_expunge(Mailbox *box) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + int failed; + + if (box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return FALSE; + } + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_EXCLUSIVE)) + return mail_storage_set_index_error(ibox); + + failed = !ibox->expunge_locked(ibox, NULL, NULL); + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || failed) + return mail_storage_set_index_error(ibox); + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-fetch-section.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,401 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "temp-string.h" +#include "iobuffer.h" +#include "rfc822-tokenize.h" +#include "imap-message-send.h" +#include "index-storage.h" +#include "index-fetch.h" + +#include <ctype.h> +#include <unistd.h> + +ImapCacheField index_fetch_body_get_cache(const char *section) +{ + if (*section >= '0' && *section <= '9') + return IMAP_CACHE_MESSAGE_PART | IMAP_CACHE_MESSAGE_OPEN; + + if (*section == '\0' || strcasecmp(section, "TEXT") == 0) { + /* no IMAP_CACHE_MESSAGE_BODY_SIZE, so that we don't + uselessly check it when we want to read partial data */ + return IMAP_CACHE_MESSAGE_OPEN; + } + + if (strncasecmp(section, "HEADER", 6) == 0 || + strcasecmp(section, "MIME") == 0) + return IMAP_CACHE_MESSAGE_HDR_SIZE | IMAP_CACHE_MESSAGE_OPEN; + + /* error */ + return 0; +} + +/* fetch BODY[] or BODY[TEXT] */ +static int fetch_body(MailIndexRecord *rec, MailFetchBodyData *sect, + FetchData *data, int fetch_header) +{ + MessageSize size; + const char *msg, *str; + int fd; + + if (!imap_msgcache_get_rfc822_partial(data->cache, rec->uid, + sect->skip, sect->max_size, + fetch_header, &size, &msg, &fd)) { + i_error("Couldn't get BODY[] for UID %u (index %s)", + rec->uid, data->index->filepath); + return FALSE; + } + + str = t_strdup_printf("{%lu}\r\n", (unsigned long) size.virtual_size); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + + (void)imap_message_send(data->outbuf, msg, fd, &size, + 0, sect->max_size); + return TRUE; +} + +static char *const *get_fields_array(const char *fields) +{ + char **field_list, **field; + + while (*fields == ' ') + fields++; + if (*fields == '(') + fields++; + + field_list = (char **) t_strsplit(fields, " )"); + + /* array ends at ")" element */ + for (field = field_list; *field != NULL; field++) { + if (strcasecmp(*field, ")") == 0) + *field = NULL; + } + + return field_list; +} + +static int header_match(char *const *fields, const char *data, size_t size) +{ + const char *field, *data_start, *data_end; + + i_assert(size > 0); + + data_start = data; + data_end = data + size; + + for (; *fields != NULL; fields++) { + field = *fields; + if (*field == '\0') + continue; + + for (data = data_start; data != data_end; data++) { + /* field has been uppercased long time ago while + parsing FETCH command */ + if (i_toupper(*data) != *field) + break; + + field++; + if (*field == '\0') { + /* "field : value" is valid */ + while (data+1 != data_end && IS_LWSP(data[1])) + data++; + + if (data+1 != data_end && data[1] == ':') + return TRUE; + break; + } + } + } + + return FALSE; +} + +static int header_match_not(char *const *fields, const char *data, size_t size) +{ + return !header_match(fields, data, size); +} + +static int header_match_mime(char *const *fields __attr_unused__, + const char *data, size_t size) +{ + if (size > 8 && strncasecmp(data, "Content-", 8) == 0) + return TRUE; + + if (size >= 13 && strncasecmp(data, "Mime-Version:", 13) == 0) + return TRUE; + + return FALSE; +} + +/* Store headers into dest, returns number of bytes written. */ +static unsigned int +fetch_header_fields(const char *msg, size_t size, + char *dest, char *const *fields, + int (*match_func) (char *const *, const char *, size_t)) +{ + const char *msg_start, *msg_end, *cr; + char *dest_start; + unsigned int i; + int matched; + + dest_start = dest; + + /* parse fields uppercased into array - no error checking */ + msg_start = msg; + msg_end = msg + size; + + cr = NULL; matched = FALSE; + for (; msg != msg_end; msg++) { + if (*msg == '\r') + cr = msg; + else if (*msg == '\n') { + if (!matched && msg != msg_start && + !IS_LWSP(*msg_start)) { + matched = match_func(fields, msg_start, + (size_t) (msg-msg_start)); + } + + if (matched) { + if (cr == msg-1) { + /* contains CR+LF, copy them */ + i = (unsigned int) (msg-msg_start)+1; + memcpy(dest, msg_start, i); + dest += i; + } else { + /* copy line without LF, appending + CR+LF afterwards */ + i = (unsigned int) (msg-msg_start); + memcpy(dest, msg_start, i); + dest += i; + + *dest++ = '\r'; + *dest++ = '\n'; + } + + /* see if it continues in next line */ + matched = msg+1 != msg_end && IS_LWSP(msg[1]); + } + + msg_start = msg+1; + } + } + + /* headers should always end with \n\n, so we don't need to + check the last line here */ + + return (unsigned int) (dest - dest_start); +} + +/* fetch wanted headers from given data */ +static void fetch_header_from(const char *msg, int fd, MessageSize *size, + const char *section, MailFetchBodyData *sect, + FetchData *data) +{ + const char *str; + char *dest; + unsigned int len; + + /* HEADER, MIME, HEADER.FIELDS (list), HEADER.FIELDS.NOT (list) */ + + if (strcasecmp(section, "HEADER") == 0) { + /* all headers */ + str = t_strdup_printf("{%lu}\r\n", + (unsigned long) size->virtual_size); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + (void)imap_message_send(data->outbuf, msg, fd, + size, sect->skip, sect->max_size); + return; + } + + /* partial headers - copy the wanted fields into temporary memory. + Insert missing CRs on the way. */ + t_push(); + dest = t_malloc(size->virtual_size); + + if (strncasecmp(section, "HEADER.FIELDS ", 14) == 0) { + len = fetch_header_fields(msg, size->physical_size, dest, + get_fields_array(section + 14), + header_match); + } else if (strncasecmp(section, "HEADER.FIELDS.NOT ", 18) == 0) { + len = fetch_header_fields(msg, size->physical_size, dest, + get_fields_array(section + 18), + header_match_not); + } else if (strcasecmp(section, "MIME") == 0) { + /* Mime-Version + Content-* fields */ + len = fetch_header_fields(msg, size->physical_size, dest, + NULL, header_match_mime); + } else { + /* error */ + len = 0; + } + + i_assert(len <= size->virtual_size); + + if ((off_t) len <= sect->skip) + len = 0; + else { + dest += sect->skip; + len -= sect->skip; + + if (sect->max_size > 0 && len > sect->max_size) + len = sect->max_size; + } + + str = t_strdup_printf("{%u}\r\n", len); + io_buffer_send(data->outbuf, str, strlen(str)); + if (len > 0) io_buffer_send(data->outbuf, dest, len); + + t_pop(); +} + +/* fetch BODY[HEADER...] */ +static int fetch_header(MailIndexRecord *rec, MailFetchBodyData *sect, + FetchData *data) +{ + MessageSize hdr_size; + const char *msg; + int fd; + + if (!imap_msgcache_get_rfc822(data->cache, rec->uid, + &hdr_size, NULL, &msg, &fd)) + return FALSE; + + fetch_header_from(msg, fd, &hdr_size, sect->section, sect, data); + return TRUE; +} + +/* Find MessagePart for section (eg. 1.3.4) */ +static MessagePart *part_find(MailIndexRecord *rec, MailFetchBodyData *sect, + FetchData *data, const char **section) +{ + MessagePart *part; + const char *path; + int num; + + part = imap_msgcache_get_parts(data->cache, rec->uid); + + path = sect->section; + while (*path >= '0' && *path <= '9' && part != NULL) { + /* get part number */ + num = 0; + while (*path != '\0' && *path != '.') { + if (*path < '0' || *path > '9') + return NULL; + num = num*10 + *path - '0'; + path++; + } + + if (*path == '.') + path++; + + if (part->multipart) { + /* find the part */ + part = part->children; + for (; num > 1 && part != NULL; num--) + part = part->next; + } else { + /* only 1 allowed with non-multipart messages */ + if (num != 1) + return NULL; + } + } + + *section = path; + return part; +} + +/* fetch BODY[1.2] or BODY[1.2.TEXT] */ +static int fetch_part_body(MessagePart *part, unsigned int uid, + MailFetchBodyData *sect, FetchData *data) +{ + const char *msg, *str; + off_t skip_pos; + int fd; + + if (!imap_msgcache_get_data(data->cache, uid, &msg, &fd, NULL)) + return FALSE; + + /* jump to beginning of wanted data */ + skip_pos = (off_t) (part->pos.physical_pos + + part->header_size.physical_size); + msg += skip_pos; + if (fd != -1 && lseek(fd, skip_pos, SEEK_CUR) == (off_t)-1) + fd = -1; + + str = t_strdup_printf("{%lu}\r\n", + (unsigned long) part->body_size.virtual_size); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + + /* FIXME: potential performance problem with big messages: + FETCH BODY[1]<100000..1024>, hopefully no clients do this */ + (void)imap_message_send(data->outbuf, msg, -1, &part->body_size, + sect->skip, sect->max_size); + return TRUE; +} + +/* fetch BODY[1.2.MIME|HEADER...] */ +static int fetch_part_header(MessagePart *part, unsigned int uid, + const char *section, MailFetchBodyData *sect, + FetchData *data) +{ + const char *msg; + + if (!imap_msgcache_get_data(data->cache, uid, &msg, NULL, NULL)) + return FALSE; + + fetch_header_from(msg + part->pos.physical_pos, -1, + &part->header_size, section, sect, data); + return TRUE; +} + +static int fetch_part(MailIndexRecord *rec, MailFetchBodyData *sect, + FetchData *data) +{ + MessagePart *part; + const char *section; + + part = part_find(rec, sect, data, §ion); + if (part == NULL) + return FALSE; + + if (*section == '\0' || strcasecmp(section, "TEXT") == 0) + return fetch_part_body(part, rec->uid, sect, data); + + if (strncasecmp(section, "HEADER", 6) == 0) + return fetch_part_header(part, rec->uid, section, sect, data); + if (strcasecmp(section, "MIME") == 0) + return fetch_part_header(part, rec->uid, section, sect, data); + + return FALSE; +} + +void index_fetch_body_section(MailIndexRecord *rec, + unsigned int seq __attr_unused__, + MailFetchBodyData *sect, FetchData *data) +{ + const char *str; + int fetch_ok; + + str = !sect->skip_set ? + t_strdup_printf(" BODY[%s] ", sect->section) : + t_strdup_printf(" BODY[%s]<%lu> ", sect->section, + (unsigned long) sect->skip); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + + if (*sect->section == '\0') { + fetch_ok = fetch_body(rec, sect, data, TRUE); + } else if (strcasecmp(sect->section, "TEXT") == 0) { + fetch_ok = fetch_body(rec, sect, data, FALSE); + } else if (strncasecmp(sect->section, "HEADER", 6) == 0) { + fetch_ok = fetch_header(rec, sect, data); + } else if (*sect->section >= '0' && *sect->section <= '9') { + fetch_ok = fetch_part(rec, sect, data); + } else { + fetch_ok = FALSE; + } + + if (!fetch_ok) { + /* error */ + (void)io_buffer_send(data->outbuf, "{0}\r\n", 5); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-fetch.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,365 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "iobuffer.h" +#include "temp-string.h" +#include "rfc822-date.h" +#include "index-storage.h" +#include "index-fetch.h" +#include "mail-messageset.h" +#include "imap-util.h" +#include "imap-message-cache.h" +#include "imap-message-send.h" + +#include <unistd.h> + +static void index_fetch_body(MailIndexRecord *rec, FetchData *data) +{ + const char *body; + + body = imap_msgcache_get(data->cache, rec->uid, IMAP_CACHE_BODY); + if (body != NULL) + t_string_printfa(data->str, " BODY %s", body); + else { + i_error("Couldn't generate BODY for UID %u (index %s)", + rec->uid, data->index->filepath); + } +} + +static void index_fetch_bodystructure(MailIndexRecord *rec, FetchData *data) +{ + const char *bodystructure; + + bodystructure = imap_msgcache_get(data->cache, rec->uid, + IMAP_CACHE_BODYSTRUCTURE); + if (bodystructure != NULL) { + t_string_printfa(data->str, " BODYSTRUCTURE %s", + bodystructure); + } else { + i_error("Couldn't generate BODYSTRUCTURE for UID %u (index %s)", + rec->uid, data->index->filepath); + } +} + +static void index_fetch_envelope(MailIndexRecord *rec, FetchData *data) +{ + const char *envelope; + + envelope = imap_msgcache_get(data->cache, rec->uid, + IMAP_CACHE_ENVELOPE); + if (envelope != NULL) + t_string_printfa(data->str, " ENVELOPE (%s)", envelope); + else { + i_error("Couldn't generate ENVELOPE for UID %u (index %s)", + rec->uid, data->index->filepath); + } +} + +static void index_fetch_rfc822_size(MailIndexRecord *rec, FetchData *data) +{ + t_string_printfa(data->str, " RFC822.SIZE %lu", + (unsigned long) rec->full_virtual_size); +} + +static void index_fetch_flags(MailIndexRecord *rec, FetchData *data) +{ + MailFlags flags; + + flags = rec->msg_flags; + if (rec->uid >= data->index->first_recent_uid) + flags |= MAIL_RECENT; + if (data->update_seen) + flags |= MAIL_SEEN; + + t_string_printfa(data->str, " FLAGS (%s)", + imap_write_flags(flags, data->custom_flags)); +} + +static void index_fetch_internaldate(MailIndexRecord *rec, FetchData *data) +{ + t_string_printfa(data->str, " INTERNALDATE \"%s\"", + rfc822_to_date(rec->internal_date)); +} + +static void index_fetch_uid(MailIndexRecord *rec, FetchData *data) +{ + t_string_printfa(data->str, " UID %u", rec->uid); +} + +static void index_fetch_rfc822(MailIndexRecord *rec, FetchData *data) +{ + MessageSize hdr_size, body_size; + const char *msg, *str; + int fd; + + if (!imap_msgcache_get_rfc822(data->cache, rec->uid, + &hdr_size, &body_size, &msg, &fd)) { + i_error("Couldn't get RFC822 for UID %u (index %s)", + rec->uid, data->index->filepath); + return; + } + + str = t_strdup_printf(" RFC822 {%lu}\r\n", + (unsigned long) (hdr_size.virtual_size + + body_size.virtual_size)); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + + body_size.physical_size += hdr_size.physical_size; + body_size.virtual_size += hdr_size.virtual_size; + (void)imap_message_send(data->outbuf, msg, fd, &body_size, 0, 0); +} + +static void index_fetch_rfc822_header(MailIndexRecord *rec, FetchData *data) +{ + MessageSize hdr_size; + const char *msg, *str; + int fd; + + if (!imap_msgcache_get_rfc822(data->cache, rec->uid, + &hdr_size, NULL, &msg, &fd)) { + i_error("Couldn't get RFC822.HEADER for UID %u (index %s)", + rec->uid, data->index->filepath); + return; + } + + str = t_strdup_printf(" RFC822.HEADER {%lu}\r\n", + (unsigned long) hdr_size.virtual_size); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + (void)imap_message_send(data->outbuf, msg, fd, &hdr_size, 0, 0); +} + +static void index_fetch_rfc822_text(MailIndexRecord *rec, FetchData *data) +{ + MessageSize body_size; + const char *msg, *str; + int fd; + + if (!imap_msgcache_get_rfc822(data->cache, rec->uid, + NULL, &body_size, &msg, &fd)) { + i_error("Couldn't get RFC822.TEXT for UID %u (index %s)", + rec->uid, data->index->filepath); + return; + } + + str = t_strdup_printf(" RFC822.TEXT {%lu}\r\n", + (unsigned long) body_size.virtual_size); + (void)io_buffer_send(data->outbuf, str, strlen(str)); + (void)imap_message_send(data->outbuf, msg, fd, &body_size, 0, 0); +} + +static ImapCacheField index_get_cache(MailFetchData *fetch_data) +{ + MailFetchBodyData *sect; + ImapCacheField field; + + field = 0; + if (fetch_data->body) + field |= IMAP_CACHE_BODY; + if (fetch_data->bodystructure) + field |= IMAP_CACHE_BODYSTRUCTURE; + if (fetch_data->envelope) + field |= IMAP_CACHE_ENVELOPE; + + if (fetch_data->rfc822_size) { + field |= IMAP_CACHE_MESSAGE_HDR_SIZE | + IMAP_CACHE_MESSAGE_BODY_SIZE; + } + if (fetch_data->rfc822) { + field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE | + IMAP_CACHE_MESSAGE_BODY_SIZE; + } + if (fetch_data->rfc822_header) + field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_HDR_SIZE; + if (fetch_data->rfc822_text) + field |= IMAP_CACHE_MESSAGE_OPEN | IMAP_CACHE_MESSAGE_BODY_SIZE; + + /* check what body[] sections want */ + sect = fetch_data->body_sections; + for (; sect != NULL; sect = sect->next) + field |= index_fetch_body_get_cache(sect->section); + return field; +} + +static int index_cache_message(MailIndexRecord *rec, FetchData *data, + ImapCacheField field) +{ + off_t offset; + size_t size; + int fd; + + fd = data->index->open_mail(data->index, rec, &offset, &size); + if (fd == -1) { + i_error("Couldn't open message UID %u (index %s)", + rec->uid, data->index->filepath); + return FALSE; + } + + if (MSG_HAS_VALID_CRLF_DATA(rec)) { + imap_msgcache_message(data->cache, rec->uid, fd, offset, size, + rec->full_virtual_size, + rec->header_size, rec->body_size, field); + } else { + imap_msgcache_message(data->cache, rec->uid, fd, offset, size, + rec->full_virtual_size, 0, 0, field); + } + return TRUE; +} + +static void index_cache_mail(FetchData *data, MailIndexRecord *rec) +{ + ImapCacheField fields; + const char *value; + + fields = index_get_cache(data->fetch_data); + if (imap_msgcache_is_cached(data->cache, rec->uid, fields)) + return; + + /* see if we can get some of the values from our index */ + if (fields & IMAP_CACHE_BODY) { + value = data->index->lookup_field(data->index, rec, + FIELD_TYPE_BODY); + imap_msgcache_set(data->cache, rec->uid, + IMAP_CACHE_BODY, value); + } + + if (fields & IMAP_CACHE_BODYSTRUCTURE) { + value = data->index->lookup_field(data->index, rec, + FIELD_TYPE_BODYSTRUCTURE); + imap_msgcache_set(data->cache, rec->uid, + IMAP_CACHE_BODYSTRUCTURE, value); + } + + if (fields & IMAP_CACHE_ENVELOPE) { + value = data->index->lookup_field(data->index, rec, + FIELD_TYPE_ENVELOPE); + imap_msgcache_set(data->cache, rec->uid, + IMAP_CACHE_ENVELOPE, value); + } + + /* if we still don't have everything, open the message and + cache the needed fields */ + if (fields != 0 && + !imap_msgcache_is_cached(data->cache, rec->uid, fields)) + index_cache_message(rec, data, fields); +} + +static int index_fetch_mail(MailIndex *index __attr_unused__, + MailIndexRecord *rec, unsigned int seq, + void *user_data) +{ + FetchData *data = user_data; + MailFetchBodyData *sect; + + data->str = t_string_new(2048); + + t_string_printfa(data->str, "* %u FETCH (", seq); + (void)io_buffer_send(data->outbuf, data->str->str, data->str->len); + t_string_truncate(data->str, 0); + + /* first see what we need to do. this way we don't first do some + light parsing and later notice that we need to do heavier parsing + anyway */ + index_cache_mail(data, rec); + + if (data->fetch_data->uid) + index_fetch_uid(rec, data); + if (data->fetch_data->flags) + index_fetch_flags(rec, data); + if (data->fetch_data->internaldate) + index_fetch_internaldate(rec, data); + + if (data->fetch_data->body) + index_fetch_body(rec, data); + if (data->fetch_data->bodystructure) + index_fetch_bodystructure(rec, data); + if (data->fetch_data->envelope) + index_fetch_envelope(rec, data); + if (data->fetch_data->rfc822_size) + index_fetch_rfc822_size(rec, data); + + /* send the data written into temp string, skipping the initial space */ + if (data->str->len > 0) { + (void)io_buffer_send(data->outbuf, data->str->str+1, + data->str->len-1); + } + + /* large data */ + if (data->fetch_data->rfc822) + index_fetch_rfc822(rec, data); + if (data->fetch_data->rfc822_text) + index_fetch_rfc822_text(rec, data); + if (data->fetch_data->rfc822_header) + index_fetch_rfc822_header(rec, data); + + sect = data->fetch_data->body_sections; + for (; sect != NULL; sect = sect->next) + index_fetch_body_section(rec, seq, sect, data); + + (void)io_buffer_send(data->outbuf, ")\r\n", 3); + + return TRUE; +} + +int index_storage_fetch(Mailbox *box, MailFetchData *fetch_data, + IOBuffer *outbuf, int *all_found) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + FetchData data; + MailFetchBodyData *sect; + int ret; + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED)) + return mail_storage_set_index_error(ibox); + + memset(&data, 0, sizeof(data)); + data.box = box; + data.cache = ibox->cache; + data.index = ibox->index; + data.custom_flags = flags_file_list_get(ibox->flagsfile); + + data.fetch_data = fetch_data; + data.outbuf = outbuf; + + /* If we have any BODY[..] sections, \Seen flag is added for + all messages */ + sect = data.fetch_data->body_sections; + for (; sect != NULL; sect = sect->next) { + if (!sect->peek) { + data.update_seen = TRUE; + break; + } + } + + if (fetch_data->uidset) { + ret = mail_index_uidset_foreach(ibox->index, + fetch_data->messageset, + ibox->synced_messages_count, + index_fetch_mail, &data); + } else { + ret = mail_index_messageset_foreach(ibox->index, + fetch_data->messageset, + ibox->synced_messages_count, + index_fetch_mail, &data); + } + + flags_file_list_unref(ibox->flagsfile); + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || ret == -1) + return mail_storage_set_index_error(ibox); + + if (all_found != NULL) + *all_found = ret == 1; + + if (ret >= 1 && data.update_seen && !box->readonly) { + /* BODY[..] was fetched, set \Seen flag for all messages. + This needs to be done separately because we need exclusive + lock for it */ + if (!index_storage_update_flags(box, fetch_data->messageset, + fetch_data->uidset, + MAIL_SEEN, NULL, MODIFY_ADD, + NULL, NULL, NULL)) + return FALSE; + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-fetch.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,20 @@ +#ifndef __INDEX_FETCH_H +#define __INDEX_FETCH_H + +typedef struct { + Mailbox *box; + ImapMessageCache *cache; + MailIndex *index; + const char **custom_flags; + + MailFetchData *fetch_data; + IOBuffer *outbuf; + TempString *str; + int update_seen; +} FetchData; + +ImapCacheField index_fetch_body_get_cache(const char *section); +void index_fetch_body_section(MailIndexRecord *rec, unsigned int seq, + MailFetchBodyData *sect, FetchData *data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-save.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,74 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "iobuffer.h" +#include "index-storage.h" + +#include <stdlib.h> +#include <unistd.h> + +static int write_with_crlf(int fd, const unsigned char *data, + unsigned int size, unsigned int *last_cr) +{ + int i, cr; + + i_assert(size < INT_MAX); + + cr = *last_cr ? -1 : -2; + for (i = 0; i < (int)size; i++) { + if (data[i] == '\r') + cr = i; + else if (data[i] == '\n' && cr != i-1) { + /* missing CR */ + if (write(fd, data, (size_t) i) != i) + return FALSE; + if (write(fd, "\r", 1) != 1) + return FALSE; + + /* skip the data so far. \n is left into buffer and + we'll continue from the next character. */ + data += i; + size -= i; + i = 0; cr = -2; + } + } + + return write(fd, data, size) == i; +} + +int index_storage_save_into_fd(MailStorage *storage, int fd, const char *path, + IOBuffer *buf, size_t data_size) +{ + unsigned char *data; + unsigned int size, last_cr; + int ret; + + last_cr = FALSE; + + while ((ret = io_buffer_read(buf)) != 0) { + if (ret == -1) { + mail_storage_set_critical(storage, + "Error reading mail: %m"); + return FALSE; + } + + /* -2 = buffer full, ignore it since we're just emptying it.. */ + + data = io_buffer_get_data(buf, &size); + if (size == 0) + continue; + + if (size > data_size) + size = data_size; + buf->pos += size; + data_size -= size; + + if (write_with_crlf(fd, data, size, &last_cr)) { + mail_storage_set_critical(storage, "write() failed " + "for file %s: %m", path); + return FALSE; + } + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-search.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,635 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "iobuffer.h" +#include "mmap-util.h" +#include "rfc822-date.h" +#include "rfc822-tokenize.h" +#include "index-storage.h" +#include "mail-search.h" + +#include <stdlib.h> +#include <ctype.h> + +#define ARG_SET_RESULT(arg, res) \ + STMT_START { \ + (arg)->result = !(arg)->not ? (res) : -(res); \ + } STMT_END + +typedef struct { + IndexMailbox *ibox; + MailIndexRecord *rec; + unsigned int seq; +} SearchIndexData; + +typedef struct { + MailSearchArg *args; + int custom_header; + + const char *name, *value; + unsigned int name_len, value_len; +} SearchHeaderData; + +typedef struct { + MailSearchArg *args; + const char *msg; + size_t size; + int last_block; +} SearchTextData; + +/* truncate timestamp to day */ +static time_t timestamp_trunc(time_t t) +{ + struct tm *tm; + + tm = localtime(&t); + tm->tm_hour = tm->tm_min = tm->tm_sec = 0; + + return mktime(tm); +} + +static int msgset_contains(const char *set, unsigned int match_num, + unsigned int max_num) +{ + unsigned int num, num2; + + while (*set != '\0') { + if (*set == '*') { + set++; + num = max_num; + } else { + num = 0; + while (*set >= '0' && *set <= '9') { + num = num*10 + (*set-'0'); + set++; + } + } + + if (*set == ',' || *set == '\0') { + if (num == match_num) + return TRUE; + if (*set == '\0') + return FALSE; + } else if (*set == ':') { + if (*set == '*') { + set++; + + if (match_num >= num && num <= max_num) + return TRUE; + } else { + num2 = 0; + while (*set >= '0' && *set <= '9') { + num2 = num2*10 + (*set-'0'); + set++; + } + + if (match_num >= num && match_num <= num2) + return TRUE; + } + + if (*set != ',') + return FALSE; + } + + set++; + } + + return FALSE; +} + +/* Returns >0 = matched, 0 = not matched, -1 = unknown */ +static int search_arg_match_index(IndexMailbox *ibox, MailIndexRecord *rec, + unsigned int seq, MailSearchArgType type, + const char *value) +{ + time_t t; + + switch (type) { + case SEARCH_ALL: + return TRUE; + case SEARCH_SET: + return msgset_contains(value, seq, ibox->synced_messages_count); + case SEARCH_UID: + return msgset_contains(value, rec->uid, + ibox->synced_messages_count); + + /* flags */ + case SEARCH_ANSWERED: + return rec->msg_flags & MAIL_ANSWERED; + case SEARCH_DELETED: + return rec->msg_flags & MAIL_DELETED; + case SEARCH_DRAFT: + return rec->msg_flags & MAIL_DRAFT; + case SEARCH_FLAGGED: + return rec->msg_flags & MAIL_FLAGGED; + case SEARCH_SEEN: + return rec->msg_flags & MAIL_SEEN; + case SEARCH_RECENT: + return rec->uid >= ibox->index->first_recent_uid; + case SEARCH_KEYWORD: + return FALSE; + + /* dates */ + case SEARCH_BEFORE: + if (!rfc822_parse_date(value, &t)) + return FALSE; + return rec->internal_date < timestamp_trunc(t); + case SEARCH_ON: + if (!rfc822_parse_date(value, &t)) + return FALSE; + t = timestamp_trunc(t); + return rec->internal_date >= t && + rec->internal_date < t + 3600*24; + case SEARCH_SINCE: + if (!rfc822_parse_date(value, &t)) + return FALSE; + return rec->internal_date >= timestamp_trunc(t); + + case SEARCH_SENTBEFORE: + if (!rfc822_parse_date(value, &t)) + return FALSE; + return rec->sent_date < timestamp_trunc(t); + case SEARCH_SENTON: + if (!rfc822_parse_date(value, &t)) + return FALSE; + t = timestamp_trunc(t); + return rec->sent_date >= t && + rec->sent_date < t + 3600*24; + case SEARCH_SENTSINCE: + if (!rfc822_parse_date(value, &t)) + return FALSE; + return rec->sent_date >= timestamp_trunc(t); + + /* sizes */ + case SEARCH_SMALLER: + return rec->full_virtual_size < strtoul(value, NULL, 10); + case SEARCH_LARGER: + return rec->full_virtual_size > strtoul(value, NULL, 10); + + default: + return -1; + } +} + +static void search_index_arg(MailSearchArg *arg, void *user_data) +{ + SearchIndexData *data = user_data; + + switch (search_arg_match_index(data->ibox, data->rec, data->seq, + arg->type, arg->value.str)) { + case -1: + /* unknown */ + break; + case 0: + ARG_SET_RESULT(arg, -1); + break; + default: + ARG_SET_RESULT(arg, 1); + break; + } +} + +static int match_field(MailIndex *index, MailIndexRecord *rec, + MailField field, const char *value) +{ + const char *field_value; + unsigned int i, value_len; + + field_value = index->lookup_field(index, rec, field); + if (field_value == NULL) + return -1; + + /* note: value is already uppercased */ + value_len = strlen(value); + for (i = 0; field_value[i] != '\0'; i++) { + if (value[0] == i_toupper(field_value[i]) && + strncasecmp(value, field_value+i, value_len) == 0) + return 1; + } + + return 0; +} + +/* Returns >0 = matched, 0 = not matched, -1 = unknown */ +static int search_arg_match_cached(MailIndex *index, MailIndexRecord *rec, + MailSearchArgType type, const char *value) +{ + switch (type) { + case SEARCH_FROM: + return match_field(index, rec, FIELD_TYPE_FROM, value); + case SEARCH_TO: + return match_field(index, rec, FIELD_TYPE_TO, value); + case SEARCH_CC: + return match_field(index, rec, FIELD_TYPE_CC, value); + case SEARCH_BCC: + return match_field(index, rec, FIELD_TYPE_BCC, value); + case SEARCH_SUBJECT: + return match_field(index, rec, FIELD_TYPE_SUBJECT, value); + default: + return -1; + } +} + +static void search_cached_arg(MailSearchArg *arg, void *user_data) +{ + SearchIndexData *data = user_data; + + switch (search_arg_match_cached(data->ibox->index, data->rec, + arg->type, arg->value.str)) { + case -1: + /* unknown */ + break; + case 0: + ARG_SET_RESULT(arg, -1); + break; + default: + ARG_SET_RESULT(arg, 1); + break; + } +} + +/* needle must be uppercased */ +static int header_value_match(const char *haystack, unsigned int haystack_len, + const char *needle) +{ + const char *n; + unsigned int i, j, needle_len, max; + + if (*needle == '\0') + return TRUE; + + needle_len = strlen(needle); + if (haystack_len < needle_len) + return FALSE; + + max = haystack_len - needle_len; + for (i = 0; i <= max; i++) { + if (needle[0] != i_toupper(haystack[i])) + continue; + + for (j = i, n = needle; j < haystack_len; j++) { + if (haystack[j] == '\r') { + if (j+1 != haystack_len) + j++; + } + + if (haystack[j] == '\n' && j+1 < haystack_len && + IS_LWSP(haystack[j+1])) { + /* long header continuation */ + j++; + } + + if (*n++ != i_toupper(haystack[j])) + break; + + if (*n == '\0') + return 1; + } + } + + return -1; +} + +static void search_header_arg(MailSearchArg *arg, void *user_data) +{ + SearchHeaderData *data = user_data; + const char *value; + unsigned int len; + int ret; + + /* first check that the field name matches to argument. */ + switch (arg->type) { + case SEARCH_FROM: + if (data->name_len != 4 || + strncasecmp(data->name, "From", 4) != 0) + return; + value = arg->value.str; + break; + case SEARCH_TO: + if (data->name_len != 2 || + strncasecmp(data->name, "To", 2) != 0) + return; + value = arg->value.str; + break; + case SEARCH_CC: + if (data->name_len != 2 || + strncasecmp(data->name, "Cc", 2) != 0) + return; + value = arg->value.str; + break; + case SEARCH_BCC: + if (data->name_len != 3 || + strncasecmp(data->name, "Bcc", 3) != 0) + return; + value = arg->value.str; + break; + case SEARCH_SUBJECT: + if (data->name_len != 7 || + strncasecmp(data->name, "Subject", 7) != 0) + return; + value = arg->value.str; + break; + case SEARCH_HEADER: + data->custom_header = TRUE; + + len = strlen(arg->value.str); + if (data->name_len != len || + strncasecmp(data->name, arg->value.str, len) != 0) + return; + + value = arg->hdr_value; + default: + return; + } + + /* then check if the value matches */ + ret = header_value_match(data->value, data->value_len, value); + ARG_SET_RESULT(arg, ret); +} + +static void search_header(MessagePart *part __attr_unused__, + const char *name, unsigned int name_len, + const char *value, unsigned int value_len, + void *user_data) +{ + SearchHeaderData *data = user_data; + + if (data->custom_header || + (name_len == 4 && strncasecmp(name, "From", 4) == 0) || + (name_len == 2 && strncasecmp(name, "To", 2) == 0) || + (name_len == 2 && strncasecmp(name, "Cc", 2) == 0) || + (name_len == 3 && strncasecmp(name, "Bcc", 3) == 0) || + (name_len == 7 && strncasecmp(name, "Subject", 7) == 0)) { + data->name = name; + data->value = value; + data->name_len = name_len; + data->value_len = value_len; + + data->custom_header = FALSE; + mail_search_args_foreach(data->args, search_header_arg, data); + } +} + +static void search_text(MailSearchArg *arg, SearchTextData *data) +{ + const char *p; + unsigned int i, len, max; + + if (arg->result != 0) + return; + + len = strlen(arg->value.str); + max = data->size-len; + for (i = 0, p = data->msg; i <= max; i++, p++) { + if (i_toupper(*p) == arg->value.str[0] && + strncasecmp(p, arg->value.str, len) == 0) { + /* match */ + ARG_SET_RESULT(arg, 1); + return; + } + } + + if (data->last_block) + ARG_SET_RESULT(arg, -1); +} + +static void search_text_header(MailSearchArg *arg, void *user_data) +{ + SearchTextData *data = user_data; + + if (arg->type == SEARCH_TEXT) + search_text(arg, data); +} + +static void search_text_body(MailSearchArg *arg, void *user_data) +{ + SearchTextData *data = user_data; + + if (arg->type == SEARCH_TEXT || arg->type == SEARCH_BODY) + search_text(arg, data); +} + +static int search_arg_match_text(IndexMailbox *ibox, MailIndexRecord *rec, + MailSearchArg *args) +{ + const char *msg; + void *mmap_base; + off_t offset; + size_t size, mmap_length; + int fd, failed; + int have_headers, have_body, have_text; + + /* first check what we need to use */ + mail_search_args_analyze(args, &have_headers, &have_body, &have_text); + if (!have_headers && !have_body && !have_text) + return TRUE; + + fd = ibox->index->open_mail(ibox->index, rec, &offset, &size); + if (fd == -1) + return FALSE; + + mmap_base = mmap_aligned(fd, PROT_READ, offset, size, + (void **) &msg, &mmap_length); + if (mmap_base == MAP_FAILED) { + failed = TRUE; + mail_storage_set_critical(ibox->box.storage, "mmap() failed " + "for msg %u: %m", rec->uid); + } else { + failed = FALSE; + (void)madvise(mmap_base, mmap_length, MADV_SEQUENTIAL); + + if (have_headers) { + SearchHeaderData data; + + memset(&data, 0, sizeof(data)); + + /* header checks */ + data.custom_header = TRUE; + data.args = args; + message_parse_header(NULL, msg, size, NULL, + search_header, &data); + } + + if (have_text) { + /* first search text from header*/ + SearchTextData data; + + data.args = args; + data.msg = msg; + data.size = rec->header_size; + data.last_block = FALSE; + + mail_search_args_foreach(args, search_text_header, + &data); + } + + if (have_text || have_body) { + /* search text from body */ + SearchTextData data; + + /* FIXME: we should check this in blocks, so the whole + message doesn't need to be in memory */ + data.args = args; + data.msg = msg + rec->header_size; + data.size = size - rec->header_size; + data.last_block = TRUE; + + mail_search_args_foreach(args, search_text_body, &data); + } + } + + (void)munmap(mmap_base, mmap_length); + (void)close(fd); + return !failed; +} + +static void seq_update(const char *set, unsigned int *first_seq, + unsigned int *last_seq, unsigned int max_value) +{ + unsigned int seq; + + while (*set != '\0') { + if (*set == '*') { + seq = max_value; + set++; + } else { + seq = 0; + while (*set >= '0' && *set <= '9') { + seq = seq*10 + (*set-'0'); + set++; + } + } + + if (seq != 0) { + if (*first_seq == 0 || seq < *first_seq) + *first_seq = seq; + if (*last_seq == 0 || seq > *last_seq) + *last_seq = seq; + } + + seq++; + } +} + +static void search_get_sequid(IndexMailbox *ibox, MailSearchArg *args, + unsigned int *first_seq, unsigned int *last_seq, + unsigned int *first_uid, unsigned int *last_uid) +{ + for (; args != NULL; args = args->next) { + if (args->type == SEARCH_OR || args->type == SEARCH_SUB) { + search_get_sequid(ibox, args->value.subargs, + first_seq, last_seq, + first_uid, last_uid); + } if (args->type == SEARCH_SET) { + seq_update(args->value.str, first_seq, last_seq, + ibox->synced_messages_count); + } else if (args->type == SEARCH_UID) { + seq_update(args->value.str, first_uid, last_uid, + ibox->index->header->next_uid-1); + } else if (args->type == SEARCH_ALL) { + /* go through everything */ + *first_seq = 1; + *last_seq = ibox->synced_messages_count; + return; + } + } +} + +static void search_get_sequences(IndexMailbox *ibox, MailSearchArg *args, + unsigned int *first_seq, + unsigned int *last_seq) +{ + MailIndexRecord *rec; + unsigned int seq, first_uid, last_uid; + + *first_seq = *last_seq = 0; + first_uid = last_uid = 0; + + search_get_sequid(ibox, args, first_seq, last_seq, + &first_uid, &last_uid); + + if (first_uid != 0 && (*first_seq != 1 || + *last_seq != ibox->synced_messages_count)) { + /* UIDs were used - see if they affect the sequences */ + rec = ibox->index->lookup_uid_range(ibox->index, + first_uid, last_uid); + if (rec != NULL) { + /* update lower UID */ + seq = ibox->index->get_sequence(ibox->index, rec); + if (seq < *first_seq) + *first_seq = seq; + + /* update higher UID .. except we don't really + know it and it'd be uselessly slow to find it. + use a kludgy method which might limit the + sequences. */ + seq += last_uid-first_uid; + if (seq >= ibox->synced_messages_count) + seq = ibox->synced_messages_count; + + if (seq > *last_seq) + *last_seq = seq; + } + } + + if (*first_seq == 0) + *first_seq = 1; + if (*last_seq == 0) + *last_seq = ibox->synced_messages_count; +} + +static void search_messages(IndexMailbox *ibox, MailSearchArg *args, + IOBuffer *outbuf, int uid_result) +{ + SearchIndexData data; + MailIndexRecord *rec; + unsigned int first_seq, last_seq, seq; + char num[MAX_INT_STRLEN+10]; + + /* see if we can limit the records we look at */ + search_get_sequences(ibox, args, &first_seq, &last_seq); + + data.ibox = ibox; + + rec = ibox->index->lookup(ibox->index, first_seq); + for (seq = first_seq; rec != NULL && seq <= last_seq; seq++) { + data.rec = rec; + data.seq = seq; + + mail_search_args_reset(args); + + mail_search_args_foreach(args, search_index_arg, &data); + mail_search_args_foreach(args, search_cached_arg, &data); + + if (search_arg_match_text(ibox, rec, args) && + args->result == 1) { + i_snprintf(num, sizeof(num), " %u", + uid_result ? rec->uid : seq); + io_buffer_send(outbuf, num, strlen(num)); + } + rec = ibox->index->next(ibox->index, rec); + } +} + +int index_storage_search(Mailbox *box, MailSearchArg *args, + IOBuffer *outbuf, int uid_result) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + int failed; + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED)) + failed = TRUE; + else { + io_buffer_send(outbuf, "* SEARCH", 8); + + search_messages(ibox, args, outbuf, uid_result); + failed = !ibox->index->set_lock(ibox->index, + MAIL_LOCK_UNLOCK); + io_buffer_send(outbuf, "\r\n", 2); + } + + if (failed) + (void)mail_storage_set_index_error(ibox); + + return !failed; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-status.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,109 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "index-storage.h" + +static unsigned int get_recent_count(MailIndex *index) +{ + MailIndexHeader *hdr; + MailIndexRecord *rec; + + hdr = mail_index_get_header(index); + if (index->first_recent_uid <= 1) { + /* all are recent */ + return hdr->messages_count; + } + + /* get the first recent message */ + rec = index->lookup_uid_range(index, index->first_recent_uid, + hdr->next_uid - 1); + if (rec == NULL) + return 0; + + /* now we know the record, but we'd still need to know how many + messages there's after this. there's two way to do this - + get the sequence number thus far (fast, unless there's deleted + messages) or just start reading messages forward until we're at + the end (fast assuming there's only a few recent messages). + it's a bit easier to use the first method and often it should be + faster too.. */ + return hdr->messages_count - index->get_sequence(index, rec) + 1; +} + +static unsigned int get_first_unseen_seq(MailIndex *index) +{ + MailIndexHeader *hdr; + MailIndexRecord *rec; + unsigned int seq, lowwater_uid; + + hdr = mail_index_get_header(index); + if (hdr->seen_messages_count == hdr->messages_count) { + /* no unseen messages */ + return 0; + } + + lowwater_uid = hdr->first_unseen_uid_lowwater; + if (lowwater_uid != 0) { + /* begin scanning from the low water mark */ + rec = index->lookup_uid_range(index, lowwater_uid, + hdr->next_uid - 1); + if (rec == NULL) { + i_warning("index header's seen_messages_count or " + "first_unseen_uid_lowwater is invalid."); + INDEX_MARK_CORRUPTED(index); + return 0; + } else { + seq = index->get_sequence(index, rec); + } + } else { + /* begin scanning from the beginning */ + rec = index->lookup(index, 1); + seq = 1; + } + + while (rec != NULL && (rec->msg_flags & MAIL_SEEN)) { + rec = index->next(index, rec); + seq++; + } + + if (rec != NULL && rec->uid != lowwater_uid) { + /* update the low water mark if we can get exclusive + lock immediately. */ + if (index->try_lock(index, MAIL_LOCK_EXCLUSIVE)) + hdr->first_unseen_uid_lowwater = rec->uid; + } + + return rec == NULL ? 0 : seq; +} + +int index_storage_get_status(Mailbox *box, MailboxStatusItems items, + MailboxStatus *status) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + MailIndexHeader *hdr; + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_SHARED)) + return mail_storage_set_index_error(ibox); + + /* we can get most of the status items without any trouble */ + hdr = mail_index_get_header(ibox->index); + status->messages = hdr->messages_count; + status->unseen = hdr->messages_count - hdr->seen_messages_count; + status->uidvalidity = hdr->uid_validity; + status->uidnext = hdr->next_uid; + + if (items & STATUS_FIRST_UNSEEN_SEQ) { + status->first_unseen_seq = + get_first_unseen_seq(ibox->index); + } + + if (items & STATUS_RECENT) + status->recent = get_recent_count(ibox->index); + + /* STATUS sends EXISTS, so we've synced it */ + ibox->synced_messages_count = hdr->messages_count; + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK)) + return mail_storage_set_index_error(ibox); + return hdr != NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-storage.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,97 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mail-index.h" +#include "index-storage.h" + +IndexMailbox *index_storage_init(MailStorage *storage, Mailbox *box, + MailIndex *index, const char *name, + int readonly) +{ + IndexMailbox *ibox; + FlagsFile *flagsfile; + const char *path, *error; + + i_assert(name != NULL); + + /* open the index first */ + if (!index->open_or_create(index, !readonly)) { + error = index->get_last_error(index); + if (error == NULL) + error = "(maildir_open)"; + mail_storage_set_error(storage, "%s", error); + + index->free(index); + return NULL; + } + + /* then flags file */ + path = t_strconcat(index->dir, "/", FLAGS_FILE_NAME, NULL); + flagsfile = flags_file_open_or_create(storage, path); + if (flagsfile == NULL) { + index->free(index); + return NULL; + } + + ibox = i_new(IndexMailbox, 1); + ibox->box = *box; + + ibox->box.storage = storage; + ibox->box.name = i_strdup(name); + ibox->box.readonly = readonly; + + ibox->index = index; + ibox->flagsfile = flagsfile; + ibox->cache = imap_msgcache_alloc(); + + return ibox; +} + +void index_storage_close(Mailbox *box) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + + flags_file_destroy(ibox->flagsfile); + imap_msgcache_free(ibox->cache); + ibox->index->free(ibox->index); + i_free(box->name); + i_free(box); +} + +int mail_storage_set_index_error(IndexMailbox *ibox) +{ + const char *error; + + error = ibox->index->get_last_error(ibox->index); + if (error == NULL) + error = "(no error message)"; + + ibox->box.inconsistent = + ibox->index->is_inconsistency_error(ibox->index); + mail_storage_set_error(ibox->box.storage, "%s", error); + return FALSE; +} + +static MailFlags get_used_flags(void *user_data) +{ + IndexMailbox *ibox = user_data; + MailIndexRecord *rec; + MailFlags used_flags; + + used_flags = 0; + + rec = ibox->index->lookup(ibox->index, 1); + while (rec != NULL) { + used_flags |= rec->msg_flags; + rec = ibox->index->next(ibox->index, rec); + } + + return used_flags; +} + +int index_mailbox_fix_custom_flags(IndexMailbox *ibox, MailFlags *flags, + const char *custom_flags[]) +{ + return flags_file_fix_custom_flags(ibox->flagsfile, flags, + custom_flags, get_used_flags, ibox); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-storage.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,61 @@ +#ifndef __INDEX_STORAGE_H +#define __INDEX_STORAGE_H + +#include "mail-storage.h" +#include "mail-index.h" +#include "imap-message-cache.h" +#include "flags-file/flags-file.h" + +typedef struct _IndexMailbox IndexMailbox; + +struct _IndexMailbox { + Mailbox box; + + /* expunge messages marked as deleted, requires index to be + exclusively locked */ + int (*expunge_locked)(IndexMailbox *ibox, + MailExpungeFunc expunge_func, void *user_data); + + MailIndex *index; + FlagsFile *flagsfile; + ImapMessageCache *cache; + unsigned int synced_messages_count; +}; + +IndexMailbox *index_storage_init(MailStorage *storage, Mailbox *box, + MailIndex *index, const char *name, + int readonly); +void index_storage_close(Mailbox *box); + +int mail_storage_set_index_error(IndexMailbox *ibox); + +int index_mailbox_fix_custom_flags(IndexMailbox *ibox, MailFlags *flags, + const char *custom_flags[]); + +MailIndexRecord *index_expunge_seek_first(IndexMailbox *ibox, + unsigned int *seq); + +int index_storage_save_into_fd(MailStorage *storage, int fd, const char *path, + IOBuffer *buf, size_t data_size); + +/* Mailbox methods: */ +int index_storage_copy(Mailbox *box, Mailbox *destbox, + const char *messageset, int uidset); +int index_storage_expunge(Mailbox *box); +int index_storage_get_status(Mailbox *box, MailboxStatusItems items, + MailboxStatus *status); +int index_storage_sync(Mailbox *box, unsigned int *messages, int expunge, + MailExpungeFunc expunge_func, + MailFlagUpdateFunc flag_func, + void *user_data); +int index_storage_update_flags(Mailbox *box, const char *messageset, int uidset, + MailFlags flags, const char *custom_flags[], + ModifyType modify_type, + MailFlagUpdateFunc func, void *user_data, + int *all_found); +int index_storage_fetch(Mailbox *box, MailFetchData *fetch_data, + IOBuffer *outbuf, int *all_found); +int index_storage_search(Mailbox *box, MailSearchArg *args, + IOBuffer *outbuf, int uid_result); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-sync.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,87 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "index-storage.h" +#include "mail-modifylog.h" + +int index_storage_sync(Mailbox *box, unsigned int *messages, int expunge, + MailExpungeFunc expunge_func, + MailFlagUpdateFunc flag_func, + void *user_data) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + ModifyLogRecord *log; + MailIndexRecord *rec; + MailFlags flags; + const char **custom_flags; + unsigned int count; + int failed; + + if (expunge && box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return FALSE; + } + + *messages = 0; + + if (!ibox->index->set_lock(ibox->index, expunge ? + MAIL_LOCK_EXCLUSIVE : MAIL_LOCK_SHARED)) + return mail_storage_set_index_error(ibox); + + /* show the log */ + log = mail_modifylog_get_nonsynced(ibox->index->modifylog, &count); + if (log == NULL) + failed = TRUE; + + custom_flags = flags_file_list_get(ibox->flagsfile); + for (; count > 0; count--, log++) { + switch (log->type) { + case RECORD_TYPE_EXPUNGE: + if (expunge_func != NULL) { + expunge_func(box, log->seq, + log->uid, user_data); + } + break; + case RECORD_TYPE_FLAGS_CHANGED: + if (flag_func == NULL) + break; + + rec = ibox->index->lookup_uid_range(ibox->index, + log->uid, + log->uid); + if (rec != NULL) { + flags = rec->msg_flags; + if (rec->uid >= ibox->index->first_recent_uid) + flags |= MAIL_RECENT; + + flag_func(box, log->seq, log->uid, flags, + custom_flags, user_data); + } + break; + } + } + flags_file_list_unref(ibox->flagsfile); + + /* mark synced */ + failed = !mail_modifylog_mark_synced(ibox->index->modifylog); + + if (!failed && expunge) { + /* expunge messages */ + failed = !ibox->expunge_locked(ibox, expunge_func, user_data); + } + + /* get the messages count even if there was some failures. + also it must be done after expunging messages */ + count = ibox->index->get_header(ibox->index)->messages_count; + if (count != ibox->synced_messages_count) { + if (count > ibox->synced_messages_count) { + /* new messages in mailbox */ + *messages = count; + } + ibox->synced_messages_count = count; + } + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || failed) + return mail_storage_set_index_error(ibox); + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/index-update-flags.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,97 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "index-storage.h" +#include "mail-messageset.h" + +typedef struct { + Mailbox *box; + MailFlags flags; + FlagsFile *flagsfile; + ModifyType modify_type; + MailFlagUpdateFunc func; + void *user_data; +} UpdateData; + +static int update_func(MailIndex *index, MailIndexRecord *rec, + unsigned int seq, void *user_data) +{ + UpdateData *data = user_data; + MailFlags flags; + + switch (data->modify_type) { + case MODIFY_ADD: + flags = rec->msg_flags | data->flags; + break; + case MODIFY_REMOVE: + flags = rec->msg_flags & ~data->flags; + break; + case MODIFY_REPLACE: + flags = data->flags; + break; + default: + flags = 0; + i_assert(0); + } + + if (rec->uid >= index->first_recent_uid) + flags |= MAIL_RECENT; + + if (!index->update_flags(index, rec, seq, flags, FALSE)) + return FALSE; + + if (data->func != NULL) { + data->func(data->box, seq, rec->uid, flags, + flags_file_list_get(data->flagsfile), + data->user_data); + flags_file_list_unref(data->flagsfile); + } + return TRUE; +} + +int index_storage_update_flags(Mailbox *box, const char *messageset, int uidset, + MailFlags flags, const char *custom_flags[], + ModifyType modify_type, + MailFlagUpdateFunc func, void *user_data, + int *all_found) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + UpdateData data; + int ret; + + if (box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return FALSE; + } + + if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags)) + return mail_storage_set_index_error((IndexMailbox *) box); + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_EXCLUSIVE)) + return mail_storage_set_index_error(ibox); + + data.box = box; + data.flags = flags & ~MAIL_RECENT; /* \Recent can't be changed */ + data.flagsfile = ibox->flagsfile; + data.modify_type = modify_type; + data.func = func; + data.user_data = user_data; + + if (uidset) { + ret = mail_index_uidset_foreach(ibox->index, messageset, + ibox->synced_messages_count, + update_func, &data); + } else { + ret = mail_index_messageset_foreach(ibox->index, + messageset, + ibox->synced_messages_count, + update_func, &data); + } + + if (!ibox->index->set_lock(ibox->index, MAIL_LOCK_UNLOCK) || ret == -1) + return mail_storage_set_index_error(ibox); + + if (all_found != NULL) + *all_found = ret == 1; + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,20 @@ +noinst_LIBRARIES = libstorage_maildir.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-index/maildir \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index + +libstorage_maildir_a_SOURCES = \ + maildir-copy.c \ + maildir-expunge.c \ + maildir-list.c \ + maildir-save.c \ + maildir-storage.c + +noinst_HEADERS = \ + maildir-storage.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/maildir-copy.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,111 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mail-messageset.h" +#include "maildir-storage.h" + +#include <stdlib.h> +#include <unistd.h> + +typedef struct { + MailStorage *storage; + const char *dest_maildir; + int error; +} CopyHardData; + +static int copy_hard_func(MailIndex *index, MailIndexRecord *rec, + unsigned int seq __attr_unused__, void *user_data) +{ + CopyHardData *data = user_data; + const char *fname; + char src[1024], dest[1024]; + + /* link the file */ + fname = index->lookup_field(index, rec, FIELD_TYPE_LOCATION); + i_snprintf(src, sizeof(src), "%s/cur/%s", index->dir, fname); + i_snprintf(dest, sizeof(dest), "%s/new/%s", data->dest_maildir, fname); + + if (link(src, dest) == 0) + return TRUE; + else { + if (errno != EXDEV) { + mail_storage_set_critical(data->storage, "link(%s, %s) " + "failed: %m", src, dest); + data->error = TRUE; + } + return FALSE; + } +} + +static int maildir_copy_with_hardlinks(IndexMailbox *src, + IndexMailbox *dest, + const char *messageset, int uidset) +{ + CopyHardData data; + int ret; + + if (!src->index->set_lock(src->index, MAIL_LOCK_SHARED)) + return mail_storage_set_index_error(src); + if (!dest->index->set_lock(dest->index, MAIL_LOCK_EXCLUSIVE)) { + (void)src->index->set_lock(src->index, MAIL_LOCK_UNLOCK); + return mail_storage_set_index_error(dest); + } + + data.storage = src->box.storage; + data.dest_maildir = dest->index->dir; + data.error = FALSE; + + if (uidset) { + ret = mail_index_uidset_foreach(src->index, messageset, + src->synced_messages_count, + copy_hard_func, &data); + } else { + ret = mail_index_messageset_foreach(src->index, messageset, + src->synced_messages_count, + copy_hard_func, &data); + } + + if (ret == -1) + mail_storage_set_index_error(src); + + if (!dest->index->set_lock(dest->index, MAIL_LOCK_UNLOCK)) { + mail_storage_set_index_error(dest); + ret = -1; + } + + if (!src->index->set_lock(src->index, MAIL_LOCK_SHARED)) { + mail_storage_set_index_error(src); + ret = -1; + } + + return data.error ? -1 : ret; +} + +int maildir_storage_copy(Mailbox *box, Mailbox *destbox, + const char *messageset, int uidset) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + + if (destbox->readonly) { + mail_storage_set_error(box->storage, + "Destination mailbox is read-only"); + return FALSE; + } + + if (getenv("COPY_WITH_HARDLINKS") != NULL && + destbox->storage == box->storage) { + /* both source and destination mailbox are in maildirs and + copy_with_hardlinks option is on, do it */ + switch (maildir_copy_with_hardlinks(ibox, + (IndexMailbox *) destbox, messageset, uidset)) { + case -1: + return FALSE; + case 1: + return TRUE; + } + + /* non-fatal hardlinking failure, try the slow way */ + } + + return index_storage_copy(box, destbox, messageset, uidset); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/maildir-expunge.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,57 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "maildir-storage.h" + +#include <unistd.h> + +static int expunge_msg(IndexMailbox *ibox, MailIndexRecord *rec, + unsigned int seq) +{ + const char *fname; + char path[1024]; + + /* get our file name - ignore if it's missing, + we're deleting it after all.. */ + fname = ibox->index->lookup_field(ibox->index, rec, + FIELD_TYPE_LOCATION); + if (fname != NULL) { + i_snprintf(path, sizeof(path), "%s/cur/%s", + ibox->index->dir, fname); + if (unlink(path) == -1 && errno != ENOENT) { + mail_storage_set_error(ibox->box.storage, + "unlink() failed for " + "message file %s: %m", path); + /* continue anyway */ + } + } + + return ibox->index->expunge(ibox->index, rec, seq, FALSE); + +} + +int maildir_expunge_locked(IndexMailbox *ibox, + MailExpungeFunc expunge_func, void *user_data) +{ + MailIndexRecord *rec; + unsigned int seq, uid; + + rec = index_expunge_seek_first(ibox, &seq); + while (rec != NULL) { + if (rec->msg_flags & MAIL_DELETED) { + /* save UID before deletion */ + uid = rec->uid; + + if (!expunge_msg(ibox, rec, seq)) + return FALSE; + + if (expunge_func != NULL) + expunge_func(&ibox->box, seq, uid, user_data); + seq--; + } + rec = ibox->index->next(ibox->index, rec); + seq++; + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/maildir-list.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,163 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "unlink-directory.h" +#include "imap-match.h" +#include "subscription-file/subscription-file.h" +#include "maildir-index.h" +#include "maildir-storage.h" + +#include <dirent.h> +#include <sys/stat.h> + +typedef struct { + MailboxFunc func; + void *user_data; +} FindSubscribedData; + +static MailboxFlags maildir_get_marked_flags(const char *dir) +{ + struct stat st; + char path[1024]; + time_t index_stamp, cur_stamp; + + i_snprintf(path, sizeof(path), "%s/" INDEX_FILE_PREFIX, dir); + if (stat(path, &st) == -1) { + /* index file wasn't found. it might be with another name, + but finding it would be too slow. */ + return 0; + } + + index_stamp = st.st_mtime; + + i_snprintf(path, sizeof(path), "%s/cur", dir); + if (stat(path, &st) == -1) { + /* no cur/ directory - broken */ + return 0; + } + + cur_stamp = st.st_mtime; + if (cur_stamp != index_stamp) { + /* changes in cur directory */ + return MAILBOX_MARKED; + } + + i_snprintf(path, sizeof(path), "%s/new", dir); + if (stat(path, &st) == -1) { + /* no new/ directory - broken */ + return 0; + } + + return st.st_mtime <= cur_stamp ? MAILBOX_UNMARKED : MAILBOX_MARKED; +} + +int maildir_find_mailboxes(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data) +{ + const ImapMatchGlob *glob; + DIR *dirp; + struct dirent *d; + struct stat st; + MailboxFlags flags; + char path[1024]; + int failed, found_inbox; + + mail_storage_clear_error(storage); + + dirp = opendir(storage->dir); + if (dirp == NULL) { + mail_storage_set_critical(storage, "opendir(%s) failed: %m", + storage->dir); + return FALSE; + } + + glob = imap_match_init(mask, TRUE, '.'); + + failed = found_inbox = FALSE; + while ((d = readdir(dirp)) != NULL) { + const char *fname = d->d_name; + + if (fname[0] != '.') + continue; + + /* skip . and .. */ + if (fname[1] == '\0' || (fname[1] == '.' && fname[2] == '\0')) + continue; + + /* make sure the mask matches - dirs beginning with ".." + should be deleted and we always want to check those. */ + if (fname[1] == '.' || imap_match(glob, fname+1, 0, NULL) < 0) + continue; + + /* make sure it's a directory */ + i_snprintf(path, sizeof(path), "%s/%s", storage->dir, fname); + if (stat(path, &st) != 0) { + if (errno == ENOENT) + continue; /* just deleted, ignore */ + + mail_storage_set_critical(storage, + "stat(%s) failed: %m", path); + failed = TRUE; + break; + } + + if (!S_ISDIR(st.st_mode)) + continue; + + if (fname[1] == '.') { + /* this mailbox is in the middle of being deleted, + or the process trying to delete it had died. + + delete it ourself if it's been there longer than + one hour */ + if (st.st_mtime < 3600) + (void)unlink_directory(path); + continue; + } + + flags = maildir_get_marked_flags(path); + func(storage, fname+1, flags, user_data); + } + + if (!failed && !found_inbox && + imap_match(glob, "INBOX", 0, NULL) >= 0) { + /* .INBOX directory doesn't exist yet, but INBOX still exists */ + func(storage, "INBOX", 0, user_data); + } + + (void)closedir(dirp); + return !failed; +} + +static int maildir_subs_func(MailStorage *storage, const char *name, + void *user_data) +{ + FindSubscribedData *data = user_data; + MailboxFlags flags; + struct stat st; + char path[1024]; + + i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name); + + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + flags = maildir_get_marked_flags(path); + else + flags = MAILBOX_NOSELECT; + + data->func(storage, name, flags, data->user_data); + return TRUE; +} + +int maildir_find_subscribed(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data) +{ + FindSubscribedData data; + + data.func = func; + data.user_data = user_data; + + if (subsfile_foreach(storage, mask, maildir_subs_func, &data) <= 0) + return FALSE; + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/maildir-save.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,108 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "hostpid.h" +#include "iobuffer.h" +#include "maildir-index.h" +#include "maildir-storage.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <utime.h> + +static int maildir_create_tmp(MailStorage *storage, const char *dir, + const char **fname) +{ + static unsigned int create_count = 0; + const char *path; + int fd; + + hostpid_init(); + + *fname = t_strdup_printf("%lu.%s_%u.%s", (unsigned long) ioloop_time, + my_pid, create_count++, my_hostname); + + path = t_strconcat(dir, "/", *fname, NULL); + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0660); + if (fd == -1) { + /* don't bother checking if it was because file existed - + if that happens it's itself an error. */ + mail_storage_set_critical(storage, + "Can't create file %s: %m", path); + } + + return fd; +} + +static const char *maildir_read_into_tmp(MailStorage *storage, const char *dir, + IOBuffer *buf, size_t data_size) +{ + const char *fname, *path; + int fd; + + fd = maildir_create_tmp(storage, dir, &fname); + if (fd == -1) + return NULL; + + path = t_strconcat(dir, "/", fname, NULL); + if (!index_storage_save_into_fd(storage, fd, path, buf, data_size)) + fname = NULL; + + (void)close(fd); + + if (fname == NULL) + (void)unlink(path); + return fname; +} + +int maildir_storage_save(Mailbox *box, MailFlags flags, + const char *custom_flags[], time_t internal_date, + IOBuffer *data, size_t data_size) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + struct utimbuf buf; + const char *tmpdir, *fname, *tmp_path, *new_path; + int failed; + + if (box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return FALSE; + } + + if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags)) + return mail_storage_set_index_error(ibox); + + t_push(); + + /* create the file into tmp/ directory */ + tmpdir = t_strconcat(box->storage->dir, "/tmp", NULL); + fname = maildir_read_into_tmp(box->storage, tmpdir, data, data_size); + if (fname == NULL) { + t_pop(); + return FALSE; + } + tmp_path = t_strconcat(tmpdir, "/", fname, NULL); + + fname = maildir_filename_set_flags(fname, flags); + new_path = t_strconcat(box->storage->dir, "/new/", fname, NULL); + + /* set the internal_date by modifying mtime */ + buf.actime = ioloop_time; + buf.modtime = internal_date; + (void)utime(tmp_path, &buf); + + /* move the file into new/ directory - syncing will pick it + up from there */ + if (rename(tmp_path, new_path) == 0) + failed = FALSE; + else { + mail_storage_set_critical(box->storage, "rename(%s, %s) " + "failed: %m", tmp_path, new_path); + (void)unlink(tmp_path); + failed = TRUE; + } + + t_pop(); + return !failed; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/maildir-storage.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,373 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "unlink-directory.h" +#include "subscription-file/subscription-file.h" +#include "maildir-index.h" +#include "maildir-storage.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +#define CREATE_MODE 0770 /* umask() should limit it more */ + +extern MailStorage maildir_storage; +static Mailbox maildir_mailbox; + +static const char *maildirs[] = { "cur", "new", "tmp", NULL }; + +static MailStorage *maildir_create(const char *data) +{ + MailStorage *storage; + const char *home, *path; + + if (data == NULL || *data == '\0') { + /* we'll need to figure out the maildir location ourself. + it's either root dir if we've already chroot()ed, or + $HOME/Maildir otherwise */ + if (access("/cur", R_OK|W_OK|X_OK) == 0) + data = "/"; + else { + home = getenv("HOME"); + if (home != NULL) { + path = t_strconcat(home, "/Maildir", NULL); + if (access(path, R_OK|W_OK|X_OK) == 0) + data = path; + } + } + } + + if (data == NULL) + return NULL; + + storage = i_new(MailStorage, 1); + memcpy(storage, &maildir_storage, sizeof(MailStorage)); + + storage->dir = i_strdup(data); + return storage; +} + +static void maildir_free(MailStorage *storage) +{ + i_free(storage->dir); + i_free(storage); +} + +static int maildir_autodetect(const char *data) +{ + struct stat st; + + return stat(t_strconcat(data, "/cur", NULL), &st) == 0 && + S_ISDIR(st.st_mode); +} + +static int maildir_is_valid_name(MailStorage *storage, const char *name) +{ + return name[0] != '\0' && name[0] != storage->hierarchy_sep && + strchr(name, '/') == NULL; +} + +/* create or fix maildir, ignore if it already exists */ +static int create_maildir(const char *dir, int verify) +{ + const char **tmp; + char path[1024]; + + if (mkdir(dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify)) + return FALSE; + + for (tmp = maildirs; *tmp != NULL; tmp++) { + i_snprintf(path, sizeof(path), "%s/%s", dir, *tmp); + + if (mkdir(path, CREATE_MODE) == -1 && + (errno != EEXIST || !verify)) + return FALSE; + } + + return TRUE; +} + +static int verify_inbox(MailStorage *storage, const char *dir) +{ + const char **tmp; + char src[1024], dest[1024]; + + /* first make sure the cur/ new/ and tmp/ dirs exist in root dir */ + (void)create_maildir(dir, TRUE); + + /* create the .INBOX directory */ + i_snprintf(dest, sizeof(dest), "%s/.INBOX", dir); + if (mkdir(dest, CREATE_MODE) == -1 && errno != EEXIST) { + mail_storage_set_critical(storage, "Can't create directory " + "%s: %m", dest); + return FALSE; + } + + /* then symlink the cur/ new/ and tmp/ into the .INBOX/ directory */ + for (tmp = maildirs; *tmp != NULL; tmp++) { + i_snprintf(src, sizeof(src), "../%s", *tmp); + i_snprintf(dest, sizeof(dest), "%s/.INBOX/%s", dir, *tmp); + + if (symlink(src, dest) == -1 && errno != EEXIST) { + mail_storage_set_critical(storage, "symlink(%s, %s) " + "failed: %m", src, dest); + return FALSE; + } + } + + return TRUE; +} + +static Mailbox *maildir_open(MailStorage *storage, const char *name, + int readonly) +{ + IndexMailbox *ibox; + const char *path; + + path = t_strconcat(storage->dir, "/.", name, NULL); + + ibox = index_storage_init(storage, &maildir_mailbox, + maildir_index_alloc(path), name, readonly); + if (ibox != NULL) + ibox->expunge_locked = maildir_expunge_locked; + return (Mailbox *) ibox; +} + +static Mailbox *maildir_open_mailbox(MailStorage *storage, const char *name, + int readonly) +{ + struct stat st; + char path[1024]; + + mail_storage_clear_error(storage); + + /* INBOX is always case-insensitive */ + if (strcasecmp(name, "INBOX") == 0) { + if (!verify_inbox(storage, storage->dir)) + return NULL; + return maildir_open(storage, "INBOX", readonly); + } + + i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name); + if (stat(path, &st) == 0) { + /* exists - make sure the required directories are also there */ + (void)create_maildir(path, TRUE); + + return maildir_open(storage, name, readonly); + } else if (errno == ENOENT) { + mail_storage_set_error(storage, "Mailbox doesn't exist"); + return NULL; + } else { + mail_storage_set_critical(storage, "Can't open mailbox %s: %m", + name); + return NULL; + } +} + +static int maildir_create_mailbox(MailStorage *storage, const char *name) +{ + char path[1024]; + + mail_storage_clear_error(storage); + + if (strcasecmp(name, "INBOX") == 0) + name = "INBOX"; + + if (!maildir_is_valid_name(storage, name)) { + mail_storage_set_error(storage, "Invalid mailbox name"); + return FALSE; + } + + i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name); + if (create_maildir(path, FALSE)) + return TRUE; + else if (errno == EEXIST) { + mail_storage_set_error(storage, "Mailbox already exists"); + return FALSE; + } else { + mail_storage_set_critical(storage, "Can't create mailbox " + "%s: %m", name); + return FALSE; + } +} + +static int maildir_delete_mailbox(MailStorage *storage, const char *name) +{ + struct stat st; + char src[1024], dest[1024]; + int count; + + mail_storage_clear_error(storage); + + if (strcasecmp(name, "INBOX") == 0) { + mail_storage_set_error(storage, "INBOX can't be deleted."); + return FALSE; + } + + /* rename the .maildir into ..maildir which marks it as being + deleted. this way we never see partially deleted maildirs. */ + i_snprintf(src, sizeof(src), "%s/.%s", storage->dir, name); + i_snprintf(dest, sizeof(dest), "%s/..%s", storage->dir, name); + + if (stat(src, &st) != 0 && errno == ENOENT) { + mail_storage_set_error(storage, "Mailbox doesn't exist."); + return FALSE; + } + + count = 0; + while (rename(src, dest) == -1 && count < 2) { + if (errno != EEXIST) { + mail_storage_set_critical(storage, + "rename(%s, %s) failed: %m", + src, dest); + return FALSE; + } + + /* ..dir already existed? delete it and try again */ + if (!unlink_directory(dest)) { + mail_storage_set_critical(storage, + "unlink_directory(%s) " + "failed: %m", dest); + return FALSE; + } + count++; + } + + if (!unlink_directory(dest)) { + mail_storage_set_critical(storage, "unlink_directory(%s) " + "failed: %m", dest); + return FALSE; + } + return TRUE; +} + +static int move_inbox_data(MailStorage *storage, const char *newdir) +{ + const char **tmp; + char oldpath[1024], newpath[1024]; + + /* newpath points to the destination folder directory, which contains + symlinks to real INBOX directories. unlink() the symlinks and + move the real cur/ directory here. */ + for (tmp = maildirs; *tmp != NULL; tmp++) { + i_snprintf(newpath, sizeof(newpath), "%s/%s", newdir, *tmp); + + if (unlink(newpath) == -1 && errno != EEXIST) { + mail_storage_set_error(storage, "unlink(%s) failed: " + "%m", newpath); + return FALSE; + } + } + + i_snprintf(oldpath, sizeof(oldpath), "%s/cur", storage->dir); + i_snprintf(newpath, sizeof(newpath), "%s/cur", newdir); + if (rename(oldpath, newpath) != 0) { + mail_storage_set_critical(storage, "rename(%s, %s) failed: %m", + oldpath, newpath); + return FALSE; + } + + /* create back the cur/ directory for INBOX */ + (void)mkdir(oldpath, CREATE_MODE); + return TRUE; +} + +static int maildir_rename_mailbox(MailStorage *storage, const char *oldname, + const char *newname) +{ + char oldpath[1024], newpath[1024]; + + mail_storage_clear_error(storage); + + if (strcasecmp(oldname, "INBOX") == 0) + oldname = "INBOX"; + + /* NOTE: renaming INBOX works just fine with us, it's simply created + the next time it's needed. Only problem with it is that it's not + atomic operation but that can't be really helped. */ + i_snprintf(oldpath, sizeof(oldpath), "%s/.%s", storage->dir, oldname); + i_snprintf(newpath, sizeof(newpath), "%s/.%s", storage->dir, newname); + if (rename(oldpath, newpath) == 0) { + if (strcmp(oldname, "INBOX") == 0) + return move_inbox_data(storage, newpath); + return TRUE; + } + + if (errno == EEXIST) { + mail_storage_set_error(storage, + "Target mailbox already exists"); + return FALSE; + } else { + mail_storage_set_critical(storage, "rename(%s, %s) failed: %m", + oldpath, newpath); + return FALSE; + } +} + +static int maildir_get_mailbox_name_status(MailStorage *storage, + const char *name, + MailboxNameStatus *status) +{ + struct stat st; + char path[1024]; + + mail_storage_clear_error(storage); + + if (strcasecmp(name, "INBOX") == 0) + name = "INBOX"; + + if (!maildir_is_valid_name(storage, name)) { + *status = MAILBOX_NAME_INVALID; + return TRUE; + } + + i_snprintf(path, sizeof(path), "%s/.%s", storage->dir, name); + if (stat(path, &st) == 0) { + *status = MAILBOX_NAME_EXISTS; + return TRUE; + } else if (errno == ENOENT) { + *status = MAILBOX_NAME_VALID; + return TRUE; + } else { + mail_storage_set_critical(storage, "mailbox name status: " + "stat(%s) failed: %m", path); + return FALSE; + } +} + +MailStorage maildir_storage = { + "maildir", /* name */ + + '.', /* hierarchy_sep - can't be changed */ + + maildir_create, + maildir_free, + maildir_autodetect, + maildir_open_mailbox, + maildir_create_mailbox, + maildir_delete_mailbox, + maildir_rename_mailbox, + maildir_find_mailboxes, + subsfile_set_subscribed, + maildir_find_subscribed, + maildir_get_mailbox_name_status, + mail_storage_get_last_error +}; + +static Mailbox maildir_mailbox = { + NULL, /* name */ + NULL, /* storage */ + + index_storage_close, + index_storage_get_status, + index_storage_sync, + index_storage_expunge, + index_storage_update_flags, + maildir_storage_copy, + index_storage_fetch, + index_storage_search, + maildir_storage_save, + mail_storage_is_inconsistency_error +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/maildir/maildir-storage.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,20 @@ +#ifndef __MAILDIR_STORAGE_H +#define __MAILDIR_STORAGE_H + +#include "index-storage.h" + +int maildir_storage_copy(Mailbox *box, Mailbox *destbox, + const char *messageset, int uidset); +int maildir_storage_save(Mailbox *box, MailFlags flags, + const char *custom_flags[], time_t internal_date, + IOBuffer *data, size_t data_size); + +int maildir_find_mailboxes(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data); +int maildir_find_subscribed(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data); + +int maildir_expunge_locked(IndexMailbox *ibox, + MailExpungeFunc expunge_func, void *user_data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,19 @@ +noinst_LIBRARIES = libstorage_mbox.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-mail \ + -I$(top_srcdir)/src/lib-imap \ + -I$(top_srcdir)/src/lib-index \ + -I$(top_srcdir)/src/lib-index/mbox \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-storage/index + +libstorage_mbox_a_SOURCES = \ + mbox-expunge.c \ + mbox-list.c \ + mbox-save.c \ + mbox-storage.c + +noinst_HEADERS = \ + mbox-storage.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/mbox-expunge.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,35 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mbox-storage.h" + +int mbox_expunge_locked(IndexMailbox *ibox, + MailExpungeFunc expunge_func, void *user_data) +{ + MailIndexRecord *rec; + unsigned int seq, uid; + + /* FIXME: open the mbox file, lock it, and remove the deleted + blocks. probably better to do it in small blocks than to + memmove() megabytes of data.. */ + + rec = index_expunge_seek_first(ibox, &seq); + while (rec != NULL) { + if (rec->msg_flags & MAIL_DELETED) { + /* save UID before deletion */ + uid = rec->uid; + + if (!ibox->index->expunge(ibox->index, rec, + seq, FALSE)) + return FALSE; + + if (expunge_func != NULL) + expunge_func(&ibox->box, seq, uid, user_data); + seq--; + } + rec = ibox->index->next(ibox->index, rec); + seq++; + } + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/mbox-list.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,166 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "unlink-directory.h" +#include "imap-match.h" +#include "subscription-file/subscription-file.h" +#include "mbox-index.h" +#include "mbox-storage.h" + +#include <dirent.h> +#include <sys/stat.h> + +typedef struct { + MailboxFunc func; + void *user_data; +} FindSubscribedData; + +static int mbox_find_path(MailStorage *storage, const ImapMatchGlob *glob, + MailboxFunc func, void *user_data, + const char *relative_dir, int *found_inbox) +{ + DIR *dirp; + struct dirent *d; + struct stat st; + const char *dir; + char fulldir[1024], path[1024]; + int failed, len; + + if (relative_dir == NULL) + dir = storage->dir; + else { + i_snprintf(fulldir, sizeof(fulldir), "%s/%s", + storage->dir, relative_dir); + dir = fulldir; + } + + dirp = opendir(dir); + if (dirp == NULL) { + mail_storage_set_critical(storage, "opendir(%s) failed: %m", + dir); + return FALSE; + } + + failed = FALSE; + while ((d = readdir(dirp)) != NULL) { + const char *fname = d->d_name; + + /* skip all hidden files */ + if (fname[0] == '.') + continue; + + /* skip all .lock files */ + len = strlen(fname); + if (len > 5 && strcmp(fname+len-5, ".lock") == 0) + continue; + + /* make sure the mask matches */ + if (relative_dir == NULL) { + if (imap_match(glob, fname, 0, NULL) < 0) + continue; + } else { + i_snprintf(path, sizeof(path), + "%s/%s", relative_dir, fname); + if (imap_match(glob, path, 0, NULL) < 0) + continue; + } + + /* see if it's a directory */ + i_snprintf(path, sizeof(path), "%s/%s", dir, fname); + if (stat(path, &st) != 0) { + if (errno == ENOENT) + continue; /* just deleted, ignore */ + + mail_storage_set_critical(storage, "stat(%s) failed: " + "%m", path); + failed = TRUE; + break; + } + + if (relative_dir == NULL) { + strncpy(path, fname, sizeof(path)-1); + path[sizeof(path)-1] = '\0'; + } else { + i_snprintf(path, sizeof(path), "%s/%s", + relative_dir, fname); + } + + if (S_ISDIR(st.st_mode)) { + /* subdirectory, scan it too */ + if (!mbox_find_path(storage, glob, func, + user_data, path, NULL)) { + failed = TRUE; + break; + } + } else { + if (found_inbox != NULL && + strcasecmp(path, "inbox") == 0) + *found_inbox = TRUE; + + func(storage, path, MAILBOX_NOINFERIORS, user_data); + } + } + + (void)closedir(dirp); + return !failed; +} + +int mbox_find_mailboxes(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data) +{ + const ImapMatchGlob *glob; + int found_inbox; + + mail_storage_clear_error(storage); + + glob = imap_match_init(mask, TRUE, '/'); + + found_inbox = FALSE; + if (!mbox_find_path(storage, glob, func, user_data, + NULL, &found_inbox)) + return FALSE; + + if (!found_inbox && imap_match(glob, "INBOX", 0, NULL) < 0) { + /* INBOX always exists */ + func(storage, "INBOX", MAILBOX_UNMARKED | MAILBOX_NOINFERIORS, + user_data); + } + + return TRUE; +} + +static int mbox_subs_func(MailStorage *storage, const char *name, + void *user_data) +{ + FindSubscribedData *data = user_data; + MailboxFlags flags; + struct stat st; + char path[1024]; + + /* see if the mailbox exists, don't bother with the marked flags */ + if (strcasecmp(name, "INBOX") == 0) { + /* inbox always exists */ + flags = 0; + } else { + i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name); + flags = stat(path, &st) == 0 && !S_ISDIR(st.st_mode) ? + 0 : MAILBOX_NOSELECT; + } + + data->func(storage, name, flags, data->user_data); + return TRUE; +} + +int mbox_find_subscribed(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data) +{ + FindSubscribedData data; + + data.func = func; + data.user_data = user_data; + + if (subsfile_foreach(storage, mask, mbox_subs_func, &data) <= 0) + return FALSE; + + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/mbox-save.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,62 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "hostpid.h" +#include "iobuffer.h" +#include "mbox-index.h" +#include "mbox-lock.h" +#include "mbox-storage.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> + +int mbox_storage_save(Mailbox *box, MailFlags flags, const char *custom_flags[], + time_t internal_date, IOBuffer *data, size_t data_size) +{ + IndexMailbox *ibox = (IndexMailbox *) box; + off_t pos; + int fd, failed; + + if (box->readonly) { + mail_storage_set_error(box->storage, "Mailbox is read-only"); + return FALSE; + } + + if (!index_mailbox_fix_custom_flags(ibox, &flags, custom_flags)) + return mail_storage_set_index_error(ibox); + + /* append the data into mbox file */ + fd = open(ibox->index->mbox_path, O_RDWR | O_CREAT); + if (fd == -1) { + mail_storage_set_error(box->storage, "Can't open mbox file " + "%s: %m", ibox->index->mbox_path); + return FALSE; + } + + if (!mbox_lock(ibox->index, ibox->index->mbox_path, fd)) { + (void)close(fd); + return mail_storage_set_index_error(ibox); + } + + failed = FALSE; + + pos = lseek(fd, 0, SEEK_END); + if (pos == (off_t)-1) { + mail_storage_set_error(box->storage, "lseek() failed for mbox " + "file %s: %m", ibox->index->mbox_path); + failed = TRUE; + } + + if (!failed && !index_storage_save_into_fd(box->storage, fd, + ibox->index->mbox_path, + data, data_size)) { + /* failed, truncate file back to original size */ + (void)ftruncate(fd, pos); + failed = TRUE; + } + + (void)mbox_unlock(ibox->index, ibox->index->mbox_path, fd); + (void)close(fd); + return !failed; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/mbox-storage.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,365 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "unlink-directory.h" +#include "subscription-file/subscription-file.h" +#include "mbox-index.h" +#include "mbox-storage.h" + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/stat.h> + +#define CREATE_MODE 0770 /* umask() should limit it more */ + +extern MailStorage mbox_storage; +static Mailbox mbox_mailbox; + +static int mbox_autodetect(const char *data) +{ + const char *path; + struct stat st; + + path = t_strconcat(data, "/.imap", NULL); + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode) && + access(path, R_OK|W_OK|X_OK) == 0) + return TRUE; + + path = t_strconcat(data, "/inbox", NULL); + if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) && + access(path, R_OK|W_OK) == 0) + return TRUE; + + path = t_strconcat(data, "/mbox", NULL); + if (stat(path, &st) == 0 && !S_ISDIR(st.st_mode) && + access(path, R_OK|W_OK) == 0) + return TRUE; + + return FALSE; +} + +static MailStorage *mbox_create(const char *data) +{ + MailStorage *storage; + const char *home, *path; + + if (data == NULL || *data == '\0') { + /* we'll need to figure out the mail location ourself. + it's root dir if we've already chroot()ed, otherwise + either $HOME/mail or $HOME/Mail */ + if (mbox_autodetect("")) + data = "/"; + else { + home = getenv("HOME"); + if (home != NULL) { + path = t_strconcat(home, "/mail", NULL); + if (access(path, R_OK|W_OK|X_OK) == 0) + data = path; + else { + path = t_strconcat(home, "/Mail", NULL); + if (access(path, R_OK|W_OK|X_OK) == 0) + data = path; + } + } + } + } + + if (data == NULL) + return NULL; + + storage = i_new(MailStorage, 1); + memcpy(storage, &mbox_storage, sizeof(MailStorage)); + + storage->dir = i_strdup(data); + return storage; +} + +static void mbox_free(MailStorage *storage) +{ + i_free(storage->dir); + i_free(storage); +} + +static int mbox_is_valid_name(MailStorage *storage, const char *name) +{ + return name[0] != '\0' && name[0] != storage->hierarchy_sep; +} + +static const char *mbox_get_index_dir(const char *mbox_path) +{ + const char *p, *rootpath; + + p = strrchr(mbox_path, '/'); + if (p == NULL) + return t_strconcat(".imap/", mbox_path); + else { + rootpath = t_strndup(mbox_path, (unsigned int) (p-mbox_path)); + return t_strconcat(rootpath, "/.imap/", p+1, NULL); + } +} + +static int create_mbox_index_dirs(const char *mbox_path, int verify) +{ + const char *index_dir, *imap_dir; + + index_dir = mbox_get_index_dir(mbox_path); + imap_dir = strstr(index_dir, ".imap/"); + imap_dir = t_strndup(index_dir, + (unsigned int) (imap_dir - index_dir) + 5); + + if (mkdir(imap_dir, CREATE_MODE) == -1 && errno != EEXIST) + return FALSE; + if (mkdir(index_dir, CREATE_MODE) == -1 && (errno != EEXIST || !verify)) + return FALSE; + + return TRUE; +} + +static void verify_inbox(MailStorage *storage) +{ + char path[1024]; + int fd; + + i_snprintf(path, sizeof(path), "%s/inbox", storage->dir); + + /* make sure inbox file itself exists */ + fd = open(path, O_RDWR | O_CREAT | O_EXCL); + if (fd != -1) + (void)close(fd); + + /* make sure the index directories exist */ + (void)create_mbox_index_dirs(path, TRUE); +} + +static Mailbox *mbox_open(MailStorage *storage, const char *name, int readonly) +{ + IndexMailbox *ibox; + const char *path, *index_dir; + + /* name = "foo/bar" + mbox_path = "/mail/foo/bar" + index_dir = "/mail/foo/.imap/bar" */ + path = t_strconcat(storage->dir, "/", name, NULL); + index_dir = mbox_get_index_dir(path); + + ibox = index_storage_init(storage, &mbox_mailbox, + mbox_index_alloc(index_dir, path), + name, readonly); + if (ibox != NULL) + ibox->expunge_locked = mbox_expunge_locked; + return (Mailbox *) ibox; +} + +static Mailbox *mbox_open_mailbox(MailStorage *storage, const char *name, + int readonly) +{ + struct stat st; + char path[1024]; + + mail_storage_clear_error(storage); + + /* INBOX is always case-insensitive */ + if (strcasecmp(name, "INBOX") == 0) { + /* make sure inbox exists */ + verify_inbox(storage); + return mbox_open(storage, "inbox", readonly); + } + + i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name); + if (stat(path, &st) == 0) { + /* exists - make sure the required directories are also there */ + (void)create_mbox_index_dirs(path, TRUE); + + return mbox_open(storage, name, readonly); + } else if (errno == ENOENT) { + mail_storage_set_error(storage, "Mailbox doesn't exist"); + return NULL; + } else { + mail_storage_set_critical(storage, "Can't open mailbox %s: %m", + name); + return NULL; + } +} + +static int mbox_create_mailbox(MailStorage *storage, const char *name) +{ + struct stat st; + char path[1024]; + int fd; + + mail_storage_clear_error(storage); + + if (strcasecmp(name, "INBOX") == 0) + name = "inbox"; + + if (!mbox_is_valid_name(storage, name)) { + mail_storage_set_error(storage, "Invalid mailbox name"); + return FALSE; + } + + /* make sure it doesn't exist already */ + i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name); + if (stat(path, &st) == 0) { + mail_storage_set_error(storage, "Mailbox already exists"); + return FALSE; + } + + if (errno != EEXIST) { + mail_storage_set_critical(storage, "stat() failed for mbox " + "file %s: %m", path); + return FALSE; + } + + /* create the mailbox file */ + fd = open(path, O_RDWR | O_CREAT | O_EXCL); + if (fd != -1) { + (void)close(fd); + return TRUE; + } else if (errno == EEXIST) { + /* mailbox was just created between stat() and open() call.. */ + mail_storage_set_error(storage, "Mailbox already exists"); + return FALSE; + } else { + mail_storage_set_critical(storage, "Can't create mailbox " + "%s: %m", name); + return FALSE; + } +} + +static int mbox_delete_mailbox(MailStorage *storage, const char *name) +{ + const char *index_dir; + char path[1024]; + + mail_storage_clear_error(storage); + + if (strcasecmp(name, "INBOX") == 0) { + mail_storage_set_error(storage, "INBOX can't be deleted."); + return FALSE; + } + + /* first unlink the mbox file */ + i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name); + if (unlink(path) == -1) { + if (errno == ENOENT) { + mail_storage_set_error(storage, + "Mailbox doesn't exist."); + } else { + mail_storage_set_error(storage, "Can't delete mbox " + "file %s: %m", path); + } + return FALSE; + } + + /* next delete the index directory */ + index_dir = mbox_get_index_dir(path); + if (!unlink_directory(index_dir)) { + mail_storage_set_critical(storage, "unlink_directory(%s) " + "failed: %m", index_dir); + return FALSE; + } + return TRUE; +} + +static int mbox_rename_mailbox(MailStorage *storage, const char *oldname, + const char *newname) +{ + const char *old_indexdir, *new_indexdir; + char oldpath[1024], newpath[1024]; + + mail_storage_clear_error(storage); + + if (strcasecmp(oldname, "INBOX") == 0) + oldname = "inbox"; + + /* NOTE: renaming INBOX works just fine with us, it's simply created + the next time it's needed. */ + i_snprintf(oldpath, sizeof(oldpath), "%s/%s", storage->dir, oldname); + i_snprintf(newpath, sizeof(newpath), "%s/%s", storage->dir, newname); + if (link(oldpath, newpath) == 0) { + (void)unlink(oldpath); + /* ... */ + } else if (errno == EEXIST) { + mail_storage_set_error(storage, + "Target mailbox already exists"); + return FALSE; + } else { + mail_storage_set_critical(storage, "link(%s, %s) failed: %m", + oldpath, newpath); + return FALSE; + } + + /* we need to rename the index directory as well */ + old_indexdir = mbox_get_index_dir(oldpath); + new_indexdir = mbox_get_index_dir(newpath); + (void)rename(old_indexdir, new_indexdir); + + return TRUE; +} + +static int mbox_get_mailbox_name_status(MailStorage *storage, const char *name, + MailboxNameStatus *status) +{ + struct stat st; + char path[1024]; + + mail_storage_clear_error(storage); + + if (strcasecmp(name, "INBOX") == 0) + name = "inbox"; + + if (!mbox_is_valid_name(storage, name)) { + *status = MAILBOX_NAME_INVALID; + return TRUE; + } + + i_snprintf(path, sizeof(path), "%s/%s", storage->dir, name); + if (stat(path, &st) == 0) { + *status = MAILBOX_NAME_EXISTS; + return TRUE; + } else if (errno == ENOENT) { + *status = MAILBOX_NAME_VALID; + return TRUE; + } else { + mail_storage_set_critical(storage, "mailbox name status: " + "stat(%s) failed: %m", path); + return FALSE; + } +} + +MailStorage mbox_storage = { + "mbox", /* name */ + + '/', /* hierarchy_sep - can't be changed */ + + mbox_create, + mbox_free, + mbox_autodetect, + mbox_open_mailbox, + mbox_create_mailbox, + mbox_delete_mailbox, + mbox_rename_mailbox, + mbox_find_mailboxes, + subsfile_set_subscribed, + mbox_find_subscribed, + mbox_get_mailbox_name_status, + mail_storage_get_last_error +}; + +static Mailbox mbox_mailbox = { + NULL, /* name */ + NULL, /* storage */ + + index_storage_close, + index_storage_get_status, + index_storage_sync, + index_storage_expunge, + index_storage_update_flags, + index_storage_copy, + index_storage_fetch, + index_storage_search, + mbox_storage_save, + mail_storage_is_inconsistency_error +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/index/mbox/mbox-storage.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,19 @@ +#ifndef __MBOX_STORAGE_H +#define __MBOX_STORAGE_H + +#include "index-storage.h" + +int mbox_storage_copy(Mailbox *box, Mailbox *destbox, + const char *messageset, int uidset); +int mbox_storage_save(Mailbox *box, MailFlags flags, const char *custom_flags[], + time_t internal_date, IOBuffer *data, size_t data_size); + +int mbox_find_mailboxes(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data); +int mbox_find_subscribed(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data); + +int mbox_expunge_locked(IndexMailbox *ibox, + MailExpungeFunc expunge_func, void *user_data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,496 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mail-search.h" + +typedef struct { + Pool pool; + const char *error; +} SearchBuildData; + +static MailSearchArg *search_arg_new(Pool pool, MailSearchArgType type) +{ + MailSearchArg *arg; + + arg = p_new(pool, MailSearchArg, 1); + arg->type = type; + + return arg; +} + +#define ARG_NEW(type, value) \ + arg_new(data, args, next_sarg, type, value) + +static int arg_new(SearchBuildData *data, ImapArgList **args, + MailSearchArg **next_sarg, MailSearchArgType type, int value) +{ + MailSearchArg *sarg; + + *next_sarg = sarg = search_arg_new(data->pool, type); + if (value == 0) + return TRUE; + + /* first arg */ + if (*args == NULL) { + data->error = "Missing parameter for argument"; + return FALSE; + } + + sarg->value.str = str_ucase((*args)->arg.data.str); + *args = (*args)->next; + + /* second arg */ + if (value == 2) { + if (*args == NULL) { + data->error = "Missing parameter for argument"; + return FALSE; + } + + sarg->hdr_value = str_ucase((*args)->arg.data.str); + *args = (*args)->next; + } + + return TRUE; +} + +static int search_arg_build(SearchBuildData *data, ImapArgList **args, + MailSearchArg **next_sarg) +{ + MailSearchArg **subargs; + ImapArg *arg; + char *str; + + if (*args == NULL) { + data->error = "Missing argument"; + return FALSE; + } + + arg = &(*args)->arg; + + if (arg->type == IMAP_ARG_NIL) { + /* NIL not allowed */ + data->error = "NIL not allowed"; + return FALSE; + } + + if (arg->type == IMAP_ARG_LIST) { + ImapArgList *list = arg->data.list; + + *next_sarg = search_arg_new(data->pool, SEARCH_SUB); + subargs = &(*next_sarg)->value.subargs; + while (list != NULL) { + if (!search_arg_build(data, &list, subargs)) + return FALSE; + subargs = &(*subargs)->next; + } + + *args = (*args)->next; + return TRUE; + } + + i_assert(arg->type == IMAP_ARG_ATOM || + arg->type == IMAP_ARG_STRING); + + /* string argument - get the name and jump to next */ + str = arg->data.str; + *args = (*args)->next; + str_ucase(str); + + switch (*str) { + case 'A': + if (strcmp(str, "ANSWERED") == 0) + return ARG_NEW(SEARCH_ANSWERED, 0); + else if (strcmp(str, "ALL") == 0) + return ARG_NEW(SEARCH_ALL, 0); + break; + case 'B': + if (strcmp(str, "BODY") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_BODY, 1); + } else if (strcmp(str, "BEFORE") == 0) { + /* <date> */ + return ARG_NEW(SEARCH_BEFORE, 1); + } else if (strcmp(str, "BCC") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_BCC, 1); + } + break; + case 'C': + if (strcmp(str, "CC") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_CC, 1); + } + break; + case 'D': + if (strcmp(str, "DELETED") == 0) + return ARG_NEW(SEARCH_DELETED, 0); + else if (strcmp(str, "DRAFT") == 0) + return ARG_NEW(SEARCH_DRAFT, 0); + break; + case 'F': + if (strcmp(str, "FLAGGED") == 0) + return ARG_NEW(SEARCH_FLAGGED, 0); + else if (strcmp(str, "FROM") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_FROM, 1); + } + break; + case 'H': + if (strcmp(str, "HEADER") == 0) { + /* <field-name> <string> */ + const char *key; + + if (*args == NULL) { + data->error = "Missing parameter for HEADER"; + return FALSE; + } + key = str_ucase((*args)->arg.data.str); + + if (strcmp(key, "FROM") == 0) { + *args = (*args)->next; + return ARG_NEW(SEARCH_FROM, 1); + } else if (strcmp(key, "TO") == 0) { + *args = (*args)->next; + return ARG_NEW(SEARCH_TO, 1); + } else if (strcmp(key, "CC") == 0) { + *args = (*args)->next; + return ARG_NEW(SEARCH_CC, 1); + } else if (strcmp(key, "BCC") == 0) { + *args = (*args)->next; + return ARG_NEW(SEARCH_BCC, 1); + } else if (strcmp(key, "SUBJECT") == 0) { + *args = (*args)->next; + return ARG_NEW(SEARCH_SUBJECT, 1); + } else { + return ARG_NEW(SEARCH_HEADER, 2); + } + } + break; + case 'K': + if (strcmp(str, "KEYWORD") == 0) { + /* <flag> */ + return ARG_NEW(SEARCH_KEYWORD, 1); + } + break; + case 'L': + if (strcmp(str, "LARGER") == 0) { + /* <n> */ + return ARG_NEW(SEARCH_LARGER, 1); + } + break; + case 'N': + if (strcmp(str, "NOT") == 0) { + if (!search_arg_build(data, args, next_sarg)) + return FALSE; + (*next_sarg)->not = !(*next_sarg)->not; + return TRUE; + } else if (strcmp(str, "NEW") == 0) { + /* NEW == (RECENT UNSEEN) */ + *next_sarg = search_arg_new(data->pool, SEARCH_SUB); + + subargs = &(*next_sarg)->value.subargs; + *subargs = search_arg_new(data->pool, SEARCH_RECENT); + (*subargs)->next = search_arg_new(data->pool, + SEARCH_SEEN); + (*subargs)->next->not = TRUE; + return TRUE; + } + break; + case 'O': + if (strcmp(str, "OR") == 0) { + /* <search-key1> <search-key2> */ + *next_sarg = search_arg_new(data->pool, SEARCH_OR); + + subargs = &(*next_sarg)->value.subargs; + for (;;) { + if (!search_arg_build(data, args, subargs)) + return FALSE; + + subargs = &(*subargs)->next; + + /* <key> OR <key> OR ... <key> - put them all + under one SEARCH_OR list. */ + if (*args == NULL) + break; + + arg = &(*args)->arg; + if (arg->type != IMAP_ARG_ATOM || + strcasecmp(arg->data.str, "OR") != 0) + break; + + *args = (*args)->next; + } + + if (!search_arg_build(data, args, subargs)) + return FALSE; + return TRUE; + } if (strcmp(str, "ON") == 0) { + /* <date> */ + return ARG_NEW(SEARCH_ON, 1); + } if (strcmp(str, "OLD") == 0) { + /* OLD == NOT RECENT */ + if (!ARG_NEW(SEARCH_RECENT, 0)) + return FALSE; + + (*next_sarg)->not = TRUE; + return TRUE; + } + break; + case 'R': + if (strcmp(str, "RECENT") == 0) + return ARG_NEW(SEARCH_RECENT, 0); + break; + case 'S': + if (strcmp(str, "SEEN") == 0) + return ARG_NEW(SEARCH_SEEN, 0); + else if (strcmp(str, "SUBJECT") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_SUBJECT, 1); + } else if (strcmp(str, "SENTBEFORE") == 0) { + /* <date> */ + return ARG_NEW(SEARCH_SENTBEFORE, 1); + } else if (strcmp(str, "SENTON") == 0) { + /* <date> */ + return ARG_NEW(SEARCH_SENTON, 1); + } else if (strcmp(str, "SENTSINCE") == 0) { + /* <date> */ + return ARG_NEW(SEARCH_SENTSINCE, 1); + } else if (strcmp(str, "SINCE") == 0) { + /* <date> */ + return ARG_NEW(SEARCH_SINCE, 1); + } else if (strcmp(str, "SMALLER") == 0) { + /* <n> */ + return ARG_NEW(SEARCH_SMALLER, 1); + } + break; + case 'T': + if (strcmp(str, "TEXT") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_TEXT, 1); + } else if (strcmp(str, "TO") == 0) { + /* <string> */ + return ARG_NEW(SEARCH_TO, 1); + } + break; + case 'U': + if (strcmp(str, "UID") == 0) { + /* <message set> */ + return ARG_NEW(SEARCH_UID, 1); + } else if (strcmp(str, "UNANSWERED") == 0) { + if (!ARG_NEW(SEARCH_ANSWERED, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNDELETED") == 0) { + if (!ARG_NEW(SEARCH_DELETED, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNDRAFT") == 0) { + if (!ARG_NEW(SEARCH_DRAFT, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNFLAGGED") == 0) { + if (!ARG_NEW(SEARCH_FLAGGED, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNKEYWORD") == 0) { + if (!ARG_NEW(SEARCH_KEYWORD, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } else if (strcmp(str, "UNSEEN") == 0) { + if (!ARG_NEW(SEARCH_SEEN, 0)) + return FALSE; + (*next_sarg)->not = TRUE; + return TRUE; + } + break; + default: + if (*str == '*' || (*str >= '0' && *str <= '9')) { + /* <message-set> */ + if (!ARG_NEW(SEARCH_SET, 0)) + return FALSE; + + (*next_sarg)->value.str = str; + return TRUE; + } + break; + } + + data->error = t_strconcat("Unknown argument ", str, NULL); + return FALSE; +} + +MailSearchArg *mail_search_args_build(Pool pool, ImapArg *args, int args_count, + const char **error) +{ + SearchBuildData data; + MailSearchArg *first_sarg, **sargs; + ImapArgList *list, **listp; + int i; + + /* first we need to conver the imap arguments into ImapArgList */ + list = NULL; listp = &list; + for (i = 0; i < args_count; i++) { + *listp = t_new(ImapArgList, 1); + memcpy(&(*listp)->arg, &args[i], sizeof(ImapArg)); + listp = &(*listp)->next; + } + + data.pool = pool; + data.error = NULL; + + /* get the first arg */ + first_sarg = NULL; sargs = &first_sarg; + while (list != NULL) { + if (!search_arg_build(&data, &list, sargs)) { + *error = data.error; + return NULL; + } + sargs = &(*sargs)->next; + } + + *error = NULL; + return first_sarg; +} + +void mail_search_args_reset(MailSearchArg *args) +{ + while (args != NULL) { + if (args->type == SEARCH_OR || args->type == SEARCH_SUB) + mail_search_args_reset(args->value.subargs); + args->result = 0; + + args = args->next; + } +} + +static void search_arg_foreach(MailSearchArg *arg, MailSearchForeachFunc func, + void *user_data) +{ + MailSearchArg *subarg; + + if (arg->result != 0) + return; + + if (arg->type == SEARCH_SUB) { + /* sublist of conditions */ + i_assert(arg->value.subargs != NULL); + + arg->result = 1; + subarg = arg->value.subargs; + while (subarg != NULL) { + if (subarg->result == 0) + search_arg_foreach(subarg, func, user_data); + + if (subarg->result == -1) { + /* failed */ + arg->result = -1; + break; + } + + if (subarg->result == 0) + arg->result = 0; + + subarg = subarg->next; + } + } else if (arg->type == SEARCH_OR) { + /* OR-list of conditions */ + i_assert(arg->value.subargs != NULL); + + subarg = arg->value.subargs; + arg->result = -1; + while (subarg != NULL) { + if (subarg->result == 0) + search_arg_foreach(subarg, func, user_data); + + if (subarg->result == 1) { + /* matched */ + arg->result = 1; + break; + } + + if (subarg->result == 0) + arg->result = 0; + + subarg = subarg->next; + } + } else { + /* just a single condition */ + func(arg, user_data); + } +} + +int mail_search_args_foreach(MailSearchArg *args, MailSearchForeachFunc func, + void *user_data) +{ + int result; + + result = 1; + for (; args != NULL; args = args->next) { + search_arg_foreach(args, func, user_data); + + if (args->result == -1) { + /* failed, abort */ + return -1; + } + + if (args->result == 0) + result = 0; + } + + return result; +} + +static void search_arg_analyze(MailSearchArg *arg, int *have_headers, + int *have_body, int *have_text) +{ + MailSearchArg *subarg; + + if (arg->result != 0) + return; + + switch (arg->type) { + case SEARCH_OR: + case SEARCH_SUB: + subarg = arg->value.subargs; + while (subarg != NULL) { + if (subarg->result == 0) { + search_arg_analyze(subarg, have_headers, + have_body, have_text); + } + + subarg = subarg->next; + } + break; + case SEARCH_FROM: + case SEARCH_TO: + case SEARCH_CC: + case SEARCH_BCC: + case SEARCH_SUBJECT: + case SEARCH_HEADER: + *have_headers = TRUE; + break; + case SEARCH_BODY: + *have_body = TRUE; + break; + case SEARCH_TEXT: + *have_text = TRUE; + break; + default: + break; + } +} + +void mail_search_args_analyze(MailSearchArg *args, int *have_headers, + int *have_body, int *have_text) +{ + *have_headers = *have_body = *have_text = FALSE; + + for (; args != NULL; args = args->next) + search_arg_analyze(args, have_headers, have_body, have_text); +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-search.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,84 @@ +#ifndef __MAIL_SEARCH_H +#define __MAIL_SEARCH_H + +#include "imap-parser.h" +#include "mail-storage.h" + +typedef enum { + SEARCH_OR, + SEARCH_SUB, + + /* message sets */ + SEARCH_ALL, + SEARCH_SET, + SEARCH_UID, + + /* flags */ + SEARCH_ANSWERED, + SEARCH_DELETED, + SEARCH_DRAFT, + SEARCH_FLAGGED, + SEARCH_SEEN, + SEARCH_RECENT, + SEARCH_KEYWORD, + + /* dates */ + SEARCH_BEFORE, + SEARCH_ON, + SEARCH_SINCE, + SEARCH_SENTBEFORE, + SEARCH_SENTON, + SEARCH_SENTSINCE, + + /* sizes */ + SEARCH_SMALLER, + SEARCH_LARGER, + + /* headers */ + SEARCH_FROM, + SEARCH_TO, + SEARCH_CC, + SEARCH_BCC, + SEARCH_SUBJECT, + SEARCH_HEADER, + + /* body */ + SEARCH_BODY, + SEARCH_TEXT +} MailSearchArgType; + +struct _MailSearchArg { + MailSearchArg *next; + + MailSearchArgType type; + union { + MailSearchArg *subargs; + const char *str; + } value; + + const char *hdr_value; /* for SEARCH_HEADER */ + unsigned int not:1; + + int result; +}; + +typedef void (*MailSearchForeachFunc)(MailSearchArg *arg, void *user_data); + +/* Builds search arguments based on IMAP arguments. */ +MailSearchArg *mail_search_args_build(Pool pool, ImapArg *args, int args_count, + const char **error); + +/* Reset the results in search arguments */ +void mail_search_args_reset(MailSearchArg *args); + +/* goes through arguments in list that don't have a result yet. + Returns 1 = search matched, -1 = search unmatched, 0 = don't know yet */ +int mail_search_args_foreach(MailSearchArg *args, MailSearchForeachFunc func, + void *user_data); + +/* Fills have_headers, have_body and have_text based on if such search + argument exists that needs to be checked. */ +void mail_search_args_analyze(MailSearchArg *args, int *have_headers, + int *have_body, int *have_text); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-storage.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,166 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "mail-storage.h" + +#include <ctype.h> + +typedef struct _MailStorageList MailStorageList; + +struct _MailStorageList { + MailStorageList *next; + MailStorage *storage; +}; + +static MailStorageList *storages = NULL; + +void mail_storage_class_register(MailStorage *storage_class) +{ + MailStorageList *list, **pos; + + list = i_new(MailStorageList, 1); + list->storage = storage_class; + + /* append it after the list, so the autodetection order is correct */ + pos = &storages; + while (*pos != NULL) + pos = &(*pos)->next; + *pos = list; +} + +void mail_storage_class_unregister(MailStorage *storage_class) +{ + MailStorageList **list, *next; + + for (list = &storages; *list != NULL; list = &(*list)->next) { + if ((*list)->storage == storage_class) { + next = (*list)->next; + + (*list)->storage->free((*list)->storage); + i_free(*list); + + *list = next; + } + } +} + +MailStorage *mail_storage_create(const char *name, const char *data) +{ + MailStorageList *list; + + i_assert(name != NULL); + + for (list = storages; list != NULL; list = list->next) { + if (strcasecmp(list->storage->name, name) == 0) + return list->storage->create(data); + } + + return NULL; +} + +MailStorage *mail_storage_create_default(void) +{ + MailStorageList *list; + MailStorage *storage; + + for (list = storages; list != NULL; list = list->next) { + storage = list->storage->create(NULL); + if (storage != NULL) + return storage; + } + + return NULL; +} + +static MailStorage *mail_storage_autodetect(const char *data) +{ + MailStorageList *list; + + for (list = storages; list != NULL; list = list->next) { + if (list->storage->autodetect(data)) + return list->storage; + } + + return NULL; +} + +MailStorage *mail_storage_create_with_data(const char *data) +{ + MailStorage *storage; + const char *p, *name; + + if (data == NULL || *data == '\0') + return mail_storage_create_default(); + + /* check if we're in the form of mailformat:data + (eg. maildir:Maildir) */ + p = data; + while (i_isalnum(*p)) p++; + + if (*p == ':') { + name = t_strndup(data, (unsigned int) (p-data)); + storage = mail_storage_create(name, p+1); + } else { + storage = mail_storage_autodetect(data); + if (storage != NULL) + storage = storage->create(data); + } + + return storage; +} + +void mail_storage_destroy(MailStorage *storage) +{ + i_assert(storage != NULL); + + i_free(storage->dir); + i_free(storage); +} + +void mail_storage_clear_error(MailStorage *storage) +{ + i_free(storage->error); + storage->error = NULL; +} + +void mail_storage_set_error(MailStorage *storage, const char *fmt, ...) +{ + va_list va; + + i_free(storage->error); + + if (fmt == NULL) + storage->error = NULL; + else { + va_start(va, fmt); + storage->error = i_strdup_vprintf(fmt, va); + va_end(va); + } +} + +void mail_storage_set_critical(MailStorage *storage, const char *fmt, ...) +{ + va_list va; + + i_free(storage->error); + + if (fmt == NULL) + storage->error = NULL; + else { + va_start(va, fmt); + storage->error = i_strdup_vprintf(fmt, va); + va_end(va); + + i_error("%s", storage->error); + } +} + +const char *mail_storage_get_last_error(MailStorage *storage) +{ + return storage->error; +} + +int mail_storage_is_inconsistency_error(Mailbox *box) +{ + return box->inconsistent; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/mail-storage.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,243 @@ +#ifndef __MAIL_STORAGE_H +#define __MAIL_STORAGE_H + +#include "imap-util.h" +#include "imap-parser.h" + +typedef enum { + MAILBOX_NOSELECT = 0x01, + MAILBOX_CHILDREN = 0x02, + MAILBOX_NOCHILDREN = 0x04, + MAILBOX_NOINFERIORS = 0x08, + MAILBOX_MARKED = 0x10, + MAILBOX_UNMARKED = 0x20, + + MAILBOX_READONLY = 0x40 +} MailboxFlags; + +typedef enum { + STATUS_MESSAGES = 0x01, + STATUS_RECENT = 0x02, + STATUS_UIDNEXT = 0x04, + STATUS_UIDVALIDITY = 0x08, + STATUS_UNSEEN = 0x10, + STATUS_FIRST_UNSEEN_SEQ = 0x20 +} MailboxStatusItems; + +typedef enum { + MAILBOX_NAME_EXISTS, + MAILBOX_NAME_VALID, + MAILBOX_NAME_INVALID +} MailboxNameStatus; + +typedef enum { + MODIFY_ADD, + MODIFY_REMOVE, + MODIFY_REPLACE +} ModifyType; + +typedef struct _MailStorage MailStorage; +typedef struct _Mailbox Mailbox; +typedef struct _MailboxStatus MailboxStatus; +typedef struct _MailFetchData MailFetchData; +typedef struct _MailFetchBodyData MailFetchBodyData; +typedef struct _MailSearchArg MailSearchArg; + +typedef void (*MailboxFunc)(MailStorage *storage, const char *name, + MailboxFlags flags, void *user_data); + +typedef void (*MailExpungeFunc)(Mailbox *mailbox, unsigned int seq, + unsigned int uid, void *user_data); +typedef void (*MailFlagUpdateFunc)(Mailbox *mailbox, unsigned int seq, + unsigned int uid, MailFlags flags, + const char *custom_flags[], + void *user_data); + +/* All methods returning int return either TRUE or FALSE. */ +struct _MailStorage { + char *name; + + char hierarchy_sep; + + /* Create new instance */ + MailStorage *(*create)(const char *data); + + /* Free this instance */ + void (*free)(MailStorage *storage); + + /* Returns TRUE if this storage would accept the given data + as a valid parameter to create(). */ + int (*autodetect)(const char *data); + + /* Open a mailbox. If readonly is TRUE, mailbox must not be + modified in any way even when it's asked. */ + Mailbox *(*open_mailbox)(MailStorage *storage, const char *name, + int readonly); + + /* name is allowed to contain multiple new hierarchy levels */ + int (*create_mailbox)(MailStorage *storage, const char *name); + int (*delete_mailbox)(MailStorage *storage, const char *name); + /* If the name has inferior hierarchical names, then the inferior + hierarchical names MUST also be renamed (ie. foo -> bar renames + also foo/bar -> bar/bar). + + If oldname is case-insensitively "INBOX", the mails are moved + into new folder but the INBOX folder must not be deleted. */ + int (*rename_mailbox)(MailStorage *storage, const char *oldname, + const char *newname); + + /* Execute specified function for all mailboxes matching given + mask. The mask is in RFC2060 LIST format. */ + int (*find_mailboxes)(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data); + + /* Subscribe/unsubscribe mailbox. There should be no error when + subscribing to already subscribed mailbox. Subscribing to + unexisting mailboxes is optional. */ + int (*set_subscribed)(MailStorage *storage, const char *name, int set); + + /* Exactly like find_mailboxes(), but list only subscribed mailboxes. */ + int (*find_subscribed)(MailStorage *storage, const char *mask, + MailboxFunc func, void *user_data); + + /* Returns mailbox name status */ + int (*get_mailbox_name_status)(MailStorage *storage, const char *name, + MailboxNameStatus *status); + + /* Returns the error message of last occured error. */ + const char *(*get_last_error)(MailStorage *storage); + +/* private: */ + char *dir; /* root directory */ + char *error; +}; + +struct _Mailbox { + char *name; + + MailStorage *storage; + + /* Close the box */ + void (*close)(Mailbox *box); + + /* Gets the mailbox status information. */ + int (*get_status)(Mailbox *box, MailboxStatusItems items, + MailboxStatus *status); + + /* Synchronize the mailbox by reading all expunges and flag changes. + If new mail has been added to mailbox, messages contains the total + number of messages in mailbox, otherwise 0. functions may be NULL. + + If expunge is TRUE, deleted mails are expunged as well. Difference + to expunge() function is that expunge_func is also called. */ + int (*sync)(Mailbox *box, unsigned int *messages, int expunge, + MailExpungeFunc expunge_func, MailFlagUpdateFunc flag_func, + void *user_data); + + /* Expunge all mails with \Deleted flag. */ + int (*expunge)(Mailbox *box); + + /* Update mail flags. func may be NULL. */ + int (*update_flags)(Mailbox *box, const char *messageset, int uidset, + MailFlags flags, const char *custom_flags[], + ModifyType modify_type, + MailFlagUpdateFunc func, void *user_data, + int *all_found); + + /* Copy mails to another mailbox */ + int (*copy)(Mailbox *box, Mailbox *destbox, + const char *messageset, int uidset); + + /* Fetch wanted mail data. The results are written into outbuf + in RFC2060 FETCH format. */ + int (*fetch)(Mailbox *box, MailFetchData *fetch_data, + IOBuffer *outbuf, int *all_found); + + /* Search wanted mail data. args contains the search criteria. + results are written into outbuf in RFC2060 SEARCH format. */ + int (*search)(Mailbox *box, MailSearchArg *args, + IOBuffer *outbuf, int uid_result); + + /* Save a new mail into mailbox. */ + int (*save)(Mailbox *box, MailFlags flags, const char *custom_flags[], + time_t internal_date, IOBuffer *data, size_t data_size); + + /* Returns TRUE if mailbox is now in inconsistent state, meaning that + the message IDs etc. may have changed - only way to recover this + would be to fully close the mailbox and reopen it. With IMAP + connection this would mean a forced disconnection since we can't + do forced CLOSE. */ + int (*is_inconsistency_error)(Mailbox *box); + +/* private: */ + unsigned int readonly:1; + unsigned int inconsistent:1; +}; + +struct _MailboxStatus { + unsigned int messages; + unsigned int recent; + unsigned int unseen; + + unsigned int uidvalidity; + unsigned int uidnext; + + unsigned int first_unseen_seq; +}; + +struct _MailFetchData { + const char *messageset; + unsigned int uidset:1; + + unsigned int body:1; + unsigned int bodystructure:1; + unsigned int envelope:1; + unsigned int flags:1; + unsigned int internaldate:1; + unsigned int rfc822:1; + unsigned int rfc822_header:1; + unsigned int rfc822_size:1; + unsigned int rfc822_text:1; + unsigned int uid:1; + + MailFetchBodyData *body_sections; +}; + +struct _MailFetchBodyData { + MailFetchBodyData *next; + + const char *section; /* NOTE: always uppercased */ + off_t skip; + size_t max_size; + unsigned int skip_set:1; + unsigned int peek:1; +}; + +/* register all mail storages */ +void mail_storage_register_all(void); + +/* Register mail storage class with given name - all methods that are NULL + are set to default methods */ +void mail_storage_class_register(MailStorage *storage_class); +void mail_storage_class_unregister(MailStorage *storage_class); + +/* Create a new instance of registered mail storage class with given + storage-specific data. If data is NULL, it tries to use defaults. + May return NULL if anything fails. */ +MailStorage *mail_storage_create(const char *name, const char *data); +void mail_storage_destroy(MailStorage *storage); + +MailStorage *mail_storage_create_default(void); +MailStorage *mail_storage_create_with_data(const char *data); + +/* Set error message in storage. Critical errors are logged with syslog() */ +void mail_storage_clear_error(MailStorage *storage); +void mail_storage_set_error(MailStorage *storage, const char *fmt, ...) + __attr_format__(2, 3); +void mail_storage_set_critical(MailStorage *storage, const char *fmt, ...) + __attr_format__(2, 3); + +const char *mail_storage_get_last_error(MailStorage *storage); +int mail_storage_is_inconsistency_error(Mailbox *box); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/subscription-file/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/subscription-file/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,13 @@ +noinst_LIBRARIES = libstorage_subscription_file.a + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -I$(top_srcdir)/src/lib-storage \ + -I$(top_srcdir)/src/lib-imap + +libstorage_subscription_file_a_SOURCES = \ + subscription-file.c + +noinst_HEADERS = \ + subscription-file.h +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/subscription-file/subscription-file.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,219 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +/* ugly code here - text files are annoying to manage */ + +#include "lib.h" +#include "mmap-util.h" +#include "imap-match.h" +#include "mail-storage.h" +#include "subscription-file.h" + +#include <unistd.h> +#include <fcntl.h> + +#define SUBSCRIPTION_FILE_NAME ".subscriptions" + +static int lock_file(int fd, int type) +{ + struct flock fl; + + /* lock whole file */ + fl.l_type = type; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + while (fcntl(fd, F_SETLKW, &fl) == -1) { + if (errno != EINTR) + return FALSE; + } + + return TRUE; +} + +static int subscription_open(MailStorage *storage, int update, + const char **path, void **mmap_base, + size_t *mmap_length) +{ + int fd; + + *path = t_strconcat(storage->dir, "/" SUBSCRIPTION_FILE_NAME, NULL); + + fd = update ? open(*path, O_RDWR | O_CREAT, 0660) : + open(*path, O_RDONLY); + if (fd == -1) { + if (update || errno != ENOENT) { + mail_storage_set_critical(storage, "Can't open " + "subscription file %s: %m", + *path); + } + return -1; + } + + if (!lock_file(fd, update ? F_WRLCK : F_RDLCK)) { + mail_storage_set_critical(storage, "fcntl() failed for " + "subscription file %s: %m", *path); + (void)close(fd); + return -1; + } + + *mmap_base = update ? mmap_rw_file(fd, mmap_length) : + mmap_ro_file(fd, mmap_length); + if (*mmap_base == MAP_FAILED) { + *mmap_base = NULL; + mail_storage_set_critical(storage, "mmap() failed for " + "subscription file %s: %m", *path); + (void)close(fd); + return -1; + } + + (void)madvise(*mmap_base, *mmap_length, MADV_SEQUENTIAL); + return fd; +} + +static int subscription_append(MailStorage *storage, int fd, const char *name, + unsigned int len, int prefix_lf, + const char *path) +{ + char *buf; + + if (lseek(fd, 0, SEEK_END) == (off_t)-1) { + mail_storage_set_critical(storage, "lseek() failed for " + "subscription file %s: %m", path); + return FALSE; + } + + buf = t_buffer_get(len+2); + buf[0] = '\n'; + memcpy(buf+1, name, len); + buf[len+1] = '\n'; + + if (prefix_lf) + len += 2; + else { + buf++; + len++; + } + + if ((size_t) write(fd, buf, len) != len) { + mail_storage_set_critical(storage, "write() failed for " + "subscription file %s: %m", path); + return FALSE; + } + + return TRUE; +} + +int subsfile_set_subscribed(MailStorage *storage, const char *name, int set) +{ + void *mmap_base; + size_t mmap_length; + const char *path; + char *subscriptions, *end, *p; + unsigned int namelen, afterlen, removelen; + int fd, failed, prefix_lf; + + if (strcasecmp(name, "INBOX") == 0) + name = "INBOX"; + + fd = subscription_open(storage, TRUE, &path, &mmap_base, &mmap_length); + if (fd == -1) + return FALSE; + + namelen = strlen(name); + + subscriptions = mmap_base; + if (subscriptions == NULL) + p = NULL; + else { + end = subscriptions + mmap_length; + for (p = subscriptions; p != end; p++) { + if (*p == *name && p+namelen <= end && + strncmp(p, name, namelen) == 0) { + /* make sure beginning and end matches too */ + if ((p == subscriptions || p[-1] == '\n') && + (p+namelen == end || p[namelen] == '\n')) + break; + } + } + + if (p == end) + p = NULL; + } + + failed = FALSE; + if (p != NULL && !set) { + /* remove it */ + afterlen = mmap_length - (unsigned int) (p - subscriptions); + removelen = namelen < afterlen ? namelen+1 : namelen; + + if (removelen < afterlen) + memmove(p, p+removelen, afterlen-removelen); + + if (ftruncate(fd, (off_t) (mmap_length - removelen)) == -1) { + mail_storage_set_critical(storage, "ftruncate() " + "failed for subscription " + "file %s: %m", path); + failed = TRUE; + } + } else if (p == NULL && set) { + /* append it */ + prefix_lf = mmap_length > 0 && + subscriptions[mmap_length-1] != '\n'; + if (!subscription_append(storage, fd, name, namelen, + prefix_lf, path)) + failed = TRUE; + } + + if (mmap_base != NULL && munmap(mmap_base, mmap_length) == -1) { + mail_storage_set_critical(storage, "munmap() failed for " + "subscription file %s: %m", path); + failed = TRUE; + } + + if (close(fd) == -1) { + mail_storage_set_critical(storage, "close() failed for " + "subscription file %s: %m", path); + failed = TRUE; + } + return !failed; +} + +int subsfile_foreach(MailStorage *storage, const char *mask, + SubsFileForeachFunc func, void *user_data) +{ + const ImapMatchGlob *glob; + const char *path, *start, *end, *p, *line; + void *mmap_base; + size_t mmap_length; + int fd, ret; + + fd = subscription_open(storage, FALSE, &path, &mmap_base, &mmap_length); + if (fd == -1) + return -1; + + glob = imap_match_init(mask, TRUE, storage->hierarchy_sep); + + start = mmap_base; end = start + mmap_length; ret = 1; + while (ret) { + t_push(); + + for (p = start; p != end; p++) { + if (*p == '\n') + break; + } + + line = t_strndup(start, (unsigned int) (p-start)); + if (line != NULL && *line != '\0' && + imap_match(glob, line, 0, NULL) >= 0) + ret = func(storage, line, user_data); + t_pop(); + + if (p == end) + break; + start = p+1; + } + + (void)close(fd); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-storage/subscription-file/subscription-file.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,16 @@ +#ifndef __SUBSCRIPTION_FILE_H +#define __SUBSCRIPTION_FILE_H + +#include "mail-storage.h" + +/* Returns FALSE if foreach should be aborted */ +typedef int (*SubsFileForeachFunc)(MailStorage *storage, const char *name, + void *user_data); + +int subsfile_set_subscribed(MailStorage *storage, const char *name, int set); + +/* Returns -1 if error, 0 if foreach function returned FALSE or 1 if all ok */ +int subsfile_foreach(MailStorage *storage, const char *mask, + SubsFileForeachFunc func, void *user_data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,73 @@ +noinst_LIBRARIES = liblib.a + +if IOLOOP_POLL +ioloop_source = ioloop-poll.c +else +ioloop_source = ioloop-select.c +endif + +ioloop_sources = \ + ioloop-poll.c \ + ioloop-select.c + +liblib_a_SOURCES = \ + base64.c \ + compat.c \ + failures.c \ + fdpass.c \ + gmtoff.c \ + hash.c \ + hex-binary.c \ + hostpid.c \ + imem.c \ + iobuffer.c \ + ioloop.c \ + $(ioloop_source) \ + lib.c \ + lib-signals.c \ + md5.c \ + mempool.c \ + mempool-alloconly.c \ + mempool-system.c \ + mmap-util.c \ + network.c \ + primes.c \ + randgen.c \ + restrict-access.c \ + strfuncs.c \ + temp-mempool.c \ + temp-string.c \ + unlink-directory.c \ + unlink-lockfiles.c + +noinst_HEADERS = \ + base64.h \ + compat.h \ + failures.h \ + fdpass.h \ + gmtoff.h \ + hash.h \ + hex-binary.h \ + hostpid.h \ + imem.h \ + iobuffer.h \ + ioloop.h \ + ioloop-internal.h \ + lib.h \ + lib-signals.h \ + macros.h \ + md5.h \ + mempool.h \ + mmap-util.h \ + network.h \ + primes.h \ + randgen.h \ + restrict-access.h \ + strfuncs.h \ + temp-mempool.h \ + temp-string.h \ + unlink-directory.h \ + unlink-lockfiles.h + +EXTRA_DIST = \ + $(ioloop_sources)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/base64.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,145 @@ +/* Based on the sources of Cyrus IMAP: + * + * Copyright (c) 2000 Carnegie Mellon University. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The name "Carnegie Mellon University" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For permission or any other legal + * details, please contact + * Office of Technology Transfer + * Carnegie Mellon University + * 5000 Forbes Avenue + * Pittsburgh, PA 15213-3890 + * (412) 268-4387, fax: (412) 268-7395 + * tech-transfer@andrew.cmu.edu + * + * 4. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by Computing Services + * at Carnegie Mellon University (http://www.cmu.edu/computing/)." + * + * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO + * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE + * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "lib.h" +#include "base64.h" + +static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +const char *base64_encode(const unsigned char *data, unsigned int size) +{ + char *buffer, *p; + int c1, c2, c3; + + buffer = p = t_malloc(size*2 + 5); + while (size > 0) { + c1 = *data++; size--; + *p++ = basis_64[c1 >> 2]; + + c2 = size == 0 ? 0 : *data++; + *p++ = basis_64[((c1 & 0x03) << 4) | ((c2 & 0xf0) >> 4)]; + if (size-- == 0) { + *p++ = '='; + *p++ = '='; + break; + } + + c3 = size == 0 ? 0 : *data++; + *p++ = basis_64[((c2 & 0x0f) << 2) | ((c3 & 0xc0) >> 6)]; + if (size-- == 0) { + *p++ = '='; + break; + } + + *p++ = basis_64[c3 & 0x3f]; + } + + *p = '\0'; + return buffer; +} + +#define XX 127 + +/* Table for decoding base64 */ +static const char index_64[256] = { + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63, + 52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, + XX, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + 15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, + XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + 41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, + XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, +}; +#define CHAR64(c) (index_64[(int)(unsigned char)(c)]) + +int base64_decode(char *data) +{ + char *p, *start; + int c1, c2, c3, c4; + + p = start = data; + while (*data != '\0') { + c1 = *data++; + + if (CHAR64(c1) == XX) + return -1; + + c2 = *data++; + if (CHAR64(c2) == XX) + return -1; + + c3 = *data++; + if (c3 != '=' && CHAR64(c3) == XX) + return -1; + + c4 = *data++; + if (c4 != '=' && CHAR64(c4) == XX) + return -1; + + *p++ = ((CHAR64(c1) << 2) | ((CHAR64(c2) & 0x30) >> 4)); + + if (c3 == '=') { + if (*data != '\0' || c4 != '=') + return -1; + break; + } + + *p++ = (((CHAR64(c2) & 0xf) << 4) | ((CHAR64(c3) & 0x3c) >> 2)); + if (c4 == '=') { + if (*data != '\0') + return -1; + break; + } + *p++ = (((CHAR64(c3) & 0x3) << 6) | CHAR64(c4)); + } + + return (int) (p-start); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/base64.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,11 @@ +#ifndef __BASE64_H +#define __BASE64_H + +/* Translates binary data into base64. Allocates memory from temporary pool. */ +const char *base64_encode(const unsigned char *data, unsigned int size); + +/* Translates base64 data into binary modifying the data itself. + Returns size of the binary data, or -1 if error occured. */ +int base64_decode(char *data); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/compat.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,122 @@ +/* + compat.c : Compatibility functions for OSes not having them + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <ctype.h> +#include <syslog.h> + +#include "lib.h" + +#ifndef INADDR_NONE +# define INADDR_NONE INADDR_BROADCAST +#endif + +#ifndef HAVE_MEMMOVE +void *my_memmove(void *dest, const void *src, size_t size) +{ + char *destp = dest; + const char *srcp = src; + + if (destp < srcp) { + /* dest = 1234, src=234 */ + destp = dest; + srcp = src; + while (size > 0) { + *destp++ = *srcp++; + size--; + } + } else if (destp > srcp) { + /* dest = 234, src=123 */ + destp += size-1; + srcp += size-1; + while (size > 0) { + *destp-- = *srcp--; + size--; + } + } + + return dest; +} +#endif + +#if !defined (HAVE_STRCASECMP) && !defined (HAVE_STRICMP) +int my_strcasecmp(const char *s1, const char *s2) +{ + while (*s1 != '\0' && i_toupper(*s1) == i_toupper(*s2)) { + s1++; s2++; + } + + return i_toupper(*s1) - i_toupper(*s2); +} + +int my_strncasecmp(const char *s1, const char *s2, size_t max_chars) +{ + while (max_chars > 0 && *s1 != '\0' && + i_toupper(*s1) == i_toupper(*s2)) { + s1++; s2++; + } + + return i_toupper(*s1) - i_toupper(*s2); +} +#endif + +#ifndef HAVE_INET_ATON +int my_inet_aton(const char *cp, struct in_addr *inp) +{ + in_addr_t addr; + + addr = inet_addr(cp); + if (addr == INADDR_NONE) + return 0; + + inp->s_addr = addr; + return 1; +} +#endif + +#ifndef HAVE_VSYSLOG +void my_vsyslog(int priority, const char *format, va_list args) +{ + const char *str; + char buf[1024]; + +#ifdef HAVE_VSNPRINTF + vsnprintf(buf, sizeof(buf), format, args); + str = buf; +#else + va_list args2; + + VA_COPY(args2, args); + + if (printf_string_upper_bound(format, args) < sizeof(buf)) { + vsprintf(buf, format, args); + str = buf; + } else { + /* this may not be safe but not choice really.. */ + str = t_strdup_vprintf(format, args2); + } +#endif + syslog(priority, "%s", str); +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/compat.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,54 @@ +#ifndef __COMPAT_H +#define __COMPAT_H + +/* memmove() */ +#ifndef HAVE_MEMMOVE +# define memmove my_memmove +void *my_memmove(void *dest, const void *src, size_t n); +#endif + +/* strcasecmp(), strncasecmp() */ +#ifndef HAVE_STRCASECMP +# ifdef HAVE_STRICMP +# define strcasecmp stricmp +# define strncasecmp strnicmp +# else +# define strcasecmp my_strcasecmp +# define strncasecmp my_strncasecmp +int my_strcasecmp(const char *s1, const char *s2); +int my_strncasecmp(const char *s1, const char *s2, size_t max_chars); +# endif +#endif + +#ifndef HAVE_INET_ATON +# include <sys/socket.h> +# include <netinet/in.h> +# include <arpa/inet.h> +# define inet_aton my_inet_aton +int my_inet_aton(const char *cp, struct in_addr *inp); +#endif + +#ifndef HAVE_VSYSLOG +# define vsyslog my_vsyslog +void my_vsyslog(int priority, const char *format, va_list args); +#endif + +/* ctype.h isn't safe with signed chars, + use our own instead if really needed */ +#define i_toupper(x) toupper((int) (unsigned char) (x)) +#define i_tolower(x) tolower((int) (unsigned char) (x)) +#define i_isalnum(x) isalnum((int) (unsigned char) (x)) +#define i_isalpha(x) isalpha((int) (unsigned char) (x)) +#define i_isascii(x) isascii((int) (unsigned char) (x)) +#define i_isblank(x) isblank((int) (unsigned char) (x)) +#define i_iscntrl(x) iscntrl((int) (unsigned char) (x)) +#define i_isdigit(x) isdigit((int) (unsigned char) (x)) +#define i_isgraph(x) isgraph((int) (unsigned char) (x)) +#define i_islower(x) islower((int) (unsigned char) (x)) +#define i_isprint(x) isprint((int) (unsigned char) (x)) +#define i_ispunct(x) ispunct((int) (unsigned char) (x)) +#define i_isspace(x) isspace((int) (unsigned char) (x)) +#define i_isupper(x) isupper((int) (unsigned char) (x)) +#define i_isxdigit(x) isxdigit((int) (unsigned char) (x)) + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/failures.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,228 @@ +/* + failures.c : Failure manager + + Copyright (c) 2001-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +#include "lib.h" +#include "ioloop.h" + +#include <stdio.h> +#include <syslog.h> +#include <time.h> + +static FailureFunc panic_handler __attr_noreturn__, fatal_handler __attr_noreturn__; +static FailureFunc error_handler, warning_handler; + +static FILE *log_fd; +static char *log_prefix, *log_stamp_format; + +static void default_panic_handler(const char *format, va_list args) + __attr_noreturn__; +static void default_fatal_handler(const char *format, va_list args) + __attr_noreturn__; + +static void write_prefix(void) +{ + struct tm *tm; + char str[256]; + + if (log_prefix != NULL) + fputs(log_prefix, log_fd); + + if (log_stamp_format != NULL) { + tm = localtime(&ioloop_time); + + if (strftime(str, sizeof(str), log_stamp_format, tm) > 0) + fputs(str, log_fd); + } +} + +static void default_panic_handler(const char *format, va_list args) +{ + write_prefix(); + + fputs("Panic: ", log_fd); + vfprintf(log_fd, format, args); + fputc('\n', log_fd); + + abort(); +} + +static void default_fatal_handler(const char *format, va_list args) +{ + write_prefix(); + + fputs("Fatal: ", log_fd); + vfprintf(log_fd, format, args); + fputc('\n', log_fd); + + exit(98); +} + +static void default_error_handler(const char *format, va_list args) +{ + write_prefix(); + + fputs("Error: ", log_fd); + vfprintf(log_fd, format, args); + fputc('\n', log_fd); + + fflush(log_fd); +} + +static void default_warning_handler(const char *format, va_list args) +{ + write_prefix(); + + fputs("Warning: ", log_fd); + vfprintf(log_fd, format, args); + fputc('\n', log_fd); + + fflush(log_fd); +} + +void i_panic(const char *format, ...) +{ + va_list args; + + va_start(args, format); + panic_handler(format, args); + va_end(args); +} + +void i_fatal(const char *format, ...) +{ + va_list args; + + va_start(args, format); + fatal_handler(format, args); + va_end(args); +} + +void i_error(const char *format, ...) +{ + va_list args; + + va_start(args, format); + error_handler(format, args); + va_end(args); +} + +void i_warning(const char *format, ...) +{ + va_list args; + + va_start(args, format); + warning_handler(format, args); + va_end(args); +} + +void i_set_panic_handler(FailureFunc func __attr_noreturn__) +{ + if (func == NULL) + func = default_panic_handler; + panic_handler = func; +} + +void i_set_fatal_handler(FailureFunc func __attr_noreturn__) +{ + if (func == NULL) + func = default_fatal_handler; + fatal_handler = func; +} + +void i_set_error_handler(FailureFunc func) +{ + if (func == NULL) + func = default_error_handler; + error_handler = func; +} + +void i_set_warning_handler(FailureFunc func) +{ + if (func == NULL) + func = default_warning_handler; + warning_handler = func; +} + +void i_syslog_panic_handler(const char *fmt, va_list args) +{ + vsyslog(LOG_CRIT, fmt, args); + abort(); +} + +void i_syslog_fatal_handler(const char *fmt, va_list args) +{ + vsyslog(LOG_CRIT, fmt, args); + exit(98); +} + +void i_syslog_error_handler(const char *fmt, va_list args) +{ + vsyslog(LOG_ERR, fmt, args); +} + +void i_syslog_warning_handler(const char *fmt, va_list args) +{ + vsyslog(LOG_WARNING, fmt, args); +} + +void i_set_failure_file(const char *path, const char *prefix) +{ + if (log_fd != stderr) + (void)fclose(log_fd); + + log_fd = fopen(path, "a"); + if (log_fd == NULL) + i_fatal("Can't open log file %s: %m", path); + + i_free(log_prefix); + log_prefix = i_strconcat(prefix, ": ", NULL); +} + +void i_set_failure_timestamp_format(const char *fmt) +{ + i_free(log_stamp_format); + log_stamp_format = i_strdup(fmt); +} + +void failures_init(void) +{ + log_fd = stderr; + log_prefix = NULL; + log_stamp_format = NULL; + + i_set_panic_handler(NULL); + i_set_fatal_handler(NULL); + i_set_error_handler(NULL); + i_set_warning_handler(NULL); +} + +void failures_deinit(void) +{ + if (log_fd != stderr) { + (void)fclose(log_fd); + log_fd = stderr; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/failures.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,33 @@ +#ifndef __FAILURES_H +#define __FAILURES_H + +#define DEFAULT_FAILURE_STAMP_FORMAT "%b %d %H:%M:%S " + +typedef void (*FailureFunc) (const char *, va_list); + +void i_panic(const char *format, ...) __attr_format__(1, 2) __attr_noreturn__; +void i_fatal(const char *format, ...) __attr_format__(1, 2) __attr_noreturn__; +void i_error(const char *format, ...) __attr_format__(1, 2); +void i_warning(const char *format, ...) __attr_format__(1, 2); + +void i_set_panic_handler(FailureFunc func __attr_noreturn__); +void i_set_fatal_handler(FailureFunc func __attr_noreturn__); +void i_set_error_handler(FailureFunc func); +void i_set_warning_handler(FailureFunc func); + +/* send failures to syslog() */ +void i_syslog_panic_handler(const char *fmt, va_list args) __attr_noreturn__; +void i_syslog_fatal_handler(const char *fmt, va_list args) __attr_noreturn__; +void i_syslog_error_handler(const char *fmt, va_list args); +void i_syslog_warning_handler(const char *fmt, va_list args); + +/* send failures to specified log file instead of stderr. */ +void i_set_failure_file(const char *path, const char *prefix); + +/* prefix failures with a timestamp. fmt is in strftime() format. */ +void i_set_failure_timestamp_format(const char *fmt); + +void failures_init(void); +void failures_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/fdpass.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,95 @@ +/* + fdpass.c - FD passing, based on an example by + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifdef __sun__ +# define _XPG4_2 +#endif + +#include "lib.h" +#include "network.h" +#include "fdpass.h" + +#include <sys/un.h> +#include <sys/uio.h> + +#if defined (__sun__) && !defined(CMSG_SPACE) +# define CMSG_ALIGN(len) \ + (((len) + sizeof(size_t) - 1) & ~(sizeof(size_t) - 1)) +# define CMSG_SPACE(len) \ + (CMSG_ALIGN(len) + CMSG_ALIGN(sizeof(struct cmsghdr))) +# define CMSG_LEN(len) \ + (CMSG_ALIGN(sizeof(struct cmsghdr)) + (len)) +#endif + +int fd_send(int handle, int send_fd, const void *data, int size) +{ + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + int *fdptr; + char buf[CMSG_SPACE(sizeof(int))]; + + memset(&msg, 0, sizeof (struct msghdr)); + + iov.iov_base = (void *) data; + iov.iov_len = size; + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + fdptr = (int *) CMSG_DATA(cmsg); + *fdptr = send_fd; + return sendmsg(handle, &msg, 0); +} + +int fd_read(int handle, void *data, int size, int *fd) +{ + struct msghdr msg; + struct iovec iov; + struct cmsghdr *cmsg; + int ret; + char buf[CMSG_SPACE(sizeof(int))]; + + memset(&msg, 0, sizeof (struct msghdr)); + + msg.msg_control = buf; + msg.msg_controllen = sizeof(buf); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + iov.iov_base = (void *) data; + iov.iov_len = size; + + ret = recvmsg(handle, &msg, 0); + *fd = *(int *) CMSG_DATA(cmsg); + return ret; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/fdpass.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,11 @@ +#ifndef __FDPASS_H +#define __FDPASS_H + +/* Returns number of bytes sent, -1 if error. */ +int fd_send(int handle, int send_fd, const void *data, int size); + +/* Returns number of bytes read, or -1 if error. fd is set only + if return value is larger than 0. */ +int fd_read(int handle, void *data, int size, int *fd); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/gmtoff.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,61 @@ +/* + compat.c : Compatibility functions for OSes not having them + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "gmtoff.h" + +#include <time.h> + +#undef HAVE_TM_GMTOFF + +int gmtoff(struct tm *tm, time_t t __attr_unused__) +{ +#ifdef HAVE_TM_GMTOFF + return tm->tm_gmtoff; +#else + struct tm ltm, gtm; + int offset; + + /* gmtime() overwrites tm, so we need to copy it elsewhere */ + ltm = *tm; + tm = gmtime(&t); + gtm = *tm; + + /* max offset of 24 hours */ + if (ltm.tm_yday < gtm.tm_yday) + offset = -24 * 3600; + else if (ltm.tm_yday > gtm.tm_yday) + offset = 24 * 3600; + else + offset = 0; + + offset += (ltm.tm_hour - gtm.tm_hour) * 3600; + offset += (ltm.tm_min - gtm.tm_min) * 60; + + /* restore overwritten tm */ + *tm = ltm; + return offset; +#endif +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/gmtoff.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,9 @@ +#ifndef __GMTOFF_H +#define __GMTOFF_H + +#include <time.h> + +/* Returns GMT offset in seconds. */ +int gmtoff(struct tm *tm, time_t t); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/hash.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,412 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GLib Team and others 1997-1999. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +/* several modifications Copyright (C) 2002 by Timo Sirainen */ + +#include <ctype.h> + +#include "lib.h" +#include "hash.h" +#include "primes.h" + +#define HASH_TABLE_MIN_SIZE 11 +#define HASH_TABLE_MAX_SIZE 13845163 + +typedef struct _HashNode { + void *key; + void *value; + + int destroyed; + struct _HashNode *next; +} HashNode; + +struct _HashTable { + Pool pool; + + unsigned int size; + unsigned int nodes_count, nodes_destroyed; + int frozen; + HashNode **nodes; + + HashFunc hash_func; + HashCompareFunc key_compare_func; +}; + +static void hash_cleanup(HashTable *table); +static int hash_resize(HashTable *table); + +static int foreach_stop; + +static unsigned int direct_hash(const void *p) +{ + /* NOTE: may truncate the value, but that doesn't matter. */ + return POINTER_TO_UINT(p); +} + +static HashNode *hash_node_create(Pool pool, const void *key, + const void *value) +{ + HashNode *node; + + node = p_new(pool, HashNode, 1); + node->key = (void *) key; + node->value = (void *) value; + + return node; +} + +static void hash_nodes_destroy(HashTable *table, HashNode *node) +{ + HashNode *next; + + while (node != NULL) { + next = node->next; + p_free(table->pool, node); + node = next; + } +} + +HashTable *hash_create(Pool pool, unsigned int initial_size, + HashFunc hash_func, HashCompareFunc key_compare_func) +{ + HashTable *table; + + i_assert(pool != NULL); + + table = p_new(pool, HashTable, 1); + table->pool = pool; + table->size = CLAMP(primes_closest(initial_size), + HASH_TABLE_MIN_SIZE, + HASH_TABLE_MAX_SIZE); + + table->hash_func = hash_func != NULL ? hash_func : direct_hash; + table->key_compare_func = key_compare_func; + table->nodes = p_new(pool, HashNode *, table->size); + + return table; +} + +void hash_destroy(HashTable *table) +{ + unsigned int i; + + if (table == NULL) + return; + + for (i = 0; i < table->size; i++) + hash_nodes_destroy(table, table->nodes[i]); + + p_free(table->pool, table->nodes); + p_free(table->pool, table); +} + +void hash_clear(HashTable *table) +{ + unsigned int i; + + i_assert(table != NULL); + + for (i = 0; i < table->size; i++) { + hash_nodes_destroy(table, table->nodes[i]); + table->nodes[i] = NULL; + } +} + +static inline HashNode ** +hash_lookup_node(HashTable *table, const void *key) +{ + HashNode **node; + + node = &table->nodes[table->hash_func(key) % table->size]; + + /* Hash table lookup needs to be fast. + We therefore remove the extra conditional of testing + whether to call the key_compare_func or not from + the inner loop. */ + if (table->key_compare_func) { + while (*node != NULL) { + if (!(*node)->destroyed && + table->key_compare_func((*node)->key, key) == 0) + break; + node = &(*node)->next; + } + } else { + while (*node != NULL && (*node)->key != key) + node = &(*node)->next; + } + + return node; +} + +void *hash_lookup(HashTable *table, const void *key) +{ + HashNode *node; + + i_assert(table != NULL); + + node = *hash_lookup_node(table, key); + return node != NULL && !node->destroyed ? node->value : NULL; +} + +int hash_lookup_full(HashTable *table, const void *lookup_key, + void **orig_key, void **value) +{ + HashNode *node; + + i_assert(table != NULL); + + node = *hash_lookup_node(table, lookup_key); + if (node == NULL || node->destroyed) + return FALSE; + + if (orig_key != NULL) + *orig_key = node->key; + if (value != NULL) + *value = node->value; + return TRUE; +} + +static void hash_insert_full(HashTable *table, const void *key, + const void *value, int replace_key) +{ + HashNode **node; + + i_assert(table != NULL); + + node = hash_lookup_node(table, key); + if (*node == NULL) { + *node = hash_node_create(table->pool, key, value); + + table->nodes_count++; + if (!table->frozen) + hash_resize(table); + } else { + if (replace_key || (*node)->destroyed) { + (*node)->key = (void *) key; + (*node)->destroyed = FALSE; + } + + (*node)->value = (void *) value; + } +} + +void hash_insert(HashTable *table, const void *key, const void *value) +{ + hash_insert_full(table, key, value, TRUE); +} + +void hash_update(HashTable *table, const void *key, const void *value) +{ + hash_insert_full(table, key, value, FALSE); +} + +void hash_remove(HashTable *table, const void *key) +{ + HashNode **node, *old_node; + + i_assert(table != NULL); + + node = hash_lookup_node(table, key); + if (*node != NULL && !(*node)->destroyed) { + table->nodes_count--; + + if (table->frozen) { + (*node)->destroyed = TRUE; + table->nodes_destroyed++; + } else { + old_node = *node; + *node = old_node->next; + p_free(table->pool, old_node); + + hash_resize(table); + } + } +} + +void hash_freeze(HashTable *table) +{ + i_assert(table != NULL); + + table->frozen++; +} + +void hash_thaw(HashTable *table) +{ + i_assert(table != NULL); + i_assert(table->frozen > 0); + + if (--table->frozen == 0) + hash_cleanup(table); +} + +void hash_foreach(HashTable *table, HashForeachFunc func, void *user_data) +{ + HashNode *node; + unsigned int i; + + i_assert(table != NULL); + i_assert(func != NULL); + + hash_freeze(table); + + foreach_stop = FALSE; + for (i = 0; i < table->size; i++) { + for (node = table->nodes[i]; node; node = node->next) { + if (!node->destroyed) { + func(node->key, node->value, user_data); + + if (foreach_stop) { + foreach_stop = FALSE; + hash_thaw(table); + return; + } + } + } + } + hash_thaw(table); +} + +void hash_foreach_stop(void) +{ + foreach_stop = TRUE; +} + +/* Returns the number of elements contained in the hash table. */ +unsigned int hash_size(HashTable *table) +{ + i_assert(table != NULL); + + return table->nodes_count; +} + +static int hash_resize(HashTable *table) +{ + HashFunc hash_func; + HashNode *node, *next, **new_nodes; + float nodes_per_list; + unsigned int hash_val, new_size, i; + + nodes_per_list = (float) table->nodes_count / (float) table->size; + if ((nodes_per_list > 0.3 || table->size <= HASH_TABLE_MIN_SIZE) && + (nodes_per_list < 3.0 || table->size >= HASH_TABLE_MAX_SIZE)) + return FALSE; + + new_size = CLAMP(primes_closest(table->nodes_count), + HASH_TABLE_MIN_SIZE, + HASH_TABLE_MAX_SIZE); + + new_nodes = p_new(table->pool, HashNode *, new_size); + + hash_func = table->hash_func; + for (i = 0; i < table->size; i++) { + for (node = table->nodes[i]; node != NULL; node = next) { + next = node->next; + + if (node->destroyed) { + p_free(table->pool, node); + } else { + hash_val = hash_func(node->key) % new_size; + + node->next = new_nodes[hash_val]; + new_nodes[hash_val] = node; + } + } + } + + p_free(table->pool, table->nodes); + table->nodes = new_nodes; + table->size = new_size; + table->nodes_destroyed = 0; + return TRUE; +} + +static void hash_cleanup(HashTable *table) +{ + HashNode **node, **next, *old_node; + unsigned int i; + + if (hash_resize(table)) + return; + + if (table->nodes_destroyed == 0) + return; + + /* find the destroyed nodes from hash table and remove them */ + for (i = 0; i < table->size; i++) { + for (node = &table->nodes[i]; *node != NULL; node = next) { + next = &(*node)->next; + + if ((*node)->destroyed) { + old_node = *node; + *node = *next; + p_free(table->pool, old_node); + + /* next points to free'd memory area now, + fix it */ + next = node; + + if (--table->nodes_destroyed == 0) + return; + } + } + } +} + +/* a char* hash function from ASU -- from glib */ +unsigned int str_hash(const void *p) +{ + const unsigned char *s = p; + unsigned int g, h = 0; + + while (*s != '\0') { + h = (h << 4) + *s; + if ((g = h & 0xf0000000UL)) { + h = h ^ (g >> 24); + h = h ^ g; + } + s++; + } + + return h; +} + +/* a char* hash function from ASU -- from glib */ +unsigned int strcase_hash(const void *p) +{ + const unsigned char *s = p; + unsigned int g, h = 0; + + while (*s != '\0') { + h = (h << 4) + i_toupper(*s); + if ((g = h & 0xf0000000UL)) { + h = h ^ (g >> 24); + h = h ^ g; + } + s++; + } + + return h; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/hash.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,55 @@ +#ifndef __HASH_H +#define __HASH_H + +/* Returns hash code. */ +typedef unsigned int (*HashFunc) (const void *p); +/* Returns 0 if the pointers are equal. */ +typedef int (*HashCompareFunc) (const void *p1, const void *p2); +typedef void (*HashForeachFunc) (void *key, void *value, void *user_data); + +typedef struct _HashTable HashTable; + +/* Create a new hash table. If initial_size is 0, the default value is used. + If hash_func or key_compare_func is NULL, direct hashing/comparing + is used. */ +HashTable *hash_create(Pool pool, unsigned int initial_size, + HashFunc hash_func, HashCompareFunc key_compare_func); +void hash_destroy(HashTable *table); + +#ifdef POOL_CHECK_LEAKS +# define hash_destroy_clean(table) hash_destroy(table) +#else +# define hash_destroy_clean(table) +#endif + +void hash_clear(HashTable *table); + +void *hash_lookup(HashTable *table, const void *key); +int hash_lookup_full(HashTable *table, const void *lookup_key, + void **orig_key, void **value); + +/* Insert/update node in hash table. The difference is that hash_insert() + replaces the key in table to given one, while hash_update() doesnt. */ +void hash_insert(HashTable *table, const void *key, const void *value); +void hash_update(HashTable *table, const void *key, const void *value); + +void hash_remove(HashTable *table, const void *key); +unsigned int hash_size(HashTable *table); + +/* Calls the given function for each node in hash table. You may safely + call hash_*() functions inside your function, but if you add any + new nodes, they may or may not be called for in this foreach loop. */ +void hash_foreach(HashTable *table, HashForeachFunc func, void *user_data); +/* Stop the active hash_foreach() loop */ +void hash_foreach_stop(void); + +/* Hash table isn't resized, and removed nodes aren't removed from + the list while hash table is freezed. Supports nesting. */ +void hash_freeze(HashTable *table); +void hash_thaw(HashTable *table); + +/* hash function for strings */ +unsigned int str_hash(const void *p); +unsigned int strcase_hash(const void *p); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/hex-binary.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,77 @@ +/* + hex-binary.c : hex translations + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "hex-binary.h" + +const char *binary_to_hex(const unsigned char *data, unsigned int size) +{ + unsigned int i, value; + char *buf, *p; + + buf = p = t_malloc(size * 2 + 1); + for (i = 0; i < size; i++) { + value = data[i] >> 4; + *p++ = value < 10 ? value + '0' : value - 10 + 'a'; + + value = data[i] & 0x0f; + *p++ = value < 10 ? value + '0' : value - 10 + 'a'; + } + + *p = '\0'; + return buf; +} + +int hex_to_binary(const char *data, unsigned char *dest) +{ + int size, value; + + size = 0; + while (*data != '\0') { + if (*data >= '0' && *data <= '9') + value = (*data - '0') << 4; + else if (*data >= 'a' && *data <= 'f') + value = (*data - 'a' + 10) << 4; + else if (*data >= 'A' && *data <= 'F') + value = (*data - 'A' + 10) << 4; + else + return -1; + + data++; + if (*data >= '0' && *data <= '9') + value |= *data - '0'; + else if (*data >= 'a' && *data <= 'f') + value |= *data - 'a' + 10; + else if (*data >= 'A' && *data <= 'F') + value |= *data - 'A' + 10; + else + return -1; + + dest[size++] = value; + data++; + } + + return size; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/hex-binary.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,13 @@ +#ifndef __HEX_BINARY_H +#define __HEX_BINARY_H + +/* Convert binary to lowercased hex digits allocating return value from + temporary memory pool */ +const char *binary_to_hex(const unsigned char *data, unsigned int size); + +/* Convert hex to binary. data and dest may point to same value. + Returns TRUE if successful. Returns number of bytes writte to dest, + or -1 if error occured. */ +int hex_to_binary(const char *data, unsigned char *dest); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/hostpid.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,50 @@ +/* + hostpid.c : Easy way to get our own hostname and pid + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "hostpid.h" + +#include <unistd.h> + +const char *my_hostname = NULL; +const char *my_pid = NULL; + +void hostpid_init(void) +{ + static char hostname[256], pid[100]; + + if (my_hostname == NULL) { + hostname[sizeof(hostname)-1] = '\0'; + if (gethostname(hostname, sizeof(hostname)-1) == -1) + strcpy(hostname, "unknown"); + + my_hostname = hostname; + } + + if (my_pid == NULL) { + i_snprintf(pid, sizeof(pid), "%lu", (unsigned long) getpid()); + my_pid = pid; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/hostpid.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,12 @@ +#ifndef __HOSTPID_H +#define __HOSTPID_H + +extern const char *my_hostname; +extern const char *my_pid; + +/* Initializes my_hostname and my_pid. Done only once, so it's safe and + fast to call this function multiple times. */ +void hostpid_init(void); + +#endif +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/imem.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,107 @@ +/* + imem.c : Wrappers for allocating memory from default memory pool + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" + +Pool default_pool; + +void *i_malloc(unsigned int size) +{ + return p_malloc(default_pool, size); +} + +void i_free(void *mem) +{ + p_free(default_pool, mem); +} + +void *i_realloc(void *mem, unsigned int size) +{ + return p_realloc(default_pool, mem, size); +} + +char *i_strdup(const char *str) +{ + return p_strdup(default_pool, str); +} + +char *i_strdup_empty(const char *str) +{ + return p_strdup_empty(default_pool, str); +} + +char *i_strndup(const char *str, unsigned int max_chars) +{ + return p_strndup(default_pool, str, max_chars); +} + +char *i_strdup_printf(const char *format, ...) +{ + va_list args; + char *ret; + + va_start(args, format); + ret = p_strdup_vprintf(default_pool, format, args); + va_end(args); + return ret; +} + +char *i_strdup_vprintf(const char *format, va_list args) +{ + return p_strdup_vprintf(default_pool, format, args); +} + +void i_strdup_replace(char **dest, const char *str) +{ + p_free(default_pool, *dest); + *dest = p_strdup(default_pool, str); +} + +char *i_strconcat(const char *str1, ...) +{ + va_list args; + const char *temp; + char *ret; + unsigned int len; + + va_start(args, str1); + + temp = temp_strconcat(str1, args, &len); + ret = p_malloc(default_pool, len); + memcpy(ret, temp, len); + + va_end(args); + return ret; +} + +void imem_init(void) +{ + default_pool = pool_create("Default pool", 4096, TRUE); +} + +void imem_deinit(void) +{ + pool_unref(default_pool); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/imem.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,26 @@ +#ifndef __IMEM_H +#define __IMEM_H + +extern Pool default_pool; + +/* For easy allocation of memory from default memory pool. */ +#define i_new(type, count) \ + ((type *) i_malloc((unsigned) sizeof(type) * (count))) +void *i_malloc(unsigned int size); +void i_free(void *mem); +void *i_realloc(void *mem, unsigned int size); + +/* string functions */ +char *i_strdup(const char *str); +char *i_strdup_empty(const char *str); /* like i_strdup(), but if str == "", return NULL */ +char *i_strndup(const char *str, unsigned int max_chars); +char *i_strdup_printf(const char *format, ...) __attr_format__(1, 2); +char *i_strdup_vprintf(const char *format, va_list args); +void i_strdup_replace(char **dest, const char *str); + +char *i_strconcat(const char *str1, ...); /* NULL terminated */ + +void imem_init(void); +void imem_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/iobuffer.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,659 @@ +/* + iobuffer.c : Input/output transmit buffer handling + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "ioloop.h" +#include "iobuffer.h" +#include "network.h" + +#include <unistd.h> + +#ifdef HAVE_SYS_SENDFILE_H +# include <sys/sendfile.h> +#endif + +IOBuffer *io_buffer_create(int fd, Pool pool, int priority, + unsigned int max_size) +{ + IOBuffer *buf; + + i_assert(fd >= 0); + i_assert(pool != NULL); + + buf = p_new(pool, IOBuffer, 1); + buf->fd = fd; + buf->pool = pool; + buf->priority = priority; + buf->max_size = max_size; + return buf; +} + +IOBuffer *io_buffer_create_file(int fd, Pool pool, unsigned int max_size) +{ + IOBuffer *buf; + + buf = io_buffer_create(fd, pool, IO_PRIORITY_DEFAULT, max_size); + buf->file = TRUE; + return buf; +} + +void io_buffer_destroy(IOBuffer *buf) +{ + if (buf == NULL) + return; + + if (buf->io != NULL) + io_remove(buf->io); + p_free(buf->pool, buf->buffer); + p_free(buf->pool, buf); +} + +void io_buffer_close(IOBuffer *buf) +{ + if (buf == NULL) + return; + + buf->closed = TRUE; +} + +void io_buffer_reset(IOBuffer *buf) +{ + buf->pos = buf->skip = buf->cr_lookup_pos = 0; + buf->last_cr = FALSE; +} + +IOBuffer *io_buffer_set_pool(IOBuffer *buf, Pool pool) +{ + IOBuffer *newbuf; + + i_assert(buf != NULL); + i_assert(pool != NULL); + + newbuf = p_new(pool, IOBuffer, 1); + memcpy(newbuf, buf, sizeof(IOBuffer)); + + newbuf->pool = pool; + newbuf->buffer = p_malloc(pool, buf->size); + memcpy(newbuf->buffer, buf->buffer + buf->skip, + buf->size - buf->skip); + + newbuf->cr_lookup_pos -= newbuf->skip; + newbuf->pos -= newbuf->skip; + newbuf->skip = 0; + + p_free(buf->pool, buf->buffer); + p_free(buf->pool, buf); + return newbuf; +} + +void io_buffer_set_max_size(IOBuffer *buf, unsigned int max_size) +{ + buf->max_size = max_size; +} + +void io_buffer_set_send_blocking(IOBuffer *buf, unsigned int max_size, + int timeout_msecs, TimeoutFunc timeout_func, + void *user_data) +{ + i_assert(!buf->receive); + + buf->transmit = TRUE; + buf->timeout_msecs = timeout_msecs; + buf->timeout_func = timeout_func; + buf->timeout_user_data = user_data; + buf->blocking = max_size > 0; + buf->max_size = max_size; +} + +static int my_write(int fd, const void *buf, unsigned int size) +{ + int ret; + + i_assert(size <= INT_MAX); + + if (size == 0) + return 1; + + ret = write(fd, buf, size); + if (ret < 0 && (errno == EINTR || errno == EAGAIN)) + ret = 0; + + return ret; +} + +static void buf_send_real(IOBuffer *buf) +{ + int ret; + + if (!buf->file) { + ret = net_transmit(buf->fd, buf->buffer + buf->skip, + buf->pos - buf->skip); + } else { + ret = my_write(buf->fd, buf->buffer + buf->skip, + buf->pos - buf->skip); + } + + if (ret < 0) { + buf->closed = TRUE; + } else { + buf->transfd += ret; + buf->skip += ret; + if (buf->skip == buf->pos) { + /* everything sent */ + buf->skip = buf->pos = 0; + + /* call flush function */ + if (buf->flush_func != NULL) { + buf->flush_func(buf->flush_user_data, buf); + buf->flush_func = NULL; + + if (buf->corked) { + /* remove cork */ + net_set_cork(buf->fd, FALSE); + buf->corked = FALSE; + } + } + } + } +} + +static int buf_send(IOBuffer *buf) +{ + buf_send_real(buf); + + if (buf->closed || buf->pos == 0) { + io_remove(buf->io); + buf->io = NULL; + return FALSE; + } + + return TRUE; +} + +typedef struct { + IOLoop ioloop; + IOBuffer *buf; + + const char *data; + unsigned int size; + + int in_fd; + off_t offset; + + int timeout; +} IOBufferBlockData; + +static void block_loop_send(IOBufferBlockData *bd) +{ + int ret; + + if (bd->buf->skip != bd->buf->pos) { + buf_send_real(bd->buf); + } else { + /* send the data */ + ret = !bd->buf->file ? + net_transmit(bd->buf->fd, bd->data, bd->size) : + my_write(bd->buf->fd, bd->data, bd->size); + + if (ret < 0) { + bd->buf->closed = TRUE; + } else { + bd->data += ret; + bd->size -= ret; + } + } + + if (bd->buf->closed || bd->size == 0) + io_loop_stop(bd->ioloop); +} + +static void block_loop_timeout(void *user_data, Timeout timeout __attr_unused__) +{ + IOBufferBlockData *data = user_data; + + data->timeout = TRUE; + io_loop_stop(data->ioloop); +} + +static int io_buffer_ioloop(IOBuffer *buf, IOBufferBlockData *bd, + void (*send_func)(IOBufferBlockData *bd)) +{ + Timeout to; + + /* close old IO */ + if (buf->io != NULL) + io_remove(buf->io); + + /* create a new I/O loop */ + bd->ioloop = io_loop_create(); + bd->buf = buf; + + buf->io = io_add(buf->fd, IO_WRITE, (IOFunc) send_func, bd); + to = buf->timeout_msecs <= 0 ? NULL : + timeout_add(buf->timeout_msecs, block_loop_timeout, bd); + + io_loop_run(bd->ioloop); + + if (buf->corked) { + /* remove cork */ + net_set_cork(buf->fd, FALSE); + buf->corked = FALSE; + } + + if (buf->io != NULL) { + io_remove(buf->io); + buf->io = NULL; + } + + if (to != NULL) { + if (bd->timeout && buf->timeout_func != NULL) { + /* call user-given timeout function */ + buf->timeout_func(buf->timeout_user_data, to); + } + timeout_remove(to); + } + + io_loop_destroy(bd->ioloop); + return bd->size > 0 ? -1 : 1; +} + +static int io_buffer_send_blocking(IOBuffer *buf, const void *data, + unsigned int size) +{ + IOBufferBlockData bd; + + memset(&bd, 0, sizeof(IOBufferBlockData)); + + bd.data = data; + bd.size = size; + + return io_buffer_ioloop(buf, &bd, block_loop_send); +} + +void io_buffer_cork(IOBuffer *buf) +{ + i_assert(!buf->receive); + + if (!buf->file && !buf->corked) { + net_set_cork(buf->fd, TRUE); + buf->corked = TRUE; + } +} + +static void buffer_alloc_more(IOBuffer *buf, unsigned int size) +{ + buf->size = buf->pos+size; + buf->size = buf->size <= IO_BUFFER_MIN_SIZE ? IO_BUFFER_MIN_SIZE : + nearest_power(buf->size); + + if (buf->max_size > 0 && buf->size > buf->max_size) + buf->size = buf->max_size; + + buf->buffer = p_realloc(buf->pool, buf->buffer, buf->size); + if (buf->buffer == NULL) { + /* pool limit exceeded */ + buf->pos = buf->size = 0; + } +} + +static inline void io_buffer_compress(IOBuffer *buf) +{ + memmove(buf->buffer, buf->buffer + buf->skip, + buf->pos - buf->skip); + buf->pos -= buf->skip; + + if (buf->skip > buf->cr_lookup_pos) + buf->cr_lookup_pos = 0; + else + buf->cr_lookup_pos -= buf->skip; + + buf->skip = 0; +} + +int io_buffer_send(IOBuffer *buf, const void *data, unsigned int size) +{ + int ret; + + i_assert(!buf->receive); + i_assert(data != NULL); + i_assert(size < INT_MAX); + buf->transmit = TRUE; + + if (buf->closed) + return -1; + + if (buf->pos == 0) { + /* buffer is empty, try to send the data immediately */ + ret = buf->file ? my_write(buf->fd, data, size) : + net_transmit(buf->fd, data, size); + if (ret < 0) { + /* disconnected */ + buf->closed = TRUE; + return -1; + } + + buf->transfd += ret; + data = (const char *) data + ret; + size -= ret; + } + + if (size == 0) + return 1; + + if (io_buffer_get_space(buf, size) == NULL) { + if (buf->blocking) { + /* if we don't have space, we block */ + return io_buffer_send_blocking(buf, data, size); + } + return -2; + } + + /* add to buffer */ + memcpy(buf->buffer + buf->pos, data, size); + buf->pos += size; + + if (buf->io == NULL) { + buf->io = io_add_priority(buf->fd, buf->priority, IO_WRITE, + (IOFunc) buf_send, buf); + } + return 1; +} + +#ifdef HAVE_SYS_SENDFILE_H +static void block_loop_sendfile(IOBufferBlockData *bd) +{ + int ret; + + ret = sendfile(bd->buf->fd, bd->in_fd, &bd->offset, bd->size); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + bd->buf->closed = TRUE; + ret = 0; + } + + bd->size -= ret; + if (bd->buf->closed || bd->size == 0) + io_loop_stop(bd->ioloop); +} +#endif + +int io_buffer_send_file(IOBuffer *buf, int fd, off_t offset, + const void *data, unsigned int size) +{ +#ifdef HAVE_SYS_SENDFILE_H + IOBufferBlockData bd; + int ret; +#endif + + i_assert(fd >= 0); + i_assert(data != NULL); + i_assert(size < INT_MAX); + +#ifdef HAVE_SYS_SENDFILE_H + io_buffer_send_flush(buf); + + /* first try if we can do it with a single sendfile() call */ + ret = sendfile(buf->fd, fd, &offset, size); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + ret = 0; + } + + if ((unsigned int) ret == size) + return 1; + + if (buf->blocking) { + memset(&bd, 0, sizeof(IOBufferBlockData)); + + bd.in_fd = fd; + bd.offset = offset + ret; + bd.size = size - ret; + + return io_buffer_ioloop(buf, &bd, block_loop_sendfile); + } else { + data = (char *) data + ret; + size -= ret; + } +#endif + return io_buffer_send(buf, data, size); +} + +void io_buffer_send_flush(IOBuffer *buf) +{ + i_assert(!buf->receive); + + if (buf->closed || buf->io == NULL) + return; + + if (buf->skip != buf->pos) + io_buffer_send_blocking(buf, NULL, 0); +} + +void io_buffer_send_flush_callback(IOBuffer *buf, IOBufferFlushFunc func, + void *user_data) +{ + i_assert(!buf->receive); + + if (buf->skip == buf->pos) { + func(user_data, buf); + return; + } + + buf->flush_func = func; + buf->flush_user_data = user_data; +} + +int io_buffer_read_max(IOBuffer *buf, unsigned int size) +{ + int ret; + + i_assert(size <= INT_MAX || size == UINT_MAX); + i_assert(!buf->transmit); + buf->receive = TRUE; + + if (buf->closed) + return -1; + + if (buf->pos == buf->size) { + if (buf->skip > 0) { + /* remove the unused bytes from beginning of buffer */ + io_buffer_compress(buf); + } else if (buf->max_size == 0 || buf->size < buf->max_size) { + /* buffer is full - grow it */ + buffer_alloc_more(buf, IO_BUFFER_MIN_SIZE); + } + + if (buf->pos == buf->size) + return -2; /* buffer full */ + } + + /* fill the buffer */ + if (size == UINT_MAX || buf->size-buf->pos < size) + size = buf->size - buf->pos; + + if (!buf->file) { + ret = net_receive(buf->fd, buf->buffer + buf->pos, + buf->size - buf->pos); + } else { + ret = read(buf->fd, buf->buffer + buf->pos, + buf->size - buf->pos); + if (ret == 0) + ret = -1; /* EOF */ + else if (ret < 0 && (errno == EINTR || errno == EAGAIN)) + ret = 0; + } + + if (ret < 0) { + /* disconnected */ + return -1; + } + + buf->transfd += ret; + buf->pos += ret; + return ret; +} + +int io_buffer_read(IOBuffer *buf) +{ + return io_buffer_read_max(buf, UINT_MAX); +} + +/* skip the first LF, if it exists */ +static inline void io_buffer_skip_lf(IOBuffer *buf) +{ + if (!buf->last_cr || buf->skip >= buf->pos) + return; + + if (buf->buffer[buf->skip] == 10) { + if (buf->skip == buf->cr_lookup_pos) + buf->cr_lookup_pos++; + buf->skip++; + } + buf->last_cr = FALSE; +} + +char *io_buffer_next_line(IOBuffer *buf) +{ + unsigned char *ret_buf; + unsigned int i; + + i_assert(buf != NULL); + + io_buffer_skip_lf(buf); + if (buf->skip >= buf->pos) + return NULL; + + ret_buf = NULL; + for (i = buf->cr_lookup_pos; i < buf->pos; i++) { + if (buf->buffer[i] == 13 || buf->buffer[i] == 10) { + /* got it */ + buf->last_cr = buf->buffer[i] == 13; + buf->buffer[i] = '\0'; + ret_buf = buf->buffer + buf->skip; + + i++; + buf->skip = i; + break; + } + } + + buf->cr_lookup_pos = i; + return (char *) ret_buf; +} + +unsigned char *io_buffer_get_data(IOBuffer *buf, unsigned int *size) +{ + io_buffer_skip_lf(buf); + + if (buf->skip >= buf->pos) { + *size = 0; + return NULL; + } + + *size = buf->pos - buf->skip; + return buf->buffer + buf->skip; +} + +unsigned char *io_buffer_get_space(IOBuffer *buf, unsigned int size) +{ + i_assert(size <= INT_MAX); + i_assert(!buf->receive); + buf->transmit = TRUE; + + /* make sure we have enough space in buffer */ + if (buf->size - buf->pos < size && buf->skip > 0) { + /* remove the unused bytes from beginning of buffer */ + io_buffer_compress(buf); + } + + if (buf->size - buf->pos < size && + (buf->max_size == 0 || size <= buf->max_size - buf->pos)) { + /* allocate more space */ + buffer_alloc_more(buf, size); + } + + if (buf->size - buf->pos < size) + return NULL; + + return buf->buffer + buf->pos; +} + +int io_buffer_send_buffer(IOBuffer *buf, unsigned int size) +{ + int ret; + + i_assert(size <= INT_MAX); + i_assert(!buf->receive); + + if (buf->pos == 0) { + /* buffer is empty, try to send the data immediately */ + ret = buf->file ? my_write(buf->fd, buf->buffer, size) : + net_transmit(buf->fd, buf->buffer, size); + if (ret < 0) { + /* disconnected */ + buf->closed = TRUE; + return -1; + } + + buf->transfd += ret; + if ((unsigned int) ret == size) { + /* all sent */ + return 1; + } + + buf->skip += ret; + } + + buf->pos += size; + + if (buf->io == NULL) { + buf->io = io_add_priority(buf->fd, buf->priority, IO_WRITE, + (IOFunc) buf_send, buf); + } + + return 1; +} + +int io_buffer_set_data(IOBuffer *buf, const void *data, unsigned int size) +{ + io_buffer_reset(buf); + + if (buf->size < size) { + buffer_alloc_more(buf, size); + if (buf->size < size) + return -2; + } + + memcpy(buf->buffer, data, size); + buf->pos = size; + buf->transfd += size; + return 1; +} + +int io_buffer_is_empty(IOBuffer *buf) +{ + return buf->skip >= buf->pos; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/iobuffer.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,119 @@ +#ifndef __IOBUFFER_H +#define __IOBUFFER_H + +#include "ioloop.h" + +#define IO_BUFFER_MIN_SIZE 512 + +typedef void (*IOBufferFlushFunc) (void *user_data, IOBuffer *buf); + +struct _IOBuffer { + int fd; + IO io; + + Pool pool; + int priority; + + int timeout_msecs; + TimeoutFunc timeout_func; + void *timeout_user_data; + + IOBufferFlushFunc flush_func; + void *flush_user_data; + + unsigned char *buffer; + + unsigned int cr_lookup_pos; /* used only when reading a line */ + unsigned int pos, skip; + unsigned int size, max_size; + unsigned int transfd; + + unsigned int file:1; /* reading/writing a file */ + unsigned int closed:1; /* all further read/writes will return 0 */ + unsigned int transmit:1; /* this is a transmit buffer */ + unsigned int receive:1; /* this is a receive buffer */ + unsigned int last_cr:1; /* we're expecting a LF to be skipped in + next call to io_buffer_next_line() */ + unsigned int blocking:1; /* writes block if buffer is full */ + unsigned int corked:1; /* TCP_CORK set */ +}; + +/* Create an I/O buffer. It can be used for either sending or receiving data, + NEVER BOTH AT SAME TIME. If max_size is 0, there's no limit. */ +IOBuffer *io_buffer_create(int fd, Pool pool, int priority, + unsigned int max_size); +/* Same as io_buffer_create(), but specify that we're reading/writing file. */ +IOBuffer *io_buffer_create_file(int fd, Pool pool, unsigned int max_size); +/* Read the file by mmap()ing it in blocks. max_size specifies the maximum + amount of data to read from the file. */ +IOBuffer *io_buffer_create_mmap(int fd, Pool pool, unsigned int block_size, + unsigned int max_size); +/* Destroy a buffer. */ +void io_buffer_destroy(IOBuffer *buf); +/* Mark the buffer closed. Any sends/reads after this will return -1. + The data already in buffer can be used, and the remaining output buffer + will be sent. */ +void io_buffer_close(IOBuffer *buf); +/* Reset all pointers so that the buffer looks empty, the actual data is + not touched and can be used. */ +void io_buffer_reset(IOBuffer *buf); + +/* Change the memory pool used by the buffer. Data already in + buffer will be transferred to new buffer. */ +IOBuffer *io_buffer_set_pool(IOBuffer *buf, Pool pool); +/* Change the maximum size for buffer to grow. */ +void io_buffer_set_max_size(IOBuffer *buf, unsigned int max_size); +/* Change output buffer's blocking state. When buffer reaches max_size, + it will block until all the data has been sent or timeout has been + reached. Setting max_size to 0 disables this (default). Setting + timeout_msecs to 0 may block infinitely. */ +void io_buffer_set_send_blocking(IOBuffer *buf, unsigned int max_size, + int timeout_msecs, TimeoutFunc timeout_func, + void *user_data); + +/* Set TCP_CORK on if supported, ie. don't send out partial frames. + io_buffer_send_flush() removes the cork. */ +void io_buffer_cork(IOBuffer *buf); + +/* Returns 1 if all was ok, -1 if disconnected, -2 if buffer is full */ +int io_buffer_send(IOBuffer *buf, const void *data, unsigned int size); +/* Send data using sendfile(), of if it's not available fallback to regular + sending from data-pointer. Returns 1 if all was ok, -1 if disconnected. */ +int io_buffer_send_file(IOBuffer *buf, int fd, off_t offset, + const void *data, unsigned int size); +/* Flush the output buffer, blocks until all is sent. If + io_buffer_set_send_blocking() is called, it's timeout settings are used. */ +void io_buffer_send_flush(IOBuffer *buf); +/* Call specified function when the whole transmit buffer has been sent. + If the buffer is empty already, the function will be called immediately. + The function will be called only once. */ +void io_buffer_send_flush_callback(IOBuffer *buf, IOBufferFlushFunc func, + void *user_data); + +/* Returns number of bytes read if read was ok, + -1 if disconnected, -2 if the buffer is full */ +int io_buffer_read(IOBuffer *buf); +/* Like io_buffer_read(), but don't read more than specified size. */ +int io_buffer_read_max(IOBuffer *buf, unsigned int size); +/* Returns the next line from input buffer, or NULL if more data is needed + to make a full line. NOTE: call to io_buffer_read() invalidates the + returned data. */ +char *io_buffer_next_line(IOBuffer *buf); +/* Returns pointer to beginning of data in buffer, + or NULL if there's no data. */ +unsigned char *io_buffer_get_data(IOBuffer *buf, unsigned int *size); + +/* Returns a pointer to buffer wanted amount of space, + or NULL if size is too big. */ +unsigned char *io_buffer_get_space(IOBuffer *buf, unsigned int size); +/* Send data saved to buffer from io_buffer_get_space(). + Returns -1 if disconnected. */ +int io_buffer_send_buffer(IOBuffer *buf, unsigned int size); + +/* Put data to buffer as if it was received. + Returns 1 if successful, -2 if buffer isn't big enough. */ +int io_buffer_set_data(IOBuffer *buf, const void *data, unsigned int size); +/* Returns TRUE if there's nothing in buffer. */ +int io_buffer_is_empty(IOBuffer *buf); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ioloop-internal.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,66 @@ +#ifndef __IOLOOP_INTERNAL_H +#define __IOLOOP_INTERNAL_H + +#include "ioloop.h" + +#include <sys/time.h> + +typedef struct _IOLoopHandlerData IOLoopHandlerData; + +struct _IOLoop { + struct _IOLoop *prev; + + Pool pool; + int highest_fd; + + IO ios; /* sorted by priority */ + Timeout timeouts; /* sorted by next_run */ + + IOLoopHandlerData *handler_data; + + unsigned int running:1; +}; + +struct _IO { + IO prev, next; + + int fd; + int priority; + int condition; + + unsigned int destroyed:1; + unsigned int invalid:1; + + IOFunc func; + void *user_data; +}; + +struct _Timeout { + Timeout next; + + struct timeval next_run; + int msecs; + int run_now; + int destroyed; + + TimeoutFunc func; + void *user_data; +}; + +int io_loop_get_wait_time(Timeout timeout, struct timeval *tv, + struct timeval *tv_now); +void io_loop_handle_timeouts(IOLoop ioloop); + +/* call only when io->destroyed is TRUE */ +void io_destroy(IOLoop ioloop, IO io); +/* call only when timeout->destroyed is TRUE */ +void timeout_destroy(IOLoop ioloop, Timeout timeout); + +/* I/O handler calls */ +void io_loop_handle_add(IOLoop ioloop, int fd, int condition); +void io_loop_handle_remove(IOLoop ioloop, int fd, int condition); + +void io_loop_handler_init(IOLoop ioloop); +void io_loop_handler_deinit(IOLoop ioloop); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ioloop-poll.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,213 @@ +/* + ioloop-poll.c : I/O loop handler using poll() + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "ioloop-internal.h" + +#include <sys/poll.h> + +#ifndef INITIAL_POLL_FDS +# define INITIAL_POLL_FDS 128 +#endif + +struct _IOLoopHandlerData { + unsigned int fds_size, fds_pos; + struct pollfd *fds; + + unsigned int idx_size; + int *fd_index; +}; + +void io_loop_handler_init(IOLoop ioloop) +{ + IOLoopHandlerData *data; + + ioloop->handler_data = data = + p_new(ioloop->pool, IOLoopHandlerData, 1); + data->fds_size = INITIAL_POLL_FDS; + data->fds = p_new(ioloop->pool, struct pollfd, data->fds_size); + + data->idx_size = INITIAL_POLL_FDS; + data->fd_index = p_new(ioloop->pool, int, data->idx_size); + memset(data->fd_index, 0xff, sizeof(int) * data->idx_size); +} + +void io_loop_handler_deinit(IOLoop ioloop) +{ + p_free(ioloop->pool, ioloop->handler_data->fds); + p_free(ioloop->pool, ioloop->handler_data->fd_index); + p_free(ioloop->pool, ioloop->handler_data); +} + +#define IO_POLL_INPUT (POLLIN|POLLPRI|POLLERR|POLLHUP|POLLNVAL) +#define IO_POLL_OUTPUT (POLLOUT|POLLERR|POLLHUP|POLLNVAL) + +void io_loop_handle_add(IOLoop ioloop, int fd, int condition) +{ + IOLoopHandlerData *data; + int index, old_size; + + data = ioloop->handler_data; + if ((unsigned int) fd >= data->idx_size) { + /* grow the fd -> index array */ + old_size = data->idx_size; + + data->idx_size = nearest_power((unsigned int) fd+1); + data->fd_index = p_realloc(ioloop->pool, data->fd_index, + sizeof(int) * data->idx_size); + memset(data->fd_index + old_size, 0xff, + sizeof(int) * (data->idx_size-old_size)); + } + + if (data->fds_pos >= data->fds_size) { + /* grow the fd array */ + data->fds_size = nearest_power(data->fds_size+1); + data->fds = p_realloc(ioloop->pool, data->fds, + sizeof(struct pollfd) * data->fds_size); + } + + if (data->fd_index[fd] != -1) { + /* update existing pollfd */ + index = data->fd_index[fd]; + } else { + /* add new pollfd */ + index = data->fds_pos++; + + data->fd_index[fd] = index; + data->fds[index].fd = fd; + data->fds[index].events = 0; + data->fds[index].revents = 0; + } + + if (condition & IO_READ) + data->fds[index].events |= IO_POLL_INPUT; + if (condition & IO_WRITE) + data->fds[index].events |= IO_POLL_OUTPUT; +} + +void io_loop_handle_remove(IOLoop ioloop, int fd, int condition) +{ + IOLoopHandlerData *data; + int index; + + data = ioloop->handler_data; + index = data->fd_index[fd]; + i_assert(index >= 0 && (unsigned int) index < data->fds_size); + + if (condition & IO_READ) + data->fds[index].events &= ~(POLLIN|POLLPRI); + if (condition & IO_WRITE) + data->fds[index].events &= ~POLLOUT; + + if ((data->fds[index].events & (POLLIN|POLLOUT)) == 0) { + /* remove the whole pollfd */ + data->fd_index[data->fds[index].fd] = -1; + if (--data->fds_pos == (unsigned int) index) + return; /* removing last one */ + + /* move the last pollfd over the removed one */ + data->fds[index] = data->fds[data->fds_pos]; + data->fd_index[data->fds[index].fd] = index; + } +} + +void io_loop_handler_run(IOLoop ioloop) +{ + IOLoopHandlerData *data; + struct pollfd *pollfd; + struct timeval tv; + IO io, next; + int msecs, ret, t_id; + + data = ioloop->handler_data; + + /* get the time left for next timeout task */ + msecs = io_loop_get_wait_time(ioloop->timeouts, &tv, NULL); + + ret = poll(data->fds, data->fds_pos, msecs); + if (ret < 0 && errno != EINTR) + i_warning("poll() : %m"); + + /* execute timeout handlers */ + io_loop_handle_timeouts(ioloop); + + if (ret <= 0 || !ioloop->running) { + /* no I/O events */ + return; + } + + /* execute the I/O handlers in prioritized order */ + for (io = ioloop->ios; io != NULL && ret > 0; io = next) { + next = io->next; + + if (io->destroyed) { + /* we were destroyed, and io->fd points to + -1 now, so we can't know if there was any + revents left. */ + io_destroy(ioloop, io); + continue; + } + + i_assert(io->fd >= 0); + + pollfd = &data->fds[data->fd_index[io->fd]]; + if (pollfd->revents != 0) + ret--; + + if (pollfd->revents == 0) + continue; + + if (pollfd->revents & POLLNVAL) { + if (!io->invalid) { + io->invalid = TRUE; + i_warning("invalid I/O fd %d, func %p", + io->fd, io->func); + } + + continue; + } + + if ((io->condition & + (IO_READ|IO_WRITE)) == (IO_READ|IO_WRITE)) { + pollfd->revents = 0; + } else if (io->condition & IO_READ) { + if ((pollfd->revents & IO_POLL_INPUT) == 0) + continue; + pollfd->revents &= ~IO_POLL_INPUT; + } else if (io->condition & IO_WRITE) { + if ((pollfd->revents & IO_POLL_OUTPUT) == 0) + continue; + pollfd->revents &= ~IO_POLL_OUTPUT; + } + + t_id = t_push(); + io->func(io->user_data, io->fd, io); + if (t_pop() != t_id) + i_panic("Leaked a t_pop() call!"); + + if (io->destroyed) + io_destroy(ioloop, io); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ioloop-select.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,122 @@ +/* + ioloop-select.c : I/O loop handler using select() + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "ioloop-internal.h" + +#include <sys/types.h> +#include <unistd.h> + +struct _IOLoopHandlerData { + fd_set read_fds, write_fds; +}; + +static fd_set tmp_read_fds, tmp_write_fds; + +void io_loop_handler_init(IOLoop ioloop) +{ + ioloop->handler_data = p_new(ioloop->pool, IOLoopHandlerData, 1); + FD_ZERO(&ioloop->handler_data->read_fds); + FD_ZERO(&ioloop->handler_data->write_fds); +} + +void io_loop_handler_deinit(IOLoop ioloop) +{ + p_free(ioloop->pool, ioloop->handler_data); +} + +void io_loop_handle_add(IOLoop ioloop, int fd, int condition) +{ + if (condition & IO_READ) + FD_SET(fd, &ioloop->handler_data->read_fds); + if (condition & IO_WRITE) + FD_SET(fd, &ioloop->handler_data->write_fds); +} + +void io_loop_handle_remove(IOLoop ioloop, int fd, int condition) +{ + if (condition & IO_READ) + FD_CLR(fd, &ioloop->handler_data->read_fds); + if (condition & IO_WRITE) + FD_CLR(fd, &ioloop->handler_data->write_fds); +} + +#define io_check_condition(fd, condition) \ + ((((condition) & IO_READ) && \ + FD_ISSET((fd), &tmp_read_fds)) || \ + (((condition) & IO_WRITE) && \ + FD_ISSET((fd), &tmp_write_fds))) + +void io_loop_handler_run(IOLoop ioloop) +{ + struct timeval tv; + IO io, next; + int ret, fd, condition, destroyed, t_id; + + /* get the time left for next timeout task */ + io_loop_get_wait_time(ioloop->timeouts, &tv, NULL); + + memcpy(&tmp_read_fds, &ioloop->handler_data->read_fds, sizeof(fd_set)); + memcpy(&tmp_write_fds, &ioloop->handler_data->write_fds, + sizeof(fd_set)); + + ret = select(ioloop->highest_fd + 1, &tmp_read_fds, &tmp_write_fds, + NULL, &tv); + if (ret < 0 && errno != EINTR) + i_warning("select() : %m"); + + /* execute timeout handlers */ + io_loop_handle_timeouts(ioloop); + + if (ret <= 0 || !ioloop->running) { + /* no I/O events */ + return; + } + + /* execute the I/O handlers in prioritized order */ + for (io = ioloop->ios; io != NULL; io = next) { + next = io->next; + + fd = io->fd; + condition = io->condition; + + destroyed = io->destroyed; + if (destroyed) + io_destroy(ioloop, io); + + if (!io_check_condition(fd, condition)) + continue; + + if (!destroyed) { + t_id = t_push(); + io->func(io->user_data, io->fd, io); + if (t_pop() != t_id) + i_panic("Leaked a t_pop() call!"); + } + + if (--ret == 0) + break; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ioloop.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,351 @@ +/* + ioloop.c : I/O loop + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +/* FIXME: inserting IO is slow if there's lots of them. I should add a linked + list of priorities pointing to first item in the list with the priority. */ + +#include "lib.h" +#include "ioloop-internal.h" + +#undef timercmp +#define timercmp(tvp, uvp) \ + ((tvp)->tv_sec > (uvp)->tv_sec || \ + ((tvp)->tv_sec == (uvp)->tv_sec && \ + (tvp)->tv_usec > (uvp)->tv_usec)) + +time_t ioloop_time; +struct timeval ioloop_timeval; + +static IOLoop current_ioloop = NULL; + +static void update_highest_fd(IOLoop ioloop) +{ + IO io; + int max_highest_fd; + + max_highest_fd = ioloop->highest_fd-1; + ioloop->highest_fd = -1; + + for (io = ioloop->ios; io != NULL; io = io->next) { + if (!io->destroyed && io->fd > ioloop->highest_fd) { + ioloop->highest_fd = io->fd; + + if (ioloop->highest_fd == max_highest_fd) + break; + } + } +} + +static void io_list_insert(IOLoop ioloop, IO io) +{ + IO prev, next; + + prev = NULL; + for (next = ioloop->ios; next != NULL; next = next->next) { + if (next->priority >= io->priority) + break; + prev = next; + } + + if (prev == NULL) + ioloop->ios = io; + else { + io->prev = prev; + prev->next = io; + } + + if (next != NULL) { + io->next = next; + next->prev = io; + } +} + +IO io_add(int fd, int condition, IOFunc func, void *data) +{ + return io_add_priority(fd, IO_PRIORITY_DEFAULT, + condition, func, data); +} + +IO io_add_priority(int fd, int priority, int condition, + IOFunc func, void *user_data) +{ + IO io; + + i_assert(fd >= 0); + i_assert(func != NULL); + + io = p_new(current_ioloop->pool, struct _IO, 1); + io->fd = fd; + io->priority = priority; + io->condition = condition; + + io->func = func; + io->user_data = user_data; + + if (io->fd > current_ioloop->highest_fd) + current_ioloop->highest_fd = io->fd; + + io_loop_handle_add(current_ioloop, io->fd, io->condition); + io_list_insert(current_ioloop, io); + + return io; +} + +void io_remove(IO io) +{ + i_assert(io != NULL); + + /* notify the real I/O handler */ + io_loop_handle_remove(current_ioloop, io->fd, io->condition); + + /* check if we removed the highest fd */ + if (io->fd == current_ioloop->highest_fd) + update_highest_fd(current_ioloop); + + io->destroyed = TRUE; + io->fd = -1; +} + +void io_destroy(IOLoop ioloop, IO io) +{ + /* remove from list */ + if (io->prev == NULL) + ioloop->ios = io->next; + else + io->prev->next = io->next; + + if (io->next != NULL) + io->next->prev = io->prev; + + p_free(ioloop->pool, io); +} + +static void timeout_list_insert(IOLoop ioloop, Timeout timeout) +{ + Timeout *t; + struct timeval *next_run; + + next_run = &timeout->next_run; + for (t = &ioloop->timeouts; *t != NULL; t = &(*t)->next) { + if (timercmp(&(*t)->next_run, next_run)) + break; + } + + timeout->next = *t; + *t = timeout; +} + +inline static void timeout_update_next(Timeout timeout, struct timeval *tv_now) +{ + if (tv_now == NULL) + gettimeofday(&timeout->next_run, NULL); + else { + timeout->next_run.tv_sec = tv_now->tv_sec; + timeout->next_run.tv_usec = tv_now->tv_usec; + } + + /* we don't want microsecond accuracy or this function will be + called all the time - millisecond is more than enough */ + timeout->next_run.tv_usec /= 1000; + timeout->next_run.tv_usec *= 1000; + + timeout->next_run.tv_sec += timeout->msecs/1000; + timeout->next_run.tv_usec += (timeout->msecs%1000)*1000; + + if (timeout->next_run.tv_usec > 1000000) { + timeout->next_run.tv_sec++; + timeout->next_run.tv_usec -= 1000000; + } +} + +Timeout timeout_add(int msecs, TimeoutFunc func, void *user_data) +{ + Timeout timeout; + + timeout = p_new(current_ioloop->pool, struct _Timeout, 1); + timeout->msecs = msecs; + + timeout->func = func; + timeout->user_data = user_data; + + timeout_update_next(timeout, NULL); + timeout_list_insert(current_ioloop, timeout); + return timeout; +} + +void timeout_remove(Timeout timeout) +{ + i_assert(timeout != NULL); + + timeout->destroyed = TRUE; +} + +void timeout_destroy(IOLoop ioloop, Timeout timeout) +{ + Timeout *t; + + for (t = &ioloop->timeouts; *t != NULL; t = &(*t)->next) { + if (*t == timeout) + break; + } + *t = timeout->next; + + p_free(ioloop->pool, timeout); +} + +int io_loop_get_wait_time(Timeout timeout, struct timeval *tv, + struct timeval *tv_now) +{ + if (timeout == NULL) + return INT_MAX; + + if (tv_now == NULL) + gettimeofday(tv, NULL); + else { + tv->tv_sec = tv_now->tv_sec; + tv->tv_usec = tv_now->tv_usec; + } + + tv->tv_sec = timeout->next_run.tv_sec - tv->tv_sec; + tv->tv_usec = timeout->next_run.tv_usec - tv->tv_usec; + if (tv->tv_usec < 0) { + tv->tv_sec--; + tv->tv_usec += 1000000; + } + + if (tv->tv_sec > 0 || (tv->tv_sec == 0 && tv->tv_usec > 0)) + return tv->tv_sec*1000 + tv->tv_usec/1000; + + /* no need to calculate the times again with this timeout */ + tv->tv_sec = tv->tv_usec = 0; + timeout->run_now = TRUE; + return 0; +} + +void io_loop_handle_timeouts(IOLoop ioloop) +{ + Timeout t, next; + struct timeval tv; + int t_id; + + gettimeofday(&ioloop_timeval, NULL); + ioloop_time = ioloop_timeval.tv_sec; + + if (ioloop->timeouts == NULL || !ioloop->timeouts->run_now) + return; + + for (t = ioloop->timeouts; t != NULL; t = next) { + next = t->next; + + if (t->destroyed) { + timeout_destroy(ioloop, t); + continue; + } + + if (!t->run_now) { + io_loop_get_wait_time(t, &tv, &ioloop_timeval); + + if (!t->run_now) + break; + } + + t->run_now = FALSE; + timeout_update_next(t, &ioloop_timeval); + + t_id = t_push(); + t->func(t->user_data, t); + if (t_pop() != t_id) + i_panic("Leaked a t_pop() call!"); + } +} + +void io_loop_run(IOLoop ioloop) +{ + ioloop->running = TRUE; + while (ioloop->running) + io_loop_handler_run(ioloop); +} + +void io_loop_stop(IOLoop ioloop) +{ + ioloop->running = FALSE; +} + +void io_loop_set_running(IOLoop ioloop) +{ + ioloop->running = TRUE; +} + +IOLoop io_loop_create(void) +{ + IOLoop ioloop; + + /* initialize time */ + gettimeofday(&ioloop_timeval, NULL); + ioloop_time = ioloop_timeval.tv_sec; + + ioloop = i_new(struct _IOLoop, 1); + ioloop->pool = pool_create("I/O loop", 10240, TRUE); + ioloop->highest_fd = -1; + + io_loop_handler_init(ioloop); + + ioloop->prev = current_ioloop; + current_ioloop = ioloop; + + return ioloop; +} + +void io_loop_destroy(IOLoop ioloop) +{ + while (ioloop->ios != NULL) { + IO io = ioloop->ios; + + if (!io->destroyed) { + i_warning("I/O leak: %p (%d)", io->func, io->fd); + io_remove(io); + } + io_destroy(ioloop, io); + } + + while (ioloop->timeouts != NULL) { + Timeout to = ioloop->timeouts; + + if (!to->destroyed) { + i_warning("Timeout leak: %p", to->func); + timeout_remove(to); + } + timeout_destroy(ioloop, to); + } + + io_loop_handler_deinit(ioloop); + + /* ->prev won't work unless loops are destroyed in create order */ + i_assert(ioloop == current_ioloop); + current_ioloop = current_ioloop->prev; + + pool_unref(ioloop->pool); + i_free(ioloop); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/ioloop.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,43 @@ +#ifndef __IOLOOP_H +#define __IOLOOP_H + +#include <time.h> + +#define IO_READ (1 << 0) +#define IO_WRITE (1 << 1) + +#define IO_PRIORITY_LOW 100 +#define IO_PRIORITY_DEFAULT 0 +#define IO_PRIORITY_HIGH -100 + +typedef void (*IOFunc) (void *user_data, int fd, IO io); +typedef void (*TimeoutFunc) (void *user_data, Timeout timeout); + +/* Time when the I/O loop started calling handlers. + Can be used instead of time(NULL). */ +extern time_t ioloop_time; +extern struct timeval ioloop_timeval; + +/* I/O listeners - you can create different handlers for IO_READ and IO_WRITE, + but make sure you don't create multiple handlers of same type, it's not + checked and removing one will stop the other from working as well. */ +IO io_add(int fd, int condition, IOFunc func, void *user_data); +IO io_add_priority(int fd, int priority, int condition, + IOFunc func, void *user_data); +void io_remove(IO io); + +/* Timeout handlers */ +Timeout timeout_add(int msecs, TimeoutFunc func, void *user_data); +void timeout_remove(Timeout timeout); + +void io_loop_run(IOLoop ioloop); +void io_loop_stop(IOLoop ioloop); /* safe to run in signal handler */ + +/* call these if you wish to run the iteration only once */ +void io_loop_set_running(IOLoop ioloop); +void io_loop_handler_run(IOLoop ioloop); + +IOLoop io_loop_create(void); +void io_loop_destroy(IOLoop ioloop); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lib-signals.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,84 @@ +/* + lib-signals.c : Handling of commonly used signals + + Copyright (c) 2001-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "lib-signals.h" + +#include <stdio.h> +#include <signal.h> + +int lib_signal_hup, lib_signal_kill; +static void (*quit_handler) (int); + +static void sig_hup(int signo) +{ +#ifndef HAVE_SIGACTION + /* some systems may have changed the signal handler to default one */ + signal(SIGHUP, sig_hup); +#endif + + lib_signal_hup = signo; +} + +static void sig_quit(int signo) +{ + /* if we get killed after this, just die instead of coming back here. */ + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + /* quit the I/O loop, deinitialization is be done properly */ + lib_signal_kill = signo; + quit_handler(signo); +} + +void lib_init_signals(void (*sig_quit_handler) (int)) +{ +#ifdef HAVE_SIGACTION + struct sigaction act; +#endif + + lib_signal_kill = 0; + lib_signal_hup = 0; + quit_handler = sig_quit_handler; + + /* signal() behaviour is a bit inconsistent between systems + after the signal handler has been called. If the signal + isn't ignored, or your handler doesn't kill the program, + sigaction() should be used. */ +#ifdef HAVE_SIGACTION + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = sig_hup; + sigaction(SIGHUP, &act, NULL); +#else + signal(SIGHUP, sig_hup); +#endif + + /* these signals should be called only once, so it's safe to use + signal() */ + signal(SIGINT, sig_quit); + signal(SIGTERM, sig_quit); + signal(SIGPIPE, SIG_IGN); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lib-signals.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,8 @@ +#ifndef __LIB_SIGNALS_H +#define __LIB_SIGNALS_H + +extern int lib_signal_hup, lib_signal_kill; + +void lib_init_signals(void (*sig_quit_handler) (int)); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lib.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,56 @@ +/* + lib.c : Initialize the library functions + + Copyright (c) 2001-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" + +#include <stdlib.h> +#include <time.h> + +unsigned int nearest_power(unsigned int num) +{ + unsigned int n = 1; + + i_assert(num <= (unsigned int) (1 << (BITS_IN_UINT-1))); + + while (n < num) n <<= 1; + return n; +} + +void lib_init(void) +{ + /* standard way to get rand() return different values. */ + srand((unsigned int) time(NULL)); + + failures_init(); + temp_mempool_init(); + imem_init(); +} + +void lib_deinit(void) +{ + imem_deinit(); + temp_mempool_deinit(); + failures_deinit(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/lib.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,37 @@ +#ifndef __LIB_H +#define __LIB_H + +/* default system includes - keep these at minimum.. */ +#include <string.h> /* strcmp() etc. */ +#include <stdarg.h> /* va_list is used everywhere */ +#include <errno.h> /* error checking is good */ +#include <sys/types.h> /* many other includes want this */ + +typedef struct _IOLoop *IOLoop; +typedef struct _IO *IO; +typedef struct _Timeout *Timeout; + +typedef struct _IPADDR IPADDR; +typedef struct _IOBuffer IOBuffer; +typedef struct _TempString TempString; + +/* default lib includes */ +#ifdef HAVE_CONFIG_H +# include "../../config.h" +#endif +#include "compat.h" +#include "macros.h" +#include "failures.h" + +#include "mempool.h" +#include "temp-mempool.h" +#include "imem.h" + +#include "strfuncs.h" + +unsigned int nearest_power(unsigned int num); + +void lib_init(void); +void lib_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/macros.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,180 @@ +#ifndef __MACROS_H +#define __MACROS_H + +/* several useful macros, mostly from glib.h */ + +#ifndef NULL +# define NULL ((void *)0) +#endif + +#ifndef FALSE +# define FALSE (0) +#endif + +#ifndef TRUE +# define TRUE (!FALSE) +#endif + +#define BITS_IN_UINT (CHAR_BIT * sizeof(unsigned int)) + +#define MEM_ALIGN(size) \ + (((size) + MEM_ALIGN_SIZE-1) & ~((unsigned int) MEM_ALIGN_SIZE-1)) + +/* Don't use simply MIN/MAX, as they're often defined elsewhere in include + files that are included after this file generating tons of warnings. */ +#define I_MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define I_MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#undef CLAMP +#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) + +#undef NVL +#define NVL(str, nullstr) ((str) != NULL ? (str) : (nullstr)) + +#define POINTER_TO_INT(p) ((int) (p)) +#define POINTER_TO_UINT(p) ((unsigned int) (p)) + +#define INT_TO_POINTER(i) ((void *) (i)) +#define UINT_TO_POINTER(u) ((void *) (u)) + +/* Define VA_COPY() to do the right thing for copying va_list variables. */ +#ifndef VA_COPY +# if defined (__GNUC__) && defined (__PPC__) && (defined (_CALL_SYSV) || defined (_WIN32)) +# define VA_COPY(ap1, ap2) (*(ap1) = *(ap2)) +# elif defined (VA_COPY_AS_ARRAY) +# define VA_COPY(ap1, ap2) i_memmove ((ap1), (ap2), sizeof (va_list)) +# else /* va_list is a pointer */ +# define VA_COPY(ap1, ap2) ((ap1) = (ap2)) +# endif /* va_list is a pointer */ +#endif + +/* Provide convenience macros for handling structure + * fields through their offsets. + */ +#define STRUCT_OFFSET(struct_p, member) \ + ((long) ((char *) &((struct_p)->member) - (char *) (struct_p))) +#define STRUCT_MEMBER_P(struct_p, struct_offset) \ + ((void *) ((char *) (struct_p) + (long) (struct_offset))) +#define STRUCT_MEMBER(member_type, struct_p, struct_offset) \ + (*(member_type *) G_STRUCT_MEMBER_P((struct_p), (struct_offset))) + +/* Provide simple macro statement wrappers (adapted from Perl): + STMT_START { statements; } STMT_END; + can be used as a single statement, as in + if (x) STMT_START { ... } STMT_END; else ... + + For gcc we will wrap the statements within `({' and `})' braces. + For SunOS they will be wrapped within `if (1)' and `else (void) 0', + and otherwise within `do' and `while (0)'. */ +#if !(defined (STMT_START) && defined (STMT_END)) +# if defined (__GNUC__) && !defined (__STRICT_ANSI__) && !defined (__cplusplus) +# define STMT_START (void)( +# define STMT_END ) +# else +# if (defined (sun) || defined (__sun__)) +# define STMT_START if (1) +# define STMT_END else (void)0 +# else +# define STMT_START do +# define STMT_END while (0) +# endif +# endif +#endif + +/* Provide macros to feature the GCC function attribute. */ +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ > 4) +# define __attr_format__(format_idx, arg_idx) \ + __attribute__((format (printf, format_idx, arg_idx))) +# define __attr_format_arg__(arg_idx) \ + __attribute__((format_arg (arg_idx))) +# define __attr_unused__ __attribute__((unused)) +# define __attr_noreturn__ __attribute__((noreturn)) +# define __attr_const__ __attribute__((const)) +#else +# define __attr_format__(format_idx, arg_idx) +# define __attr_format_arg__(arg_idx) +# define __attr_unused__ +# define __attr_noreturn__ +# define __attr_const__ +# define __attr_unused__ +#endif + +/* Wrap the gcc __PRETTY_FUNCTION__ and __FUNCTION__ variables with + macros, so we can refer to them as strings unconditionally. */ +#ifdef __GNUC__ +# define GNUC_FUNCTION __FUNCTION__ +# define GNUC_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +# define GNUC_FUNCTION "" +# define GNUC_PRETTY_FUNCTION "" +#endif + +/* Provide macros for error handling. */ +#ifdef DISABLE_CHECKS +# define i_assert(expr) +# define return_if_fail(expr) +# define return_val_if_fail(expr,val) +#elif defined (__GNUC__) && !defined (__STRICT_ANSI__) + +#define i_assert(expr) STMT_START{ \ + if (!(expr)) \ + i_panic("file %s: line %d (%s): assertion failed: (%s)", \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__, \ + #expr); }STMT_END + +#define return_if_fail(expr) STMT_START{ \ + if (!(expr)) \ + { \ + i_warning("file %s: line %d (%s): assertion `%s' failed.", \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__, \ + #expr); \ + return; \ + }; }STMT_END + +#define return_val_if_fail(expr,val) STMT_START{ \ + if (!(expr)) \ + { \ + i_warning("file %s: line %d (%s): assertion `%s' failed.", \ + __FILE__, \ + __LINE__, \ + __PRETTY_FUNCTION__, \ + #expr); \ + return val; \ + }; }STMT_END + +#else /* !__GNUC__ */ + +#define i_assert(expr) STMT_START{ \ + if (!(expr)) \ + i_panic("file %s: line %d: assertion failed: (%s)", \ + __FILE__, \ + __LINE__, \ + #expr); }STMT_END + +#define return_if_fail(expr) STMT_START{ \ + if (!(expr)) \ + { \ + i_warning("file %s: line %d: assertion `%s' failed.", \ + __FILE__, \ + __LINE__, \ + #expr); \ + return; \ + }; }STMT_END + +#define return_val_if_fail(expr, val) STMT_START{ \ + if (!(expr)) \ + { \ + i_warning("file %s: line %d: assertion `%s' failed.", \ + __FILE__, \ + __LINE__, \ + #expr); \ + return val; \ + }; }STMT_END + +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/md5.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,314 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to md5_init, call md5_update as + * needed on buffers full of bytes, and then call md5_Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* parts of this file are : + * Written March 1993 by Branko Lankester + * Modified June 1993 by Colin Plumb for altered md5.c. + * Modified October 1995 by Erik Troan for RPM + */ + + +#include <stdio.h> +#include <string.h> +#include "md5.h" + +static void md5_transform (unsigned int buf[4], const unsigned int in[16]); + +static int _ie = 0x44332211; +static union _endian { int i; char b[4]; } *_endian = (union _endian *)&_ie; +#define IS_BIG_ENDIAN() (_endian->b[0] == '\x44') +#define IS_LITTLE_ENDIAN() (_endian->b[0] == '\x11') + + +/* + * Note: this code is harmless on little-endian machines. + */ +static void +_byte_reverse (unsigned char *buf, unsigned int longs) +{ + unsigned int t; + do { + t = (unsigned int) ((unsigned int) buf[3] << 8 | buf[2]) << 16 | + ((unsigned int) buf[1] << 8 | buf[0]); + *(unsigned int *) buf = t; + buf += 4; + } while (--longs); +} + +/** + * md5_init: Initialise an md5 context object + * @ctx: md5 context + * + * Initialise an md5 buffer. + * + **/ +void +md5_init (MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bits[0] = 0; + ctx->bits[1] = 0; + + if (IS_BIG_ENDIAN()) + ctx->doByteReverse = 1; + else + ctx->doByteReverse = 0; +} + + + +/** + * md5_update: add a buffer to md5 hash computation + * @ctx: conetxt object used for md5 computaion + * @buf: buffer to add + * @len: buffer length + * + * Update context to reflect the concatenation of another buffer full + * of bytes. Use this to progressively construct an md5 hash. + **/ +void +md5_update (MD5Context *ctx, const void *buf, unsigned int len) +{ + unsigned int t; + + /* Update bitcount */ + + t = ctx->bits[0]; + if ((ctx->bits[0] = t + ((unsigned int) len << 3)) < t) + ctx->bits[1]++; /* Carry from low to high */ + ctx->bits[1] += len >> 29; + + t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ + + /* Handle any leading odd-sized chunks */ + + if (t) { + unsigned char *p = (unsigned char *) ctx->in + t; + + t = 64 - t; + if (len < t) { + memcpy (p, buf, len); + return; + } + memcpy (p, buf, t); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (unsigned int *) ctx->in); + buf = (char *) buf + t; + len -= t; + } + /* Process data in 64-byte chunks */ + + while (len >= 64) { + memcpy (ctx->in, buf, 64); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (unsigned int *) ctx->in); + buf = (char *) buf + 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + + memcpy (ctx->in, buf, len); +} + + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +/** + * md5_final: copy the final md5 hash to a bufer + * @digest: 16 bytes buffer + * @ctx: context containing the calculated md5 + * + * copy the final md5 hash to a bufer + **/ +void +md5_final (MD5Context *ctx, unsigned char digest[16]) +{ + unsigned int count; + unsigned char *p; + + /* Compute number of bytes mod 64 */ + count = (ctx->bits[0] >> 3) & 0x3F; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + p = ctx->in + count; + *p++ = 0x80; + + /* Bytes of padding needed to make 64 bytes */ + count = 64 - 1 - count; + + /* Pad out to 56 mod 64 */ + if (count < 8) { + /* Two lots of padding: Pad the first block to 64 bytes */ + memset (p, 0, count); + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 16); + md5_transform (ctx->buf, (unsigned int *) ctx->in); + + /* Now fill the next block with 56 bytes */ + memset (ctx->in, 0, 56); + } else { + /* Pad block to 56 bytes */ + memset (p, 0, count - 8); + } + if (ctx->doByteReverse) + _byte_reverse (ctx->in, 14); + + /* Append length in bits and transform */ + ((unsigned int *) ctx->in)[14] = ctx->bits[0]; + ((unsigned int *) ctx->in)[15] = ctx->bits[1]; + + md5_transform (ctx->buf, (unsigned int *) ctx->in); + if (ctx->doByteReverse) + _byte_reverse ((unsigned char *) ctx->buf, 4); + memcpy (digest, ctx->buf, 16); +} + + + + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f, w, x, y, z, data, s) \ + ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x ) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. md5_Update blocks + * the data and converts bytes into longwords for this routine. + */ +static void +md5_transform (unsigned int buf[4], const unsigned int in[16]) +{ + register unsigned int a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP (F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP (F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP (F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP (F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP (F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP (F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP (F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP (F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP (F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP (F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP (F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP (F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP (F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP (F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP (F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP (F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP (F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP (F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP (F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP (F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP (F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP (F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP (F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP (F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP (F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP (F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP (F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP (F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP (F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP (F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP (F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP (F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP (F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP (F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP (F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP (F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP (F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP (F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP (F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP (F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP (F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP (F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP (F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP (F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP (F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP (F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP (F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP (F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP (F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP (F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP (F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP (F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP (F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP (F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP (F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP (F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP (F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP (F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP (F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP (F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP (F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP (F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP (F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP (F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + + +/** + * md5_get_digest: get the md5 hash of a buffer + * @buffer: byte buffer + * @buffer_size: buffer size (in bytes) + * @digest: 16 bytes buffer receiving the hash code. + * + * Get the md5 hash of a buffer. The result is put in + * the 16 bytes buffer @digest . + **/ +void +md5_get_digest (const char *buffer, unsigned int buffer_size, + unsigned char digest[16]) +{ + MD5Context ctx; + + md5_init (&ctx); + md5_update (&ctx, buffer, buffer_size); + md5_final (&ctx, digest); + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/md5.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,45 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to rpmMD5Init, call rpmMD5Update as + * needed on buffers full of bytes, and then call rpmMD5Final, which + * will fill a supplied 16-byte array with the digest. + */ + +/* parts of this file are : + * Written March 1993 by Branko Lankester + * Modified June 1993 by Colin Plumb for altered md5.c. + * Modified October 1995 by Erik Troan for RPM + */ + + +#ifndef MD5_H +#define MD5_H + +typedef struct { + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; + int doByteReverse; +} MD5Context; + + +void md5_get_digest (const char *buffer, unsigned int buffer_size, + unsigned char digest[16]); + +/* raw routines */ +void md5_init (MD5Context *ctx); +void md5_update (MD5Context *ctx, const void *buf, unsigned int len); +void md5_final (MD5Context *ctx, unsigned char digest[16]); + + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mempool-allocfree.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,843 @@ +/* + mempool-allocfree.c : Memory pool manager for custom alloc+free + + Copyright (c) 2001-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +/* TODO: + + - p_free() could check better that it's freeing a properly allocated + memory from pool it was told to. + + - try to keep optimal pool sizes ie. keep them in the size they're + using 90% of the time + - add statistics functions that print/return the memory usage + - don't free() anything, save them to unused-list and reuse later. +*/ + +/* extensive debugging */ +/* #define POOL_DEBUG */ + +/* always clear the memory area that has been free'd. for debugging mostly */ +/* #define POOL_FREE_CLEAR 0xd0 */ + +/* Save the used pool block for each memory allocation, so you can be sure + that the memory is being free'd from correct pool. Also, with this option + we don't need to compare pointers which makes this code fully ANSI-C + compatible :) */ +#define POOL_SAVE_BLOCK + +#include "lib.h" +#include "mempool.h" + +#include <stdlib.h> + +#define is_pool(pool) ((pool) != NULL && (pool)->magic == 0xbeef) + +#define check_pool(pool) \ + if (!is_pool(pool)) \ + i_panic("Trying to use invalid memory pool, aborting") + +#define MEM_FREE_BIT 0x80000000 + +#define is_mem_free(size) (((size) & MEM_FREE_BIT) != 0) +#define mem_size(size) ((size) & ~MEM_FREE_BIT) + +#define mem2int(_mem, _int) \ + memcpy(&(_int), ((unsigned char *) (_mem)), sizeof(int)); + +#define int2mem(_mem, _int) \ + memcpy(((unsigned char *) (_mem)), &(_int), sizeof(int)); + +#ifdef POOL_SAVE_BLOCK +# define ALLOC_EXTRA_SIZE sizeof(PoolBlock *) +#else +# define ALLOC_EXTRA_SIZE 0 +#endif + +/* max. number of bytes to even try to allocate. This is done just to avoid + allocating less memory than was actually requested because of integer + overflows. -128 is much more than is actually needed. */ +#define MAX_ALLOC_SIZE (UINT_MAX - 128) + +typedef struct { + /* total number of bytes in data[] */ + unsigned int size; + /* free data position at data+free_index, none if >= size */ + unsigned int free_index; + /* largest data[] size (not including int size) */ + unsigned int largest_free_space; + + /* unsigned int size - if the last bit is set the data block is used + if 0, the rest of the block is free + unsigned char data[] + unsigned int size + ... */ + unsigned char data[1]; +} PoolBlock; + +#define BLOCK_INCREMENT_COUNT 5 +typedef struct { + struct Pool pool; + + unsigned short magic; /* 0xbeef */ + int refcount; + + int num_blocks, used_blocks; + int first_block_with_space; /* -1 = all used */ + PoolBlock **blocks; /* num_blocks size */ + + char name[1]; /* human readable name for blocks - used in statistics. + variable size */ +} AllocfreePool; + +static struct Pool static_allocfree_pool; + +static void pool_allocfree_free(Pool pool, void *mem); + +static void pool_block_create(AllocfreePool *apool, unsigned int size) +{ + PoolBlock *block; + int alloc_size; + + i_assert(size > sizeof(int)); + + if (apool->used_blocks >= apool->num_blocks) { + apool->num_blocks += BLOCK_INCREMENT_COUNT; + + alloc_size = sizeof(PoolBlock *) * apool->num_blocks; + apool->blocks = apool->blocks == NULL ? malloc(alloc_size) : + realloc(apool->blocks, alloc_size); + if (apool->blocks == NULL) { + i_panic("pool_block_create(): " + "Out of memory when reallocating %d bytes", + alloc_size); + } + } + + alloc_size = sizeof(PoolBlock)-1 + size; + block = malloc(alloc_size); + if (block == NULL) { + i_panic("pool_block_create(): " + "Out of memory when allocating %d bytes", alloc_size); + } + + block->size = size; + block->free_index = 0; + block->largest_free_space = size-sizeof(int)-ALLOC_EXTRA_SIZE; + memset(block->data, 0, sizeof(int)); + + if (apool->first_block_with_space == -1) + apool->first_block_with_space = apool->used_blocks; + apool->blocks[apool->used_blocks] = block; + apool->used_blocks++; +} + +static int pool_block_get_pos(AllocfreePool *apool, PoolBlock *block) +{ + int pos; + + for (pos = 0; pos < apool->used_blocks; pos++) { + if (apool->blocks[pos] == block) + return pos; + } + + return -1; +} + +static void pool_reset_first_free_block(AllocfreePool *apool) +{ + int pos; + + pos = apool->first_block_with_space; + i_assert(pos >= 0); + + apool->first_block_with_space = -1; + for (; pos < apool->used_blocks; pos++) { + if (apool->blocks[pos]->largest_free_space > 0) { + apool->first_block_with_space = pos; + break; + } + } +} + +static void pool_block_destroy(AllocfreePool *apool, PoolBlock *block) +{ + int pos; + + pos = pool_block_get_pos(apool, block); + if (pos == -1) + return; + + memmove(apool->blocks+pos, apool->blocks+pos+1, + sizeof(PoolBlock *) * (apool->num_blocks-pos-1)); + apool->used_blocks--; + + if (apool->first_block_with_space > pos) + apool->first_block_with_space--; + else if (apool->first_block_with_space == pos) + pool_reset_first_free_block(apool); + free(block); +} + +static PoolBlock *pool_block_find(AllocfreePool *apool, void *mem) +{ + PoolBlock *block; + int pos; + +#ifdef POOL_SAVE_BLOCK + memcpy(&block, (unsigned char *) mem - sizeof(PoolBlock *), + sizeof(PoolBlock *)); + + for (pos = 0; pos < apool->used_blocks; pos++) { + if (apool->blocks[pos] == block) + return block; + } +#else + + /* NOTE: this may be a portability problem, since it compares + unrelated pointers. */ + for (pos = 0; pos < apool->used_blocks; pos++) { + block = apool->blocks[pos]; + + if ((unsigned char *) mem >= block->data && + (unsigned char *) mem < block->data + block->size) + return block; + } +#endif + + return NULL; +} + +#ifdef POOL_DEBUG +static void pool_block_dump(PoolBlock *block) +{ + unsigned char *mem; + unsigned int holesize, holesize_real; + + printf("Block: size %d\n", block->size); + + mem = block->data; + mem2int(mem, holesize); + while (holesize > 0) { + holesize_real = mem_size(holesize); + printf(" - %u %s\n", holesize_real, + is_mem_free(holesize) ? " (free)" : ""); + + mem += holesize_real + sizeof(int); + if (mem-block->data == block->size) + break; + + if (mem-block->data > block->size) + i_panic("pool_block_alloc() : corrupted pool"); + + mem2int(mem, holesize); + } +} + +static void pool_dump(AllocfreePool *apool) +{ + int i; + + printf("Dumping pool: %s\n", apool->name); + + for (i = 0; i < apool->used_blocks; i++) + pool_block_dump(apool->blocks[i]); +} +#endif + +static void *pool_block_alloc(AllocfreePool *apool, PoolBlock *block, + unsigned int size) +{ + unsigned char *mem, *alloc; + unsigned int largest, holesize, holesize_real, avail_size; + unsigned int alloc_index, next_free_index; + +#ifdef POOL_DEBUG + printf("pool_block_alloc(%s, %d)\n", apool->name, size); +#endif + + size += ALLOC_EXTRA_SIZE; + + /* search for first large enough free space in the block. + remember the largest free block so far so if we use the + current largest, we don't have to scan the largest one + from the beginning. */ + largest = 0; + mem = block->data + block->free_index; + mem2int(mem, holesize); + while (holesize > 0) { + holesize_real = mem_size(holesize); + if (holesize_real + sizeof(int)*2 > block->size) + i_panic("pool_block_alloc() : corrupted pool"); + + if (is_mem_free(holesize)) { + if (size <= holesize_real) + break; + + if (holesize_real-ALLOC_EXTRA_SIZE > largest) + largest = holesize_real-ALLOC_EXTRA_SIZE; + } + + mem += holesize_real + sizeof(int); + mem2int(mem, holesize); + } + + avail_size = mem_size(holesize); + if (avail_size == 0) { + /* rest of the block is free */ + if ((int) (mem-block->data)+size >= block->size) { + i_panic("pool_block_alloc(): not enough space " + "in block (should have been)"); + } + + avail_size = block->size - (int) (mem-block->data) - + sizeof(int); + } else if (avail_size-ALLOC_EXTRA_SIZE != block->largest_free_space) { + /* we didn't use the largest free space in block, + so we don't need to scan the rest of the data to + search it */ + largest = block->largest_free_space; + } + + if (avail_size < size+sizeof(int)*3) { + /* don't bother leaving small holes in the pool */ + size = avail_size; + } + +#ifdef POOL_DEBUG + if (size == avail_size) + printf(" - pool is now full\n"); +#endif + + /* [i.....] -> [iXXi..] (i = space size, X = data, . = empty space) */ + int2mem(mem, size); + alloc = mem + sizeof(int); + alloc_index = (int) (mem-block->data); + +#ifdef POOL_SAVE_BLOCK + memcpy(alloc, &block, sizeof(PoolBlock *)); + alloc += sizeof(PoolBlock *); +#endif + + /* set mem point to beginning of next hole */ + mem += size + sizeof(int); + + if (size < avail_size) { + /* we didn't use the whole space, mark it unused */ + avail_size = holesize == 0 ? 0 : MEM_FREE_BIT | + (avail_size - size - sizeof(int)); + int2mem(mem, avail_size); + next_free_index = (int) (mem-block->data); + } else if (block->free_index == alloc_index) { + /* we used the first free hole, get the next one */ + next_free_index = block->size; + while (mem < block->data+block->size) { + mem2int(mem, holesize); + if (holesize == 0) { + next_free_index = (int) (mem-block->data); + break; + } + + holesize_real = mem_size(holesize); + if (is_mem_free(holesize)) { + next_free_index = + (int) (mem-block->data); + break; + } + + mem += holesize_real + sizeof(int); + } + } else { + /* just to suppress compiler warnings */ + next_free_index = 0; + } + + /* update the first free space index */ + if (block->free_index == alloc_index) + block->free_index = next_free_index; + + if (holesize > 0 && largest < block->largest_free_space) { + /* we just used the largest space in the block, + we'll need to find the next largest one and it + could be after the space we just used */ + while (mem < block->data+block->size) { + mem2int(mem, holesize); + if (holesize == 0) + break; + + holesize_real = mem_size(holesize); + if (holesize_real + + sizeof(int)*2 > block->size) + i_panic("pool_block_alloc() : corrupted pool"); + + if (is_mem_free(holesize) && + holesize_real-ALLOC_EXTRA_SIZE > largest) + largest = holesize_real-ALLOC_EXTRA_SIZE; + + mem += holesize_real + sizeof(int); + } + } + + if (holesize == 0 && mem < block->data+block->size) { + /* rest of the block is free */ + holesize = block->size - (int) (mem-block->data + sizeof(int)); + if (holesize-ALLOC_EXTRA_SIZE > largest) + largest = holesize-ALLOC_EXTRA_SIZE; + } + + block->largest_free_space = largest; + if (largest == 0) + pool_reset_first_free_block(apool); + + return alloc; +} + +/* Returns the previous block for "mem", or NULL if either there's no + previous block or if previous block is before any of the free blocks. */ +static unsigned char * +pool_block_prev(PoolBlock *block, unsigned char *find_mem) +{ + unsigned char *mem, *next_mem; + unsigned int holesize; + + if (block->free_index >= (unsigned int) (find_mem-block->data)) + return NULL; + + mem = block->data + block->free_index; + mem2int(mem, holesize); + while (holesize > 0) { + next_mem = mem + mem_size(holesize) + sizeof(int); + if (next_mem == find_mem) + return mem; + + mem = next_mem; + mem2int(mem, holesize); + } + + return NULL; +} + +static void pool_block_free(AllocfreePool *apool, PoolBlock *block, + unsigned char *mem) +{ + unsigned char *next_mem, *prev_mem; + unsigned int holesize, next_holesize, avail_space, prevsize; + unsigned int next_free_index; + int pos; + +#ifdef POOL_SAVE_BLOCK + mem -= sizeof(PoolBlock *); +#endif + mem -= sizeof(int); + mem2int(mem, holesize); + +#ifdef POOL_DEBUG + printf("pool_block_free(%s, %u)\n", apool->name, mem_size(holesize)); +#endif + + if ((holesize & MEM_FREE_BIT) != 0 || + mem_size(holesize) + sizeof(int) > block->size) + i_panic("pool_block_free() : corrupted pool"); + + if ((int) (mem-block->data) + holesize + sizeof(int) >= block->size) { + /* this is the last space in block */ + holesize = 0; + } else { + next_mem = ((unsigned char *) mem) + holesize + sizeof(int); + mem2int(next_mem, next_holesize); + + if (next_holesize == 0) { + /* last used space - combine to free space at end */ + holesize = 0; + } else if (!is_mem_free(next_holesize)) { + /* mark the space free */ + holesize |= MEM_FREE_BIT; + } else { + /* combine the two free spaces */ + holesize = (mem_size(holesize) + sizeof(int) + + mem_size(next_holesize)) | MEM_FREE_BIT; + } + } + int2mem(mem, holesize); + + /* if previous block is free, we can combine the free space */ + prev_mem = pool_block_prev(block, mem); + if (prev_mem != NULL) { + mem2int(prev_mem, prevsize); + if (is_mem_free(prevsize)) { + if (holesize != 0) { + holesize = (mem_size(holesize) + sizeof(int) + + mem_size(prevsize)) | MEM_FREE_BIT; + } + int2mem(prev_mem, holesize); + mem = prev_mem; + } + } + + /* update largest free space size */ + avail_space = holesize != 0 ? mem_size(holesize) : + block->size - (int) (mem-block->data + sizeof(int)); + +#ifdef POOL_FREE_CLEAR + memset(mem + sizeof(int), POOL_FREE_CLEAR, avail_space); +#elif defined (POOL_SAVE_BLOCK) + memset(mem + sizeof(int), 0, sizeof(PoolBlock *)); +#endif + + if (block->largest_free_space < avail_space-ALLOC_EXTRA_SIZE) + block->largest_free_space = avail_space-ALLOC_EXTRA_SIZE; + + /* update the first free space index */ + next_free_index = (int) (mem-block->data); + if (block->free_index > next_free_index) + block->free_index = next_free_index; + + /* update pool's first block with free space index */ + pos = pool_block_get_pos(apool, block); + if (apool->first_block_with_space < 0 || + pos < apool->first_block_with_space) + apool->first_block_with_space = pos; + + if (holesize == 0 && mem == block->data) { + /* FIXME: the block is completely unused, if there's more + than two empty blocks, free them */ + } +} + +Pool pool_allocfree_create(const char *name, unsigned int size) +{ + AllocfreePool *apool; + + i_assert(size > sizeof(int)); + + apool = calloc(sizeof(AllocfreePool) + strlen(name), 1); + if (apool == NULL) + i_panic("pool_create(): Out of memory"); + + apool->pool = static_allocfree_pool; + apool->magic = 0xbeef; + apool->refcount = 1; + + apool->first_block_with_space = -1; + pool_block_create(apool, nearest_power(size)); + + strcpy(apool->name, name); + return &apool->pool; +} + +#ifdef POOL_CHECK_LEAKS +static const char *get_leak_string(const unsigned char *mem, int size) +{ + int i; + + mem += sizeof(int); + +#ifdef POOL_SAVE_BLOCK + mem += sizeof(PoolBlock *); + size -= sizeof(PoolBlock *); +#endif + + for (i = 0; i < size; i++) { + if (mem[i] == '\0') + return (const char *) mem; + + if ((mem[i] & 0x7f) < 32) + break; + } + + return NULL; +} + +static const char *pool_block_count_leaks(PoolBlock *block, int *leak_count, + int *leak_size) +{ + const char *leak_string; + unsigned char *mem; + unsigned int holesize, holesize_real; + + leak_string = NULL; + + mem = block->data; + mem2int(mem, holesize); + while (holesize > 0) { + holesize_real = mem_size(holesize); + if (!is_mem_free(holesize)) { + (*leak_count)++; + *leak_size += holesize_real; + + if (leak_string == NULL) { + leak_string = get_leak_string(mem, + holesize_real); + } + } + + if (holesize_real + sizeof(int)*2 > block->size) + i_panic("pool_block_count_leaks() : corrupted pool"); + + mem += holesize_real + sizeof(int); + mem2int(mem, holesize); + } + + return leak_string; +} + +static void pool_check_leaks(AllocfreePool *apool) +{ + const char *leak_string; + int i, leak_count, leak_size; + + leak_string = NULL; + leak_count = leak_size = 0; + for (i = 0; i < apool->used_blocks; i++) { + PoolBlock *block = apool->blocks[i]; + + if (block->free_index < block->size) { + leak_string = pool_block_count_leaks(block, &leak_count, + &leak_size); + } + } + + if (leak_count > 0) { + i_warning("Pool '%s' leaked %d allocs with " + "total size of %d (%s)", + apool->name, leak_count, leak_size, + leak_string == NULL ? "" : leak_string); + } +} +#endif + +static void pool_destroy(AllocfreePool *apool) +{ + check_pool(apool); + +#ifdef POOL_CHECK_LEAKS + pool_check_leaks(apool); +#endif + + while (apool->used_blocks > 0) + pool_block_destroy(apool, apool->blocks[0]); + free(apool->blocks); + free(apool->name); + free(apool); +} + +static void pool_allocfree_ref(Pool pool) +{ + AllocfreePool *apool = (AllocfreePool *) pool; + + apool->refcount++; +} + +static void pool_allocfree_unref(Pool pool) +{ + AllocfreePool *apool = (AllocfreePool *) pool; + + if (--apool->refcount == 0) + pool_destroy(apool); +} + +static void *pool_allocfree_malloc(Pool pool, unsigned int size) +{ + AllocfreePool *apool = (AllocfreePool *) pool; + PoolBlock *block; + void *mem; + int i, allocsize; + + if (size == 0) + return NULL; + + if (size > MAX_ALLOC_SIZE) + i_panic("Trying to allocate too much memory"); + + /* allocate only aligned amount of memory so alignment comes + always properly */ + size = (size + MEM_ALIGN-1) & ~(MEM_ALIGN-1); + + check_pool(apool); + + /* check if there's enough space in one of the existing blocks */ + block = NULL; + for (i = 0; i < apool->used_blocks; i++) { + if (apool->blocks[i]->largest_free_space >= size) { + block = apool->blocks[i]; + break; + } + } + + if (block == NULL) { + /* create new block to pool */ + allocsize = 2*apool->blocks[apool->used_blocks-1]->size; + if (allocsize-sizeof(int)-ALLOC_EXTRA_SIZE < size) + allocsize = size*2 + sizeof(int) + ALLOC_EXTRA_SIZE; + + pool_block_create(apool, nearest_power(allocsize)); + block = apool->blocks[apool->used_blocks-1]; + } + + mem = pool_block_alloc(apool, block, size); + memset(mem, 0, size); + +#ifdef POOL_DEBUG + pool_dump(apool); +#endif + return mem; +} + +static void *pool_allocfree_realloc(Pool pool, void *mem, unsigned int size) +{ + AllocfreePool *apool = (AllocfreePool *) pool; + unsigned char *mem_size_pos, *oldmem; + unsigned int memsize; + + if (size == 0) { + pool_allocfree_free(pool, mem); + return NULL; + } + + if (mem == NULL) + return pool_allocfree_malloc(pool, size); + + check_pool(apool); + + mem_size_pos = (unsigned char *) mem - sizeof(int); +#ifdef POOL_SAVE_BLOCK + mem_size_pos -= sizeof(PoolBlock *); +#endif + + mem2int(mem_size_pos, memsize); + if (memsize == size) + return mem; + + /* FIXME: shrinking could be done more efficiently, also growing + might be able to check if it can extend it's current allocation */ + oldmem = mem; + mem = pool_allocfree_malloc(pool, size); + memcpy(mem, oldmem, memsize < size ? memsize : size); + pool_allocfree_free(pool, oldmem); + +#ifdef POOL_DEBUG + pool_dump(apool); +#endif + + if (size > memsize) + memset((char *) mem + memsize, 0, size-memsize); + return mem; +} + +static void *pool_allocfree_realloc_min(Pool pool, void *mem, + unsigned int size) +{ + unsigned char *mem_size_pos; + unsigned int memsize; + + if (mem == NULL) + return pool_allocfree_malloc(pool, size); + + mem_size_pos = (unsigned char *) mem - sizeof(int); +#ifdef POOL_SAVE_BLOCK + mem_size_pos -= sizeof(PoolBlock *); +#endif + + mem2int(mem_size_pos, memsize); + if (size <= memsize) + return mem; + + return pool_allocfree_realloc(pool, mem, size); +} + +static void pool_allocfree_free(Pool pool, void *mem) +{ + AllocfreePool *apool = (AllocfreePool *) pool; + PoolBlock *block; + + if (mem == NULL) + return; + + check_pool(apool); + + block = pool_block_find(apool, mem); + if (block == NULL) + i_panic("pool_allocfree_free(): invalid memory address"); + + pool_block_free(apool, block, mem); + +#ifdef POOL_DEBUG + pool_dump(apool); +#endif +} + +static void pool_allocfree_clear(Pool pool) +{ + AllocfreePool *apool = (AllocfreePool *) pool; + int i; + + apool->first_block_with_space = 0; + + for (i = 0; i < apool->used_blocks; i++) { + PoolBlock *block = apool->blocks[i]; + + block->free_index = 0; + block->largest_free_space = block->size - + sizeof(int) - ALLOC_EXTRA_SIZE; + memset(block->data, 0, sizeof(int)); + } +} + +static struct Pool static_allocfree_pool = { + pool_allocfree_ref, + pool_allocfree_unref, + + pool_allocfree_malloc, + pool_allocfree_free, + + pool_allocfree_realloc, + pool_allocfree_realloc_min, + + pool_allocfree_clear +}; + +#ifdef POOL_DEBUG +#include <stdlib.h> + +void mempool_test(void) +{ + Pool p; + void *arr[100]; + int i, j; + + memset(arr, 0, sizeof(arr)); + + p = pool_create("temp", 32); + for (i = 0; i < 10000; i++) { + arr[rand()%100] = p_malloc(p, 4*(rand()%10+1)); + + if (rand()%3 == 1) { + for (j = 0; j < 100; j++) { + if (arr[j] != NULL) + p_free_and_null(p, arr[j]); + } + } + } +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mempool-allocfree.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,47 @@ +#ifndef __MEMPOOL_H +#define __MEMPOOL_H + +#include "macros.h" + +/* #define POOL_CHECK_LEAKS */ + +/* Memory allocated and reallocated (the new data in it) in pools is always + zeroed, it will cost only a few CPU cycles and may well save some debug + time. */ + +typedef struct Pool *Pool; + +Pool pool_create(const char *name, unsigned int size); + +void pool_ref(Pool pool); +void pool_unref(Pool pool); + +#define p_new(pool, type, count) \ + ((type *) p_malloc(pool, (unsigned) sizeof(type) * (count))) +void *p_malloc(Pool pool, unsigned int size); + +void p_free(Pool pool, void *mem); +#define p_free_and_null(pool, rec) \ + STMT_START { \ + p_free(pool, rec); \ + (rec) = NULL; \ + } STMT_END + +/* p_free_clean() should be used when pool is being destroyed, so freeing + memory isn't needed for anything else than detecting memory leaks. */ +#ifdef POOL_CHECK_LEAKS +# define p_free_clean(pool, mem) p_free(pool, mem) +#else +# define p_free_clean(pool, mem) +#endif + +/* reallocate the `mem' to be exactly `size' */ +void *p_realloc(Pool pool, void *mem, unsigned int size); +/* reallocate the `mem' to be at least `size' if it wasn't previously */ +void *p_realloc_min(Pool pool, void *mem, unsigned int size); + +/* Clear the pool. Memory allocated from pool before this call must not be + used after. */ +void p_clear(Pool pool); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mempool-alloconly.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,250 @@ +/* + mempool-alloconly.c : Memory pool for fast allocation of memory without + need to free it in small blocks + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "mempool.h" + +#include <stdlib.h> + +#define MAX_ALLOC_SIZE (UINT_MAX - sizeof(unsigned int)) + +typedef struct _PoolBlock PoolBlock; + +typedef struct { + struct Pool pool; + int refcount; + + PoolBlock *block; + unsigned int last_alloc_size; + + char name[MEM_ALIGN_SIZE]; /* variable size */ +} AlloconlyPool; +#define SIZEOF_ALLOCONLYPOOL (sizeof(AlloconlyPool)-MEM_ALIGN_SIZE) + +struct _PoolBlock { + PoolBlock *prev; + + unsigned int size; + unsigned int left; + + unsigned char data[MEM_ALIGN_SIZE]; /* variable size */ +}; +#define SIZEOF_POOLBLOCK (sizeof(PoolBlock)-MEM_ALIGN_SIZE) + +typedef struct { + unsigned int size; + unsigned char data[MEM_ALIGN_SIZE]; /* variable size */ +} PoolAlloc; +#define SIZEOF_POOLALLOC (sizeof(PoolAlloc)-MEM_ALIGN_SIZE) + +static struct Pool static_alloconly_pool; +static void pool_alloconly_clear(Pool pool); + +static void block_alloc(AlloconlyPool *pool, unsigned int size); +static void *pool_alloconly_realloc_min(Pool pool, void *mem, + unsigned int size); + +Pool pool_alloconly_create(const char *name, unsigned int size) +{ + AlloconlyPool *apool; + int len; + + len = strlen(name); + + apool = calloc(SIZEOF_ALLOCONLYPOOL + len+1, 1); + apool->pool = static_alloconly_pool; + apool->refcount = 1; + + block_alloc(apool, size); + + strcpy(apool->name, name); + return (Pool) apool; +} + +static void pool_alloconly_destroy(AlloconlyPool *apool) +{ + /* destroy all but the last block */ + pool_alloconly_clear(&apool->pool); + + /* destroy the last block */ + free(apool->block); + free(apool); +} + +static void pool_alloconly_ref(Pool pool) +{ + AlloconlyPool *apool = (AlloconlyPool *) pool; + + apool->refcount++; +} + +static void pool_alloconly_unref(Pool pool) +{ + AlloconlyPool *apool = (AlloconlyPool *) pool; + + if (--apool->refcount == 0) + pool_alloconly_destroy(apool); +} + +static void block_alloc(AlloconlyPool *apool, unsigned int size) +{ + PoolBlock *block; + + /* each block is at least twice the size of the previous one */ + if (apool->block != NULL) + size += apool->block->size; + + if (size <= sizeof(PoolBlock)) + size += sizeof(PoolBlock); + size = nearest_power(size); + + block = calloc(size, 1); + block->prev = apool->block; + apool->block = block; + + block->size = size - SIZEOF_POOLBLOCK; + block->left = block->size; +} + +static void *pool_alloconly_malloc(Pool pool, unsigned int size) +{ + AlloconlyPool *apool = (AlloconlyPool *) pool; + PoolAlloc *alloc; + + size = MEM_ALIGN(size); + + if (apool->block->left < size + SIZEOF_POOLALLOC) { + /* we need a new block */ + block_alloc(apool, size); + } + + alloc = (PoolAlloc *) (apool->block->data + + apool->block->size - apool->block->left); + alloc->size = size; + + apool->block->left -= size + SIZEOF_POOLALLOC; + apool->last_alloc_size = size; + return alloc->data; +} + +static void pool_alloconly_free(Pool pool __attr_unused__, + void *mem __attr_unused__) +{ + /* ignore */ +} + +static void *pool_alloconly_realloc(Pool pool, void *mem, unsigned int size) +{ + /* there's no point in shrinking the memory usage, + so just do the same as realloc_min() */ + return pool_alloconly_realloc_min(pool, mem, size); +} + +static int pool_try_grow(AlloconlyPool *apool, void *mem, unsigned int size) +{ + /* see if we want to grow the memory we allocated last */ + if (apool->block->data + (apool->block->size - + apool->block->left - + apool->last_alloc_size) == mem) { + /* yeah, see if we can grow */ + if (apool->block->left >= size-apool->last_alloc_size) { + /* just shrink the available size */ + apool->block->left -= size - apool->last_alloc_size; + apool->last_alloc_size = size; + return TRUE; + } + } + + return FALSE; +} + +static void *pool_alloconly_realloc_min(Pool pool, void *mem, unsigned int size) +{ + AlloconlyPool *apool = (AlloconlyPool *) pool; + PoolAlloc *alloc; + unsigned char *new_mem; + unsigned int old_size; + + if (mem == NULL) { + alloc = NULL; + old_size = 0; + } else { + /* get old size */ + alloc = (PoolAlloc *) ((char *) mem - SIZEOF_POOLALLOC); + old_size = alloc->size; + } + + if (old_size >= size) + return mem; + + size = MEM_ALIGN(size); + + /* see if we can directly grow it */ + if (pool_try_grow(apool, mem, size)) + return mem; + + /* slow way - allocate + copy */ + new_mem = pool_alloconly_malloc(pool, size); + if (size > old_size) { + /* clear new data */ + memset(new_mem + old_size, 0, size - old_size); + } + + return new_mem; +} + +static void pool_alloconly_clear(Pool pool) +{ + AlloconlyPool *apool = (AlloconlyPool *) pool; + PoolBlock *block; + + /* destroy all blocks but the last, which is the largest */ + while (apool->block->prev != NULL) { + block = apool->block; + apool->block = block->prev; + + free(block); + } + + /* clear the last block */ + memset(apool->block->data, 0, apool->block->size - apool->block->left); + apool->block->left = apool->block->size; + + apool->last_alloc_size = 0; +} + +static struct Pool static_alloconly_pool = { + pool_alloconly_ref, + pool_alloconly_unref, + + pool_alloconly_malloc, + pool_alloconly_free, + + pool_alloconly_realloc, + pool_alloconly_realloc_min, + + pool_alloconly_clear +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mempool-system.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,137 @@ +/* + mempool-system.c : Memory pool wrapper for malloc() + realloc() + free() + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "mempool.h" + +#include <stdlib.h> + +#define MAX_ALLOC_SIZE (UINT_MAX - sizeof(unsigned int)) + +typedef struct { + unsigned int size; + /* void data[]; */ +} PoolAlloc; + +static struct Pool static_system_pool; + +Pool system_pool = &static_system_pool; + +static void pool_system_ref(Pool pool __attr_unused__) +{ +} + +static void pool_system_unref(Pool pool __attr_unused__) +{ +} + +static void *pool_system_malloc(Pool pool __attr_unused__, unsigned int size) +{ + PoolAlloc *alloc; + + if (size > MAX_ALLOC_SIZE) + i_panic("Trying to allocate too much memory"); + + alloc = calloc(sizeof(PoolAlloc) + size, 1); + if (alloc == NULL) + i_panic("pool_system_malloc(): Out of memory"); + alloc->size = size; + + return (char *) alloc + sizeof(PoolAlloc); +} + +static void pool_system_free(Pool pool __attr_unused__, void *mem) +{ + if (mem != NULL) + free((char *) mem - sizeof(PoolAlloc)); +} + +static void *pool_system_realloc(Pool pool __attr_unused__, void *mem, + unsigned int size) +{ + PoolAlloc *alloc; + unsigned int old_size; + char *rmem; + + if (mem == NULL) { + alloc = NULL; + old_size = 0; + } else { + /* get old size */ + alloc = (PoolAlloc *) ((char *) mem - sizeof(PoolAlloc)); + old_size = alloc->size; + } + + /* alloc & set new size */ + alloc = realloc(alloc, sizeof(PoolAlloc) + size); + if (alloc == NULL) + i_panic("pool_system_realloc(): Out of memory"); + alloc->size = size; + + rmem = (char *) alloc + sizeof(PoolAlloc); + if (size > old_size) { + /* clear new data */ + memset(rmem + old_size, 0, size-old_size); + } + + return rmem; +} + +static void *pool_system_realloc_min(Pool pool, void *mem, unsigned int size) +{ + PoolAlloc *alloc; + unsigned int old_size; + + if (mem == NULL) + old_size = 0; + else { + /* get old size */ + alloc = (PoolAlloc *) ((char *) mem - sizeof(PoolAlloc)); + old_size = alloc->size; + } + + if (old_size >= size) + return mem; + else + return pool_system_realloc(pool, mem, size); +} + +static void pool_system_clear(Pool pool __attr_unused__) +{ + i_panic("pool_system_clear() must not be called"); +} + +static struct Pool static_system_pool = { + pool_system_ref, + pool_system_unref, + + pool_system_malloc, + pool_system_free, + + pool_system_realloc, + pool_system_realloc_min, + + pool_system_clear +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mempool.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,37 @@ +/* + mempool.c : Memory pool initialization + + Copyright (c) 2001-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "mempool.h" + +Pool pool_alloconly_create(const char *name, unsigned int size); + +Pool pool_create(const char *name, unsigned int size, int allocfree) +{ + if (allocfree) + return system_pool; + + return pool_alloconly_create(name, size); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mempool.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,66 @@ +#ifndef __MEMPOOL_H +#define __MEMPOOL_H + +#include "macros.h" + +/* #define POOL_CHECK_LEAKS */ + +/* Memory allocated and reallocated (the new data in it) in pools is always + zeroed, it will cost only a few CPU cycles and may well save some debug + time. */ + +typedef struct Pool *Pool; + +struct Pool { + void (*ref)(Pool pool); + void (*unref)(Pool pool); + + void *(*malloc)(Pool pool, unsigned int size); + void (*free)(Pool pool, void *mem); + + /* reallocate the `mem' to be exactly `size' */ + void *(*realloc)(Pool pool, void *mem, unsigned int size); + /* reallocate the `mem' to be at least `size' if it wasn't previously */ + void *(*realloc_min)(Pool pool, void *mem, unsigned int size); + + /* Frees all the memory in pool. NOTE: system_pool doesn't support + this and crashes if it's used */ + void (*clear)(Pool pool); +}; + +/* system_pool uses calloc() + realloc() + free() */ +extern Pool system_pool; + +/* If allocfree is FALSE, p_free() has no effect. Note that `size' specifies + the initial malloc()ed block size, part of it is used internally. */ +Pool pool_create(const char *name, unsigned int size, int allocfree); + +/* Pools should be used through these macros: */ +#define pool_ref(pool) (pool)->ref(pool) +#define pool_unref(pool) (pool)->unref(pool) + +#define p_malloc(pool, size) (pool)->malloc(pool, size) +#define p_realloc(pool, mem, size) (pool)->realloc(pool, mem, size) +#define p_realloc_min(pool, mem, size) (pool)->realloc_min(pool, mem, size) +#define p_free(pool, mem) (pool)->free(pool, mem) + +#define p_clear(pool) (pool)->clear(pool) + +/* Extra macros to make life easier: */ +#define p_new(pool, type, count) \ + ((type *) p_malloc(pool, (unsigned) sizeof(type) * (count))) +#define p_free_and_null(pool, rec) \ + STMT_START { \ + p_free(pool, rec); \ + (rec) = NULL; \ + } STMT_END + +/* p_free_clean() should be used when pool is being destroyed, so freeing + memory isn't needed for anything else than detecting memory leaks. */ +#ifdef POOL_CHECK_LEAKS +# define p_free_clean(pool, mem) p_free(pool, mem) +#else +# define p_free_clean(pool, mem) +#endif + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mmap-util.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,88 @@ +/* + mmap-util.c - Memory mapping utilities + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "mmap-util.h" + +static void *mmap_file(int fd, size_t *length, int access) +{ + *length = lseek(fd, 0, SEEK_END); + if ((off_t)*length == (off_t)-1) + return MAP_FAILED; + + if (*length == 0) + return NULL; + + i_assert(*length > 0 && *length < INT_MAX); + + return mmap(NULL, *length, access, MAP_SHARED, fd, 0); +} + +void *mmap_ro_file(int fd, size_t *length) +{ + return mmap_file(fd, length, PROT_READ); +} + +void *mmap_rw_file(int fd, size_t *length) +{ + return mmap_file(fd, length, PROT_READ | PROT_WRITE); +} + +void *mmap_aligned(int fd, int access, off_t offset, size_t length, + void **data_start, size_t *mmap_length) +{ + void *mmap_base; + +#ifdef HAVE_GETPAGESIZE + static int pagemask = 0; + + if (pagemask == 0) { + pagemask = getpagesize(); + i_assert(pagemask > 0); + pagemask--; + } + + *mmap_length = length + (offset & pagemask); + + mmap_base = mmap(NULL, *mmap_length, access, MAP_SHARED, + fd, offset & ~pagemask); + *data_start = mmap_base == MAP_FAILED || mmap_base == NULL ? NULL : + (char *) mmap_base + (offset & pagemask); +#else + *mmap_length = length + offset; + + mmap_base = mmap(NULL, *mmap_length, access, MAP_SHARED, fd, 0); + *data_start = mmap_base == MAP_FAILED || mmap_base == NULL ? NULL : + (char *) mmap_base + offset; +#endif + + return mmap_base; +} + +#ifndef HAVE_MADVISE +int madvise(void *start, size_t length, int advice) +{ +} +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/mmap-util.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,24 @@ +#ifndef __MMAP_UTIL_H +#define __MMAP_UTIL_H + +#include <unistd.h> +#include <sys/mman.h> + +#ifndef HAVE_MADVISE +int madvise(void *start, size_t length, int advice); +# ifndef MADV_NORMAL +# define MADV_NORMAL 0 +# define MADV_RANDOM 0 +# define MADV_SEQUENTIAL 0 +# define MADV_WILLNEED 0 +# define MADV_DONTNEED 0 +# endif +#endif + +void *mmap_ro_file(int fd, size_t *length); +void *mmap_rw_file(int fd, size_t *length); + +void *mmap_aligned(int fd, int access, off_t offset, size_t length, + void **data_start, size_t *mmap_length); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/network.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,596 @@ +/* + network.c : Network stuff with IPv6 support + + Copyright (c) 1999-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "network.h" + +#include <unistd.h> +#include <fcntl.h> +#include <ctype.h> +#include <sys/un.h> +#include <netinet/tcp.h> + +#define LISTEN_BACKLOG 8 + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; +#endif +}; + +#ifdef HAVE_IPV6 +# define SIZEOF_SOCKADDR(so) ((so).sa.sa_family == AF_INET6 ? \ + sizeof(so.sin6) : sizeof(so.sin)) +#else +# define SIZEOF_SOCKADDR(so) (sizeof(so.sin)) +#endif + +int net_ip_compare(IPADDR *ip1, IPADDR *ip2) +{ + if (ip1->family != ip2->family) + return 0; + +#ifdef HAVE_IPV6 + if (ip1->family == AF_INET6) + return memcmp(&ip1->ip, &ip2->ip, sizeof(ip1->ip)) == 0; +#endif + + return memcmp(&ip1->ip, &ip2->ip, 4) == 0; +} + + +/* copy IP to sockaddr */ +static inline void sin_set_ip(union sockaddr_union *so, const IPADDR *ip) +{ + if (ip == NULL) { +#ifdef HAVE_IPV6 + so->sin6.sin6_family = AF_INET6; + so->sin6.sin6_addr = in6addr_any; +#else + so->sin.sin_family = AF_INET; + so->sin.sin_addr.s_addr = INADDR_ANY; +#endif + return; + } + + so->sin.sin_family = ip->family; +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&so->sin6.sin6_addr, &ip->ip, sizeof(ip->ip)); + else +#endif + memcpy(&so->sin.sin_addr, &ip->ip, 4); +} + +static inline void sin_get_ip(const union sockaddr_union *so, IPADDR *ip) +{ + ip->family = so->sin.sin_family; + +#ifdef HAVE_IPV6 + if (ip->family == AF_INET6) + memcpy(&ip->ip, &so->sin6.sin6_addr, sizeof(ip->ip)); + else +#endif + memcpy(&ip->ip, &so->sin.sin_addr, 4); +} + +static inline void sin_set_port(union sockaddr_union *so, int port) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + so->sin6.sin6_port = htons((unsigned short) port); + else +#endif + so->sin.sin_port = htons((unsigned short) port); +} + +static inline int sin_get_port(union sockaddr_union *so) +{ +#ifdef HAVE_IPV6 + if (so->sin.sin_family == AF_INET6) + return ntohs(so->sin6.sin6_port); +#endif + return ntohs(so->sin.sin_port); +} + +static inline void close_save_errno(int fd) +{ + int old_errno = errno; + close(fd); + errno = old_errno; +} + +/* Connect to socket with ip address */ +int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip) +{ + union sockaddr_union so; + int fd, ret, opt = 1; + + if (my_ip != NULL && ip->family != my_ip->family) { + i_warning("net_connect_ip(): ip->family != my_ip->family"); + my_ip = NULL; + } + + /* create the socket */ + memset(&so, 0, sizeof(so)); + so.sin.sin_family = ip->family; + fd = socket(ip->family, SOCK_STREAM, 0); + + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + + /* set our own address */ + if (my_ip != NULL) { + sin_set_ip(&so, my_ip); + if (bind(fd, &so.sa, SIZEOF_SOCKADDR(so)) == -1) { + /* failed, set it back to INADDR_ANY */ + sin_set_ip(&so, NULL); + bind(fd, &so.sa, SIZEOF_SOCKADDR(so)); + } + } + + /* connect */ + sin_set_ip(&so, ip); + sin_set_port(&so, port); + ret = connect(fd, &so.sa, SIZEOF_SOCKADDR(so)); + +#ifndef WIN32 + if (ret < 0 && errno != EINPROGRESS) +#else + if (ret < 0 && WSAGetLastError() != WSAEWOULDBLOCK) +#endif + { + close_save_errno(fd); + return -1; + } + + return fd; +} + +int net_connect_unix(const char *path) +{ + struct sockaddr_un sa; + int fd, ret; + + if (strlen(path) > sizeof(sa.sun_path)-1) { + /* too long path */ + errno = EINVAL; + return -1; + } + + /* create the socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + + /* connect */ + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, path); + + ret = connect(fd, (struct sockaddr *) &sa, sizeof(sa)); + if (ret < 0 && errno != EINPROGRESS) { + close_save_errno(fd); + return -1; + } + + return fd; +} + +/* Disconnect socket */ +void net_disconnect(int fd) +{ + close(fd); +} + +/* Set socket blocking/nonblocking */ +void net_set_nonblock(int fd, int nonblock) +{ +#ifdef HAVE_FCNTL + if (fcntl(fd, F_SETFL, nonblock ? O_NONBLOCK : 0) < 0) + i_fatal("net_send_nonblock() failed: %m"); +#endif +} + +void net_set_cork(int fd, int cork) +{ +#ifdef TCP_CORK + setsockopt(fd, SOL_TCP, TCP_CORK, &cork, sizeof(cork)); +#endif +} + +/* Listen for connections on a socket. if `my_ip' is NULL, listen in any + address. */ +int net_listen(IPADDR *my_ip, int *port) +{ + union sockaddr_union so; + int ret, fd, opt = 1; + socklen_t len; + + i_assert(port != NULL); + + memset(&so, 0, sizeof(so)); + sin_set_port(&so, *port); + sin_set_ip(&so, my_ip); + + /* create the socket */ + fd = socket(so.sin.sin_family, SOCK_STREAM, 0); +#ifdef HAVE_IPV6 + if (fd == -1 && (errno == EINVAL || errno == EAFNOSUPPORT)) { + /* IPv6 is not supported by OS */ + so.sin.sin_family = AF_INET; + so.sin.sin_addr.s_addr = INADDR_ANY; + + fd = socket(AF_INET, SOCK_STREAM, 0); + } +#endif + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)); + + /* specify the address/port we want to listen in */ + ret = bind(fd, &so.sa, SIZEOF_SOCKADDR(so)); + if (ret >= 0) { + /* get the actual port we started listen */ + len = SIZEOF_SOCKADDR(so); + ret = getsockname(fd, &so.sa, &len); + if (ret >= 0) { + *port = sin_get_port(&so); + + /* start listening */ + if (listen(fd, LISTEN_BACKLOG) >= 0) + return fd; + } + + } + + /* error */ + close_save_errno(fd); + return -1; +} + +int net_listen_unix(const char *path) +{ + struct sockaddr_un sa; + int fd; + + if (strlen(path) > sizeof(sa.sun_path)-1) { + /* too long path */ + errno = EINVAL; + return -1; + } + + /* create the socket */ + fd = socket(PF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return -1; + + /* set socket options */ + net_set_nonblock(fd, TRUE); + + /* bind */ + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strcpy(sa.sun_path, path); + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) == 0) { + /* start listening */ + if (listen(fd, LISTEN_BACKLOG) == 0) + return fd; + } + + close_save_errno(fd); + return -1; +} + +/* Accept a connection on a socket */ +int net_accept(int fd, IPADDR *addr, int *port) +{ + union sockaddr_union so; + int ret; + socklen_t addrlen; + + i_assert(fd >= 0); + + addrlen = sizeof(so); + ret = accept(fd, &so.sa, &addrlen); + + if (ret < 0) + return -1; + + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + + net_set_nonblock(fd, TRUE); + return ret; +} + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(int fd, void *buf, unsigned int len) +{ + int ret; + + i_assert(fd >= 0); + i_assert(buf != NULL); + i_assert(len <= INT_MAX); + + ret = recv(fd, buf, len, 0); + if (ret == 0) + return -1; /* disconnected */ + + if (ret < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + return ret; +} + +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(int fd, const void *data, unsigned int len) +{ + int ret; + + i_assert(fd >= 0); + i_assert(data != NULL); + i_assert(len <= INT_MAX); + + ret = send(fd, (void *) data, len, 0); + if (ret == -1 && (errno == EINTR || errno == EPIPE || errno == EAGAIN)) + return 0; + + return ret; +} + +/* Get IP addresses for host. ips contains ips_count of IPs, they don't need + to be free'd. Returns 0 = ok, others = error code for net_gethosterror() */ +int net_gethostbyname(const char *addr, IPADDR **ips, int *ips_count) +{ +#ifdef HAVE_IPV6 + union sockaddr_union *so; + struct addrinfo hints, *ai, *origai; + char hbuf[NI_MAXHOST]; + int host_error; +#else + struct hostent *hp; +#endif + int count; + + i_assert(addr != NULL); + i_assert(ips != NULL); + i_assert(ips_count != NULL); + + *ips = NULL; + *ips_count = 0; + +#ifdef HAVE_IPV6 + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_socktype = SOCK_STREAM; + + /* save error to host_error for later use */ + host_error = getaddrinfo(addr, NULL, &hints, &ai); + if (host_error != 0) + return host_error; + + if (getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, + sizeof(hbuf), NULL, 0, NI_NUMERICHOST) != 0) + return 1; + + + /* get number of IPs */ + origai = ai; + for (count = 0; ai != NULL; ai = ai->ai_next) + count++; + + *ips_count = count; + *ips = t_malloc(sizeof(IPADDR) * count); + + count = 0; + for (ai = origai; ai != NULL; ai = ai->ai_next, count++) { + so = (union sockaddr_union *) ai->ai_addr; + + sin_get_ip(so, ips[count]); + } + freeaddrinfo(origai); +#else + hp = gethostbyname(addr); + if (hp == NULL) + return h_errno; + + /* get number of IPs */ + count = 0; + while (hp->h_addr_list[count] != NULL) + count++; + + *ips_count = count; + *ips = t_malloc(sizeof(IPADDR) * count); + + while (count > 0) { + count--; + + (*ips)[count].family = AF_INET; + memcpy(&(*ips)[count].ip, hp->h_addr_list[count], 4); + } +#endif + + return 0; +} + +/* Get socket address/port */ +int net_getsockname(int fd, IPADDR *addr, int *port) +{ + union sockaddr_union so; + socklen_t addrlen; + + i_assert(fd >= 0); + + addrlen = sizeof(so); + if (getsockname(fd, (struct sockaddr *) &so, &addrlen) == -1) + return -1; + + if (addr != NULL) sin_get_ip(&so, addr); + if (port != NULL) *port = sin_get_port(&so); + + return 0; +} + +int net_ip2host(IPADDR *ip, char *host) +{ +#ifdef HAVE_IPV6 + if (!inet_ntop(ip->family, &ip->ip, host, MAX_IP_LEN)) + return -1; +#else + unsigned long ip4; + + if (ip->family != AF_INET) { + strcpy(host, "0.0.0.0"); + return -1; + } + + ip4 = ntohl(ip->ip.s_addr); + i_snprintf(host, MAX_IP_LEN, "%lu.%lu.%lu.%lu", + (ip4 & 0xff000000UL) >> 24, + (ip4 & 0x00ff0000) >> 16, + (ip4 & 0x0000ff00) >> 8, + (ip4 & 0x000000ff)); +#endif + return 0; +} + +int net_host2ip(const char *host, IPADDR *ip) +{ + if (strchr(host, ':') != NULL) { + /* IPv6 */ + ip->family = AF_INET6; +#ifdef HAVE_IPV6 + if (inet_pton(AF_INET6, host, &ip->ip) == 0) + return -1; +#else + ip->ip.s_addr = 0; +#endif + } else { + /* IPv4 */ + ip->family = AF_INET; + if (inet_aton(host, (struct in_addr *) &ip->ip) == 0) + return -1; + } + + return 0; +} + +/* Get socket error */ +int net_geterror(int fd) +{ + int data; + socklen_t len = sizeof(data); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &data, &len) == -1) + return -1; + + return data; +} + +/* get error of net_gethostname() */ +const char *net_gethosterror(int error) +{ +#ifdef HAVE_IPV6 + i_assert(error != 0); + + if (error == 1) { + /* getnameinfo() failed */ + return strerror(errno); + } + + return gai_strerror(error); +#else + switch (error) { + case HOST_NOT_FOUND: + return "Host not found"; + case NO_ADDRESS: + return "No IP address found for name"; + case NO_RECOVERY: + return "A non-recovable name server error occurred"; + case TRY_AGAIN: + return "A temporary error on an authoritative name server"; + } + + /* unknown error */ + return NULL; +#endif +} + +/* return TRUE if host lookup failed because it didn't exist (ie. not + some error with name server) */ +int net_hosterror_notfound(int error) +{ +#ifdef HAVE_IPV6 + return error != 1 && (error == EAI_NONAME || error == EAI_NODATA); +#else + return error == HOST_NOT_FOUND || error == NO_ADDRESS; +#endif +} + +/* Get name of TCP service */ +char *net_getservbyport(int port) +{ + struct servent *entry; + + entry = getservbyport(htons((unsigned short) port), "tcp"); + return entry == NULL ? NULL : entry->s_name; +} + +int is_ipv4_address(const char *host) +{ + while (*host != '\0') { + if (*host != '.' && !i_isdigit(*host)) + return 0; + host++; + } + + return 1; +} + +int is_ipv6_address(const char *host) +{ + while (*host != '\0') { + if (*host != ':' && !i_isxdigit(*host)) + return 0; + host++; + } + + return 1; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/network.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,98 @@ +#ifndef __NETWORK_H +#define __NETWORK_H + +#ifndef WIN32 +# include <sys/socket.h> +# include <netinet/in.h> +# include <netdb.h> +# include <arpa/inet.h> +#endif + +#ifdef HAVE_SOCKS_H +#include <socks.h> +#endif + +#ifndef AF_INET6 +# ifdef PF_INET6 +# define AF_INET6 PF_INET6 +# else +# define AF_INET6 10 +# endif +#endif + +struct _IPADDR { + unsigned short family; +#ifdef HAVE_IPV6 + struct in6_addr ip; +#else + struct in_addr ip; +#endif +}; + +/* maxmimum string length of IP address */ +#ifdef HAVE_IPV6 +# define MAX_IP_LEN INET6_ADDRSTRLEN +#else +# define MAX_IP_LEN 20 +#endif + +#define IPADDR_IS_V4(ip) ((ip)->family == AF_INET) +#define IPADDR_IS_V6(ip) ((ip)->family == AF_INET6) + +/* returns 1 if IPADDRs are the same */ +int net_ip_compare(IPADDR *ip1, IPADDR *ip2); + +/* Connect to socket with ip address */ +int net_connect_ip(IPADDR *ip, int port, IPADDR *my_ip); +/* Connect to named UNIX socket */ +int net_connect_unix(const char *path); +/* Disconnect socket */ +void net_disconnect(int fd); +/* Try to let the other side close the connection, if it still isn't + disconnected after certain amount of time, close it ourself */ +void net_disconnect_later(int fd); + +/* Set socket blocking/nonblocking */ +void net_set_nonblock(int fd, int nonblock); +/* Set TCP_CORK if supported, ie. don't send out partial frames. */ +void net_set_cork(int fd, int cork); + +/* Listen for connections on a socket */ +int net_listen(IPADDR *my_ip, int *port); +/* Listen for connections on an UNIX socket */ +int net_listen_unix(const char *path); +/* Accept a connection on a socket */ +int net_accept(int fd, IPADDR *addr, int *port); + +/* Read data from socket, return number of bytes read, -1 = error */ +int net_receive(int fd, void *buf, unsigned int len); +/* Transmit data, return number of bytes sent, -1 = error */ +int net_transmit(int fd, const void *data, unsigned int len); + +/* Get IP addresses for host. ips contains ips_count of IPs, they don't need + to be free'd. Returns 0 = ok, others = error code for net_gethosterror() */ +int net_gethostbyname(const char *addr, IPADDR **ips, int *ips_count); +/* get error of net_gethostname() */ +const char *net_gethosterror(int error); +/* return TRUE if host lookup failed because it didn't exist (ie. not + some error with name server) */ +int net_hosterror_notfound(int error); + +/* Get socket address/port */ +int net_getsockname(int fd, IPADDR *addr, int *port); + +/* IPADDR -> char* translation. `host' must be at least MAX_IP_LEN bytes */ +int net_ip2host(IPADDR *ip, char *host); +/* char* -> IPADDR translation. */ +int net_host2ip(const char *host, IPADDR *ip); + +/* Get socket error */ +int net_geterror(int fd); + +/* Get name of TCP service */ +char *net_getservbyport(int port); + +int is_ipv4_address(const char *host); +int is_ipv6_address(const char *host); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/primes.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,79 @@ +/* GLIB - Library of useful routines for C programming + * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/* + * Modified by the GLib Team and others 1997-1999. See the AUTHORS + * file for a list of people on the GLib Team. See the ChangeLog + * files for a list of changes. These files are distributed with + * GLib at ftp://ftp.gtk.org/pub/gtk/. + */ + +#include "lib.h" +#include "primes.h" + +static const unsigned int primes[] = +{ + 11, + 19, + 37, + 73, + 109, + 163, + 251, + 367, + 557, + 823, + 1237, + 1861, + 2777, + 4177, + 6247, + 9371, + 14057, + 21089, + 31627, + 47431, + 71143, + 106721, + 160073, + 240101, + 360163, + 540217, + 810343, + 1215497, + 1823231, + 2734867, + 4102283, + 6153409, + 9230113, + 13845163 +}; + +static const unsigned int primes_count = sizeof(primes) / sizeof(primes[0]); + +unsigned int primes_closest(unsigned int num) +{ + unsigned int i; + + for (i = 0; i < primes_count; i++) + if (primes[i] > num) + return primes[i]; + + return primes[primes_count - 1]; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/primes.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,6 @@ +#ifndef __PRIMES_H +#define __PRIMES_H + +unsigned int primes_closest(unsigned int num); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/randgen.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,76 @@ +/* + randgen.c : Random generator + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "randgen.h" + +#include <unistd.h> +#include <fcntl.h> + +static int init_refcount = 0; +static int urandom_fd; + +void random_fill(const void *buf, unsigned int size) +{ + unsigned int pos; + int ret; + + i_assert(init_refcount > 0); + i_assert(size < INT_MAX); + + for (pos = 0; pos < size; pos += ret) { + ret = read(urandom_fd, (char *) buf + pos, size - pos); + if (ret < 0) + i_fatal("Error reading from /dev/urandom: %m"); + } +} + +void random_init(void) +{ + if (init_refcount++ > 0) + return; + + urandom_fd = open("/dev/urandom", O_RDONLY); + if (urandom_fd == -1) { + if (errno == ENOENT) { + i_fatal("/dev/urandom doesn't exist, currently we " + "require it"); + } else { + i_fatal("Can't open /dev/urandom: %m"); + } + } + + if (fcntl(urandom_fd, FD_CLOEXEC, 1L) < 0) + i_fatal("Error setting close-on-exec flag to /dev/urandom: %m"); +} + +void random_deinit(void) +{ + if (--init_refcount > 0) + return; + + (void)close(urandom_fd); + urandom_fd = -1; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/randgen.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,10 @@ +#ifndef __RANDGEN_H +#define __RANDGEN_H + +void random_fill(const void *buf, unsigned int size); + +/* may be called multiple times */ +void random_init(void); +void random_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/restrict-access.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,94 @@ +/* + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "restrict-access.h" + +#include <stdlib.h> +#include <unistd.h> +#include <grp.h> + +void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, + const char *chroot_dir) +{ + if (user != NULL && *user != '\0') + putenv((char *) t_strconcat("USER=", user, NULL)); + if (chroot_dir != NULL && *chroot_dir != '\0') + putenv((char *) t_strconcat("CHROOT=", chroot_dir, NULL)); + + putenv((char *) t_strdup_printf("SETUID=%ld", (long) uid)); + putenv((char *) t_strdup_printf("SETGID=%ld", (long) gid)); +} + +void restrict_access_by_env(void) +{ + const char *env; + gid_t gid; + uid_t uid; + + /* chrooting */ + env = getenv("CHROOT"); + if (env != NULL) { + if (chroot(env) != 0) + i_fatal("chroot(%s) failed: %m", env); + + if (chdir("/") != 0) + i_fatal("chdir(/) failed: %m"); + } + + /* groups - the getgid() checks are just so we don't fail if we're + not running as root and try to just use our own GID. */ + env = getenv("SETGID"); + gid = env == NULL ? 0 : (gid_t) atol(env); + if (gid != 0 && (gid != getgid() || gid != getegid())) { + if (setgid(gid) != 0) + i_fatal("setgid(%ld) failed: %m", (long) gid); + + env = getenv("USER"); + if (env == NULL) { + /* user not known, use only this one group */ + (void)setgroups(1, &gid); + } else { + if (initgroups(env, gid) != 0) { + i_fatal("initgroups(%s, %ld) failed: %m", + env, (long) gid); + } + } + } + + /* uid last */ + env = getenv("SETUID"); + uid = env == NULL ? 0 : (uid_t) atol(env); + if (uid != 0) { + if (setuid(uid) != 0) + i_fatal("setuid(%ld) failed: %m", (long) uid); + + /* just extra verification */ +#ifdef HAVE_SETREUID + if (setreuid((uid_t)-1, 0) == 0) +#else + if (setuid(0) == 0) +#endif + i_fatal("We couldn't drop root privileges"); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/restrict-access.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,12 @@ +#ifndef __RESTRICT_ACCESS_H +#define __RESTRICT_ACCESS_H + +/* set environment variables so they can be read with + restrict_access_by_env() */ +void restrict_access_set_env(const char *user, uid_t uid, gid_t gid, + const char *chroot_dir); + +/* chroot, setuid() and setgid() based on environment variables */ +void restrict_access_by_env(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/strfuncs.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,884 @@ +/* + strfuncs.c : String manipulation functions (note: LGPL, because the ) + + Copyright (C) 2001-2002 Timo Sirainen + + printf_string_upper_bound() code is taken from GLIB: + Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald + Modified by the GLib Team and others 1997-1999. + + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the + Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. +*/ + +#include "lib.h" +#include "strfuncs.h" + +#include <stdio.h> +#include <limits.h> +#include <ctype.h> + +#define STRCONCAT_BUFSIZE 512 + +typedef void *(*ALLOC_FUNC)(Pool, unsigned int); + +static void *tp_malloc(Pool pool __attr_unused__, unsigned int size) +{ + return t_malloc(size); +} + +typedef union _GDoubleIEEE754 GDoubleIEEE754; +#define G_IEEE754_DOUBLE_BIAS (1023) +/* multiply with base2 exponent to get base10 exponent (nomal numbers) */ +#define G_LOG_2_BASE_10 (0.30102999566398119521) +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +union _GDoubleIEEE754 +{ + double v_double; + struct { + unsigned int mantissa_low : 32; + unsigned int mantissa_high : 20; + unsigned int biased_exponent : 11; + unsigned int sign : 1; + } mpn; +}; +#elif G_BYTE_ORDER == G_BIG_ENDIAN +union _GDoubleIEEE754 +{ + double v_double; + struct { + unsigned int sign : 1; + unsigned int biased_exponent : 11; + unsigned int mantissa_high : 20; + unsigned int mantissa_low : 32; + } mpn; +}; +#else /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ +#error unknown ENDIAN type +#endif /* !G_LITTLE_ENDIAN && !G_BIG_ENDIAN */ + +typedef struct +{ + unsigned int min_width; + unsigned int precision; + int alternate_format, zero_padding, adjust_left, locale_grouping; + int add_space, add_sign, possible_sign, seen_precision; + int mod_half, mod_long, mod_extra_long; +} PrintfArgSpec; + +#if (SIZEOF_LONG > 4) || (SIZEOF_VOID_P > 4) +# define HONOUR_LONGS 1 +#else +# define HONOUR_LONGS 0 +#endif + +unsigned int printf_string_upper_bound(const char *format, va_list args) +{ + int len = 1; + + if (!format) + return len; + + while (*format) + { + register char c = *format++; + + if (c != '%') + len += 1; + else /* (c == '%') */ + { + PrintfArgSpec spec; + int seen_l = FALSE, conv_done = FALSE; + unsigned int conv_len = 0; + const char *spec_start = format; + + memset(&spec, 0, sizeof(spec)); + do + { + c = *format++; + switch (c) + { + GDoubleIEEE754 u_double; + unsigned int v_uint; + int v_int; + const char *v_string; + + /* beware of positional parameters + */ + case '$': + i_warning (GNUC_PRETTY_FUNCTION + "(): unable to handle positional parameters (%%n$)"); + len += 1024; /* try adding some safety padding */ + break; + + /* parse flags + */ + case '#': + spec.alternate_format = TRUE; + break; + case '0': + spec.zero_padding = TRUE; + break; + case '-': + spec.adjust_left = TRUE; + break; + case ' ': + spec.add_space = TRUE; + break; + case '+': + spec.add_sign = TRUE; + break; + case '\'': + spec.locale_grouping = TRUE; + break; + + /* parse output size specifications + */ + case '.': + spec.seen_precision = TRUE; + break; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + v_uint = c - '0'; + c = *format; + while (c >= '0' && c <= '9') + { + format++; + v_uint = v_uint * 10 + c - '0'; + c = *format; + } + if (spec.seen_precision) + spec.precision = I_MAX (spec.precision, v_uint); + else + spec.min_width = I_MAX (spec.min_width, v_uint); + break; + case '*': + v_int = va_arg (args, int); + if (spec.seen_precision) + { + /* forget about negative precision */ + if (v_int >= 0) + spec.precision = I_MAX ((int)spec.precision, v_int); + } + else + { + if (v_int < 0) + { + v_int = - v_int; + spec.adjust_left = TRUE; + } + spec.min_width = I_MAX ((int)spec.min_width, v_int); + } + break; + + /* parse type modifiers + */ + case 'h': + spec.mod_half = TRUE; + break; + case 'l': + if (!seen_l) + { + spec.mod_long = TRUE; + seen_l = TRUE; + break; + } + /* else, fall through */ + case 'L': + case 'q': + spec.mod_long = TRUE; + spec.mod_extra_long = TRUE; + break; + case 'z': + case 'Z': +#if GLIB_SIZEOF_SIZE_T > 4 + spec.mod_long = TRUE; + spec.mod_extra_long = TRUE; +#endif /* GLIB_SIZEOF_SIZE_T > 4 */ + break; + case 't': +#if GLIB_SIZEOF_PTRDIFF_T > 4 + spec.mod_long = TRUE; + spec.mod_extra_long = TRUE; +#endif /* GLIB_SIZEOF_PTRDIFF_T > 4 */ + break; + case 'j': +#if GLIB_SIZEOF_INTMAX_T > 4 + spec.mod_long = TRUE; + spec.mod_extra_long = TRUE; +#endif /* GLIB_SIZEOF_INTMAX_T > 4 */ + break; + + /* parse output conversions + */ + case '%': + conv_len += 1; + break; + case 'O': + case 'D': + case 'I': + case 'U': + /* some C libraries feature long variants for these as well? */ + spec.mod_long = TRUE; + /* fall through */ + case 'o': + conv_len += 2; + /* fall through */ + case 'd': + case 'i': + conv_len += 1; /* sign */ + /* fall through */ + case 'u': + conv_len += 4; + /* fall through */ + case 'x': + case 'X': + spec.possible_sign = TRUE; + conv_len += 10; + if (spec.mod_long && HONOUR_LONGS) + conv_len *= 2; + if (spec.mod_extra_long) + conv_len *= 2; + if (spec.mod_extra_long) + { +#ifdef G_HAVE_GINT64 + (void) va_arg (args, gint64); +#else + (void) va_arg (args, long); +#endif + } + else if (spec.mod_long) + (void) va_arg (args, long); + else + (void) va_arg (args, int); + break; + case 'A': + case 'a': + /* 0x */ + conv_len += 2; + /* fall through */ + case 'g': + case 'G': + case 'e': + case 'E': + case 'f': + spec.possible_sign = TRUE; + /* n . dddddddddddddddddddddddd E +- eeee */ + conv_len += 1 + 1 + I_MAX (24, spec.precision) + 1 + 1 + 4; + if (spec.mod_extra_long) + i_warning (GNUC_PRETTY_FUNCTION + "(): unable to handle long double, collecting double only"); +#ifdef HAVE_LONG_DOUBLE +#error need to implement special handling for long double +#endif + u_double.v_double = va_arg (args, double); + /* %f can expand up to all significant digits before '.' (308) */ + if (c == 'f' && + u_double.mpn.biased_exponent > 0 && u_double.mpn.biased_exponent < 2047) + { + int exp = u_double.mpn.biased_exponent; + + exp -= G_IEEE754_DOUBLE_BIAS; + exp = exp * G_LOG_2_BASE_10 + 1; + conv_len += exp; + } + /* some printf() implementations require extra padding for rounding */ + conv_len += 2; + /* we can't really handle locale specific grouping here */ + if (spec.locale_grouping) + conv_len *= 2; + break; + case 'C': + spec.mod_long = TRUE; + /* fall through */ + case 'c': + conv_len += spec.mod_long ? MB_LEN_MAX : 1; + (void) va_arg (args, int); + break; + case 'S': + spec.mod_long = TRUE; + /* fall through */ + case 's': + v_string = va_arg (args, char*); + if (!v_string) + conv_len += 8; /* hold "(null)" */ + else if (spec.seen_precision) + conv_len += spec.precision; + else + conv_len += strlen (v_string); + conv_done = TRUE; + if (spec.mod_long) + { + i_warning (GNUC_PRETTY_FUNCTION + "(): unable to handle wide char strings"); + len += 1024; /* try adding some safety padding */ + } + break; + case 'P': /* do we actually need this? */ + /* fall through */ + case 'p': + spec.alternate_format = TRUE; + conv_len += 10; + if (HONOUR_LONGS) + conv_len *= 2; + /* fall through */ + case 'n': + conv_done = TRUE; + (void) va_arg (args, void*); + break; + case 'm': + /* there's not much we can do to be clever */ + v_string = strerror (errno); + v_uint = v_string ? strlen (v_string) : 0; + conv_len += I_MAX (256, v_uint); + break; + + /* handle invalid cases + */ + case '\000': + /* no conversion specification, bad bad */ + conv_len += format - spec_start; + break; + default: + i_warning (GNUC_PRETTY_FUNCTION + "(): unable to handle `%c' while parsing format", + c); + break; + } + conv_done |= conv_len > 0; + } + while (!conv_done); + /* handle width specifications */ + conv_len = I_MAX (conv_len, I_MAX (spec.precision, spec.min_width)); + /* handle flags */ + conv_len += spec.alternate_format ? 2 : 0; + conv_len += (spec.add_space || spec.add_sign || spec.possible_sign); + /* finally done */ + len += conv_len; + } /* else (c == '%') */ + } /* while (*format) */ + + return len; +} + +static const char *fix_format_real(const char *fmt, const char *p) +{ + const char *errstr; + char *buf; + unsigned int pos, alloc, errlen; + + errstr = strerror(errno); + errlen = strlen(errstr); + + pos = (unsigned int) (p-fmt); + i_assert(pos < INT_MAX); + + alloc = pos + errlen + 128; + buf = t_buffer_get(alloc); + + memcpy(buf, fmt, pos); + + while (*p != '\0') { + if (*p == '%' && p[1] == 'm') { + if (pos+errlen+1 > alloc) { + alloc += errlen+1 + 128; + buf = t_buffer_get(alloc); + } + + memcpy(buf+pos, errstr, errlen); + pos += errlen; + p += 2; + } else { + /* p + \0 */ + if (pos+2 > alloc) { + alloc += 128; + buf = t_buffer_get(alloc); + } + + buf[pos++] = *p; + p++; + } + } + + buf[pos++] = '\0'; + t_buffer_alloc(pos); + return buf; +} + +/* replace %m with strerror() */ +static const char *fix_format(const char *fmt) +{ + const char *p; + + for (p = fmt; *p != '\0'; p++) { + if (*p == '%' && p[1] == 'm') + return fix_format_real(fmt, p); + } + + return fmt; +} + +int i_snprintf(char *str, unsigned int max_chars, const char *format, ...) +{ +#ifdef HAVE_VSNPRINTF + va_list args; + int ret; + + i_assert(str != NULL); + i_assert(max_chars < INT_MAX); + i_assert(format != NULL); + + va_start(args, format); + ret = vsnprintf(str, max_chars, fix_format(format), args); + va_end(args); + + if (ret < 0) { + str[max_chars-1] = '\0'; + ret = strlen(str); + } + + return ret; +#else + char *buf; + va_list args; + int len; + + i_assert(str != NULL); + i_assert(max_chars < INT_MAX); + i_assert(format != NULL); + + va_start(args, format); + format = fix_format(format); + buf = t_buffer_get(printf_string_upper_bound(format, args)); + va_end(args); + + len = vsprintf(buf, format, args); + if (len >= (int)max_chars) + len = max_chars-1; + + memcpy(str, buf, len); + str[len] = '\0'; + return len; +#endif +} + +#define STRDUP_CORE(alloc_func, str) STMT_START { \ + void *mem; \ + unsigned int len; \ + \ + for (len = 0; (str)[len] != '\0'; ) \ + len++; \ + len++; \ + mem = alloc_func; \ + memcpy(mem, str, sizeof(str[0])*len); \ + return mem; \ + } STMT_END + +char *p_strdup(Pool pool, const char *str) +{ + if (str == NULL) + return NULL; + + STRDUP_CORE(p_malloc(pool, len), str); +} + +const char *t_strdup(const char *str) +{ + if (str == NULL) + return NULL; + + STRDUP_CORE(t_malloc(len), str); +} + +int *p_intarrdup(Pool pool, const int *arr) +{ + if (arr == NULL) + return NULL; + + STRDUP_CORE(p_malloc(pool, sizeof(int) * len), arr); +} + +const int *t_intarrdup(const int *arr) +{ + if (arr == NULL) + return NULL; + + STRDUP_CORE(t_malloc(sizeof(int) * len), arr); +} + +#define STRDUP_EMPTY_CORE(alloc_func, str) STMT_START { \ + if ((str) == NULL || (str)[0] == '\0') \ + return NULL; \ + \ + STRDUP_CORE(alloc_func, str); \ + } STMT_END + + +char *p_strdup_empty(Pool pool, const char *str) +{ + STRDUP_EMPTY_CORE(p_malloc(pool, len), str); +} + +const char *t_strdup_empty(const char *str) +{ + STRDUP_EMPTY_CORE(t_malloc(len), str); +} + +static inline char * +strndup_core(const char *str, unsigned int max_chars, + ALLOC_FUNC alloc, Pool pool) +{ + char *mem; + unsigned int len; + + i_assert(max_chars < INT_MAX); + + if (str == NULL) + return NULL; + + len = 0; + while (str[len] != '\0' && len < max_chars) + len++; + + mem = alloc(pool, len+1); + memcpy(mem, str, len); + mem[len] = '\0'; + return mem; +} + +char *p_strndup(Pool pool, const char *str, unsigned int max_chars) +{ + return strndup_core(str, max_chars, pool->malloc, pool); +} + +const char *t_strndup(const char *str, unsigned int max_chars) +{ + return strndup_core(str, max_chars, tp_malloc, NULL); +} + +char *p_strdup_printf(Pool pool, const char *format, ...) +{ + va_list args; + char *ret; + + va_start(args, format); + ret = p_strdup_vprintf(pool, format, args); + va_end(args); + + return ret; +} + +const char *t_strdup_printf(const char *format, ...) +{ + va_list args; + const char *ret; + + va_start(args, format); + ret = t_strdup_vprintf(format, args); + va_end(args); + + return ret; +} + +static inline char * +strdup_vprintf_core(const char *format, va_list args, + ALLOC_FUNC alloc_func, Pool pool) +{ + va_list temp_args; + char *ret; + + if (format == NULL) + return NULL; + format = fix_format(format); + + VA_COPY(temp_args, args); + + ret = alloc_func(pool, printf_string_upper_bound(format, args)); + vsprintf(ret, format, args); + + va_end(temp_args); + + return ret; +} + +char *p_strdup_vprintf(Pool pool, const char *format, va_list args) +{ + return strdup_vprintf_core(format, args, pool->malloc, pool); +} + +const char *t_strdup_vprintf(const char *format, va_list args) +{ + return strdup_vprintf_core(format, args, tp_malloc, NULL); +} + +void p_strdup_replace(Pool pool, char **dest, const char *str) +{ + p_free(pool, *dest); + *dest = p_strdup(pool, str); +} + +const char *temp_strconcat(const char *str1, va_list args, + unsigned int *ret_len) +{ + const char *str; + char *temp; + unsigned int full_len, len, bufsize; + + if (str1 == NULL) + return NULL; + + /* put str1 to buffer */ + len = strlen(str1); + bufsize = len <= STRCONCAT_BUFSIZE ? STRCONCAT_BUFSIZE : + nearest_power(len+1); + temp = t_buffer_get(bufsize); + + memcpy(temp, str1, len); + full_len = len; + + /* put rest of the strings to buffer */ + while ((str = va_arg(args, char *)) != NULL) { + len = strlen(str); + if (len == 0) + continue; + + if (bufsize < full_len+len+1) { + bufsize = nearest_power(bufsize+len+1); + temp = t_buffer_reget(temp, bufsize); + } + + memcpy(temp+full_len, str, len); + full_len += len; + } + + temp[full_len] = '\0'; + *ret_len = full_len+1; + return temp; +} + +char *p_strconcat(Pool pool, const char *str1, ...) +{ + va_list args; + const char *temp; + char *ret; + unsigned int len; + + va_start(args, str1); + + temp = temp_strconcat(str1, args, &len); + if (temp == NULL) + ret = NULL; + else { + ret = p_malloc(pool, len); + memcpy(ret, temp, len); + } + + va_end(args); + return ret; +} + +const char *t_strconcat(const char *str1, ...) +{ + va_list args; + const char *ret; + unsigned int len; + + va_start(args, str1); + + ret = temp_strconcat(str1, args, &len); + if (ret != NULL) + t_buffer_alloc(len); + + va_end(args); + return ret; +} + +const char *t_strcut(const char *str, char cutchar) +{ + const char *p; + + for (p = str; *p != '\0'; p++) { + if (*p == cutchar) + return t_strndup(str, (unsigned int) (p-str)); + } + + return str; +} + +int is_numeric(const char *str, char end_char) +{ + if (*str == '\0' || *str == end_char) + return FALSE; + + while (*str != '\0' && *str != end_char) { + if (!i_isdigit(*str)) + return FALSE; + str++; + } + + return TRUE; +} + +char *str_ucase(char *str) +{ + char *p; + + for (p = str; *p != '\0'; p++) + *p = i_toupper(*p); + return str; +} + +char *str_lcase(char *str) +{ + char *p; + + for (p = str; *p != '\0'; p++) + *p = i_tolower(*p); + return str; +} + +char *i_strtoken(char **str, char delim) +{ + char *ret; + + if (*str == NULL || **str == '\0') + return NULL; + + ret = *str; + while (**str != '\0') { + if (**str == delim) { + **str = '\0'; + (*str)++; + break; + } + (*str)++; + } + return ret; +} + +void string_remove_escapes(char *str) +{ + char *dest; + + for (dest = str; *str != '\0'; str++) { + if (*str != '\\' || str[1] == '\0') + *dest++ = *str; + } + + *dest = '\0'; +} + +int strarray_length(char *const array[]) +{ + int len; + + len = 0; + while (*array) { + len++; + array++; + } + return len; +} + +int strarray_find(char *const array[], const char *item) +{ + int index; + + i_assert(item != NULL); + + for (index = 0; *array != NULL; index++, array++) { + if (strcasecmp(*array, item) == 0) + return index; + } + + return -1; +} + +char *const *t_strsplit(const char *data, const char *separators) +{ + const char **array; + char *str; + int alloc_len, len; + + i_assert(*separators != '\0'); + + str = (char *) t_strdup(data); + + alloc_len = 20; + array = t_buffer_get(sizeof(const char *) * alloc_len); + + array[0] = str; len = 1; + while (*str != '\0') { + if (strchr(separators, *str) != NULL) { + /* separator found */ + if (len+1 >= alloc_len) { + alloc_len *= 2; + array = t_buffer_reget(array, + sizeof(const char *) * + alloc_len); + } + + *str = '\0'; + array[len++] = str+1; + } + + str++; + } + array[len] = NULL; + + t_buffer_alloc(sizeof(const char *) * (len+1)); + return (char *const *) array; +} + +const char *t_strjoin_replace(char *const args[], char separator, + int replacearg, const char *replacedata) +{ + const char *arg; + char *data; + unsigned int alloc_len, arg_len, full_len; + int i; + + if (args[0] == NULL) + return NULL; + + alloc_len = 512; full_len = 0; + data = t_buffer_get(alloc_len); + for (i = 0; args[i] != NULL; i++) { + arg = i == replacearg ? replacedata : args[i]; + arg_len = strlen(arg); + + if (full_len + arg_len+1 >= alloc_len) { + alloc_len = nearest_power(full_len + arg_len+1); + data = t_buffer_reget(data, alloc_len); + } + + memcpy(data+full_len, arg, arg_len); + full_len += arg_len; + + data[full_len++] = separator; + } + data[full_len-1] = '\0'; + + t_buffer_alloc(full_len); + return data; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/strfuncs.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,66 @@ +#ifndef __STRFUNC_H +#define __STRFUNC_H + +#include <limits.h> + +/* max. size for %d */ +#define MAX_INT_STRLEN ((sizeof(int) * CHAR_BIT + 2) / 3 + 1) +/* `str' should be type char[MAX_INT_STRLEN] */ +#define itoa(str, num) \ + i_snprintf(str, sizeof(str), "%d", num) + +#define is_empty_str(str) \ + ((str) == NULL || (str)[0] == '\0') + +unsigned int printf_string_upper_bound(const char *format, va_list args); +int i_snprintf(char *str, unsigned int max_chars, const char *format, ...) + __attr_format__(3, 4); + +char *p_strdup(Pool pool, const char *str); +char *p_strdup_empty(Pool pool, const char *str); /* return NULL if str = "" */ +char *p_strndup(Pool pool, const char *str, unsigned int max_chars); +char *p_strdup_printf(Pool pool, const char *format, ...) __attr_format__(2, 3); +char *p_strdup_vprintf(Pool pool, const char *format, va_list args); +void p_strdup_replace(Pool pool, char **dest, const char *str); +int *p_intarrdup(Pool pool, const int *arr); + +char *p_strconcat(Pool pool, const char *str1, ...); /* NULL terminated */ + +/* same with temporary memory allocations: */ +const char *t_strdup(const char *str); +const char *t_strdup_empty(const char *str); /* return NULL if str = "" */ +const char *t_strndup(const char *str, unsigned int max_chars); +const char *t_strdup_printf(const char *format, ...) __attr_format__(1, 2); +const char *t_strdup_vprintf(const char *format, va_list args); +const int *t_intarrdup(const int *arr); + +const char *t_strconcat(const char *str1, ...); /* NULL terminated */ +const char *t_strcut(const char *str, char cutchar); + +/* Return TRUE if all characters in string are numbers. + Stop when `end_char' is found from string. */ +int is_numeric(const char *str, char end_char); + +char *str_ucase(char *str); +char *str_lcase(char *str); +char *i_strtoken(char **str, char delim); +void string_remove_escapes(char *str); + +/* returns number of items in array */ +int strarray_length(char *const array[]); +/* return index of item in array, or -1 if not found */ +int strarray_find(char *const array[], const char *item); + +/* seprators is an array of separator characters, not a separator string. */ +char * const *t_strsplit(const char *data, const char *separators); + +#define t_strjoin(args, separator) \ + t_strjoin_replace(args, separator, -1, NULL) +const char *t_strjoin_replace(char *const args[], char separator, + int replacearg, const char *replacedata); + +/* INTERNAL */ +const char *temp_strconcat(const char *str1, va_list args, + unsigned int *ret_len); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/temp-mempool.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,470 @@ +/* + temp-mempool.c : Memory pool for temporary memory allocations + + Copyright (c) 2001-2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include <stdlib.h> + +#include "lib.h" +#include "temp-mempool.h" + +/* #define TEMP_POOL_DISABLE */ + +#ifndef TEMP_POOL_DISABLE + +/* max. number of bytes to even try to allocate. This is done just to avoid + allocating less memory than was actually requested because of integer + overflows. */ +#define MAX_ALLOC_SIZE (UINT_MAX - (MEM_ALIGN_SIZE-1)) + +/* Initial pool size - this should be kept in a size that doesn't exceed + in a normal use to keep it fast. */ +#define INITIAL_POOL_SIZE (1024*32) + +typedef struct _MemBlock MemBlock; +typedef struct _MemBlockStack MemBlockStack; + +struct _MemBlock { + MemBlock *next; + + unsigned int size, left; + unsigned char data[1]; +}; + +/* current_stack contains last t_push()ed blocks. After that new + MemBlockStack is created and it's ->prev is set to current_stack. */ +#define MEM_LIST_BLOCK_COUNT 16 + +struct _MemBlockStack { + MemBlockStack *prev; + + MemBlock *block[MEM_LIST_BLOCK_COUNT]; + int block_space_used[MEM_LIST_BLOCK_COUNT]; +}; + +static int stack_pos; /* next free position in current_stack->block[] */ +static MemBlockStack *current_stack; /* current stack position */ +static MemBlockStack *unused_stack_list; /* unused stack blocks */ + +static MemBlock *current_block; /* block currently used for allocation */ +static MemBlock *unused_block; /* largest unused block is kept here */ + +static int last_alloc_size; + +static MemBlock *last_buffer_block; +static unsigned int last_buffer_size; + +int t_push(void) +{ + MemBlockStack *stack; + + if (stack_pos == MEM_LIST_BLOCK_COUNT) { + /* stack list full */ + stack_pos = 0; + if (unused_stack_list == NULL) { + /* allocate new stack */ + stack = calloc(sizeof(MemBlockStack), 1); + if (stack == NULL) + i_panic("t_push(): Out of memory"); + } else { + /* use existing unused stack */ + stack = unused_stack_list; + unused_stack_list = unused_stack_list->prev; + } + + stack->prev = current_stack; + current_stack = stack; + } + + /* mark our current position */ + current_stack->block[stack_pos] = current_block; + current_stack->block_space_used[stack_pos] = current_block->left; + + return stack_pos++; +} + +static void free_blocks(MemBlock *block) +{ + /* free all the blocks, except if any of them is bigger than + unused_block, replace it */ + while (block != NULL) { + if (unused_block == NULL || block->size > unused_block->size) { + free(unused_block); + unused_block = block; + } else { + free(block); + } + + block = block->next; + } +} + +int t_pop(void) +{ + MemBlockStack *stack; + + if (stack_pos == 0) + i_panic("t_pop() called with empty stack"); + stack_pos--; + + /* update the current block */ + current_block = current_stack->block[stack_pos]; + current_block->left = current_stack->block_space_used[stack_pos]; + + if (current_block->next != NULL) { + /* free unused blocks */ + free_blocks(current_block->next); + current_block->next = NULL; + } + + if (stack_pos == 0) { + /* stack block is now unused, add it to unused list */ + stack_pos = MEM_LIST_BLOCK_COUNT; + + stack = current_stack; + current_stack = stack->prev; + + stack->prev = unused_stack_list; + unused_stack_list = stack; + } + + return stack_pos; +} + +static MemBlock *mem_block_alloc(unsigned int min_size) +{ + MemBlock *block; + unsigned int prev_size, alloc_size; + + prev_size = current_block == NULL ? 0 : current_block->size; + alloc_size = nearest_power(prev_size + min_size); + + block = malloc(sizeof(MemBlock)-1 + alloc_size); + if (block == NULL) { + i_panic("mem_block_alloc(): " + "Out of memory when allocating %u bytes", + sizeof(MemBlock)-1 + alloc_size); + } + block->size = alloc_size; + block->next = NULL; + + return block; +} + +static void *t_malloc_real(unsigned int size, int permanent) +{ + MemBlock *block; + void *ret; + + if (size == 0) + return NULL; + + if (size > MAX_ALLOC_SIZE) + i_panic("Trying to allocate too much memory"); + + /* reset t_buffer_get() mark - not really needed but makes it easier + to notice if t_malloc() is called between t_buffer_get() and + t_buffer_alloc() */ + last_buffer_block = NULL; + + /* allocate only aligned amount of memory so alignment comes + always properly */ + size = MEM_ALIGN(size); + + /* used for t_try_grow() */ + last_alloc_size = size; + + if (current_block->left >= size) { + /* enough space in current block, use it */ + ret = current_block->data + + (current_block->size - current_block->left); + if (permanent) + current_block->left -= size; + return ret; + } + + /* current block is full, see if we can use the unused_block */ + if (unused_block != NULL && unused_block->size >= size) { + block = unused_block; + unused_block = NULL; + } else { + block = mem_block_alloc(size); + } + + block->left = block->size; + if (permanent) + block->left -= size; + block->next = NULL; + + current_block->next = block; + current_block = block; + + return current_block->data; +} + +void *t_malloc(unsigned int size) +{ + return t_malloc_real(size, TRUE); +} + +void *t_malloc0(unsigned int size) +{ + void *mem; + + mem = t_malloc_real(size, TRUE); + memset(mem, 0, size); + return mem; +} + +int t_try_grow(void *mem, unsigned int size) +{ + /* see if we want to grow the memory we allocated last */ + if (current_block->data + (current_block->size - + current_block->left - + last_alloc_size) == mem) { + /* yeah, see if we can grow */ + size = MEM_ALIGN(size); + if (current_block->left >= size-last_alloc_size) { + /* just shrink the available size */ + current_block->left -= size - last_alloc_size; + last_alloc_size = size; + return TRUE; + } + } + + return FALSE; +} + +void *t_buffer_get(unsigned int size) +{ + void *ret; + + ret = t_malloc_real(size, FALSE); + + last_buffer_size = size; + last_buffer_block = current_block; + return ret; +} + +void *t_buffer_reget(void *buffer, unsigned int size) +{ + unsigned int old_size; + void *new_buffer; + + old_size = last_buffer_size; + if (size <= old_size) + return buffer; + + new_buffer = t_buffer_get(size); + if (new_buffer != buffer) + memcpy(new_buffer, buffer, old_size); + + return new_buffer; +} + +void t_buffer_alloc(unsigned int size) +{ + i_assert(last_buffer_block != NULL); + i_assert(last_buffer_size >= size); + i_assert(current_block->left >= size); + + /* we've already reserved the space, now we just mark it used */ + t_malloc_real(size, TRUE); +} + +void temp_mempool_init(void) +{ + current_block = mem_block_alloc(INITIAL_POOL_SIZE); + current_block->left = current_block->size; + current_block->next = NULL; + + current_stack = NULL; + unused_stack_list = NULL; + stack_pos = MEM_LIST_BLOCK_COUNT; + + t_push(); + + last_alloc_size = 0; + + last_buffer_block = NULL; + last_buffer_size = 0; +} + +void temp_mempool_deinit(void) +{ + t_pop(); + + if (stack_pos != MEM_LIST_BLOCK_COUNT) + i_panic("Missing t_pop() call"); + + while (unused_stack_list != NULL) { + MemBlockStack *stack = unused_stack_list; + unused_stack_list = unused_stack_list->prev; + + free(stack); + } + + free(current_block); + free(unused_block); +} + +#else + +typedef struct _Stack Stack; +typedef struct _Alloc Alloc; + +struct _Stack { + Stack *next; + Alloc *allocs; +}; + +struct _Alloc { + Alloc *next; + void *mem; +}; + +static int stack_counter; +static Stack *current_stack; +static void *buffer_mem; + +int t_push(void) +{ + Stack *stack; + + stack = malloc(sizeof(Stack)); + stack->allocs = NULL; + + stack->next = current_stack; + current_stack = stack; + return stack_counter++; +} + +int t_pop(void) +{ + Stack *stack; + Alloc *alloc; + + stack = current_stack; + current_stack = stack->next; + + while (stack->allocs != NULL) { + alloc = stack->allocs; + stack->allocs = alloc->next; + + free(alloc->mem); + free(alloc); + } + + free(stack); + return --stack_counter; +} + +static void add_alloc(void *mem) +{ + Alloc *alloc; + + alloc = malloc(sizeof(Alloc)); + alloc->mem = mem; + alloc->next = current_stack->allocs; + current_stack->allocs = alloc; + + if (buffer_mem != NULL) { + free(buffer_mem); + buffer_mem = NULL; + } +} + +void *t_malloc(unsigned int size) +{ + void *mem; + + mem = malloc(size); + add_alloc(mem); + return mem; +} + +void *t_malloc0(unsigned int size) +{ + void *mem; + + mem = calloc(size, 1); + add_alloc(mem); + return mem; +} + +int t_try_grow(void *mem, unsigned int size) +{ + void *new_mem; + + new_mem = realloc(mem, size); + if (new_mem == mem) + return TRUE; + + free(new_mem); + return FALSE; +} + +void *t_buffer_get(unsigned int size) +{ + buffer_mem = realloc(buffer_mem, size); + return buffer_mem; +} + +void *t_buffer_reget(void *buffer, unsigned int size) +{ + i_assert(buffer == buffer_mem); + + buffer_mem = realloc(buffer_mem, size); + return buffer_mem; +} + +void t_buffer_alloc(unsigned int size) +{ + void *mem; + + i_assert(buffer_mem != NULL); + + mem = buffer_mem; + buffer_mem = NULL; + + add_alloc(mem); +} + +void temp_mempool_init(void) +{ + stack_counter = 0; + current_stack = NULL; + buffer_mem = NULL; + + t_push(); +} + +void temp_mempool_deinit(void) +{ + t_pop(); + + if (stack_counter != 0) + i_panic("Missing t_pop() call"); +} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/temp-mempool.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,51 @@ +#ifndef __TEMP_MEMPOOL_H +#define __TEMP_MEMPOOL_H + +/* temporary memory allocations. All t_..() allocations between + t_push() and t_pop() are free'd after t_pop() is called. */ +int t_push(void); +int t_pop(void); + +/* WARNING: Be careful when using this functions, it's too easy to + accidentally save the returned value somewhere permanently. + + You probably should never use this function directly, rather + create functions that return 'const xxx*' types and use t_malloc() + internally in them. This is a lot safer, since usually compiler + warns if you try to place them in xxx*. See strfuncs.c for examples. */ +void *t_malloc(unsigned int size); +void *t_malloc0(unsigned int size); + +/* Try growing allocated memory. Returns TRUE if successful. */ +int t_try_grow(void *mem, unsigned int size); + +#define t_new(type, count) \ + ((type *) t_malloc0((unsigned) sizeof(type) * (count))) + +/* Returns pointer to temporary buffer you can use. The buffer will be + invalid as soon as t_malloc() or t_pop() is called! + + If you wish to grow the buffer, you must give the full wanted size + in the size parameter. If return value doesn't point to the same value + as last time, you need to memcpy() the data from old buffer the this + new one (or do some other trickery). See t_buffer_reget(). */ +#define t_buffer_get_type(type, size) \ + t_buffer_get(sizeof(type) * (size)) +void *t_buffer_get(unsigned int size); + +/* Grow the buffer, memcpy()ing the memory to new location if needed. */ +#define t_buffer_reget_type(buffer, type, size) \ + t_buffer_reget(buffer, sizeof(type) * (size)) +void *t_buffer_reget(void *buffer, unsigned int size); + +/* Make given t_buffer_get()ed buffer permanent. Note that size MUST be + less or equal than the size you gave with last t_buffer_get() or the + result will be undefined. */ +#define t_buffer_alloc_type(type, size) \ + t_buffer_alloc(sizeof(type) * (size)) +void t_buffer_alloc(unsigned int size); + +void temp_mempool_init(void); +void temp_mempool_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/temp-string.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,119 @@ +/* + temp-string.c : Temporary string + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "temp-string.h" + +#include <stdio.h> + +typedef struct { + char *str; + unsigned int len; + + unsigned int alloc_size; +} RealTempString; + +TempString *t_string_new(unsigned int initial_size) +{ + RealTempString *rstr; + + if (initial_size <= 0) + initial_size = 64; + + rstr = t_new(RealTempString, 1); + rstr->alloc_size = initial_size; + rstr->str = t_malloc(rstr->alloc_size); + rstr->str[0] = '\0'; + return (TempString *) rstr; +} + +static void t_string_inc(TempString *tstr, unsigned int size) +{ + RealTempString *rstr = (RealTempString *) tstr; + char *str; + + if (rstr->len + size + 1 > rstr->alloc_size) { + rstr->alloc_size = nearest_power(rstr->len + size + 1); + + if (!t_try_grow(rstr->str, rstr->alloc_size)) { + str = t_malloc(rstr->alloc_size); + memcpy(str, rstr->str, rstr->len+1); + rstr->str = str; + } + } +} + +/* Append string/character */ +void t_string_append(TempString *tstr, const char *str) +{ + t_string_append_n(tstr, str, strlen(str)); +} + +void t_string_append_n(TempString *tstr, const char *str, unsigned int size) +{ + i_assert(size < INT_MAX); + + t_string_inc(tstr, size); + memcpy(tstr->str + tstr->len, str, size); + + tstr->len += size; + tstr->str[tstr->len] = '\0'; +} + +void t_string_append_c(TempString *tstr, char chr) +{ + t_string_inc(tstr, 1); + tstr->str[tstr->len++] = chr; + tstr->str[tstr->len] = '\0'; +} + +void t_string_printfa(TempString *tstr, const char *fmt, ...) +{ + va_list args, args2; + + va_start(args, fmt); + VA_COPY(args2, args); + + t_string_inc(tstr, printf_string_upper_bound(fmt, args)); + tstr->len += vsprintf(tstr->str + tstr->len, fmt, args2); + + va_end(args); +} + +void t_string_erase(TempString *tstr, unsigned int pos, unsigned int len) +{ + i_assert(pos < tstr->len && tstr->len - pos >= len); + + memmove(tstr->str + pos + len, tstr->str + pos, + tstr->len - pos - len + 1); +} + +void t_string_truncate(TempString *tstr, unsigned int len) +{ + i_assert(len <= tstr->len); + + tstr->len = len; + tstr->str[tstr->len] = '\0'; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/temp-string.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,32 @@ +#ifndef __TEMP_STRING_H +#define __TEMP_STRING_H + +/* All memory in TempString is allocated from temporary memory pool, + so it can't be stored permanently. */ + +struct _TempString { + char *str; + unsigned int len; +}; + +TempString *t_string_new(unsigned int initial_size); + +/* Append string/character */ +void t_string_append(TempString *tstr, const char *str); +void t_string_append_n(TempString *tstr, const char *str, unsigned int size); +void t_string_append_c(TempString *tstr, char chr); + +/* Insert string/character (FIXME: not implemented) */ +/*void t_string_insert(TempString *tstr, int pos, const char *str); +void t_string_insert_n(TempString *tstr, int pos, const char *str, int size); +void t_string_insert_c(TempString *tstr, int pos, char chr);*/ + +/* Append printf()-like data */ +void t_string_printfa(TempString *tstr, const char *fmt, ...) + __attr_format__(2, 3); + +/* Erase/truncate */ +void t_string_erase(TempString *tstr, unsigned int pos, unsigned int len); +void t_string_truncate(TempString *tstr, unsigned int len); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/unlink-directory.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,72 @@ +/* + unlink-directory.c : Unlink directory with everything under it. + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "unlink-directory.h" + +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +int unlink_directory(const char *dir) +{ + DIR *dirp; + struct dirent *d; + struct stat st; + char path[1024]; + + dirp = opendir(dir); + if (dirp == NULL) + return FALSE; + + while ((d = readdir(dirp)) != NULL) { + if (d->d_name[0] == '.' && + (d->d_name[1] == '\0' || + (d->d_name[1] == '.' && d->d_name[2] == '\0'))) { + /* skip . and .. */ + continue; + } + + i_snprintf(path, sizeof(path), "%s/%s", dir, d->d_name); + + if (unlink(path) == -1) { + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + if (!unlink_directory(path)) + return FALSE; + } else { + /* so it wasn't a directory, unlink() again + to get correct errno */ + if (unlink(path) == -1) + return FALSE; + } + } + } + + (void)closedir(dirp); + + if (rmdir(dir) == -1) + return FALSE; + return TRUE; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/unlink-directory.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,7 @@ +#ifndef __UNLINK_DIRECTORY_H +#define __UNLINK_DIRECTORY_H + +/* Unlink directory with everything under it. Returns TRUE if successful. */ +int unlink_directory(const char *dir); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/unlink-lockfiles.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,77 @@ +/* + unlink-lockfiles.c : Utility function for easier deletion of lock files. + + Copyright (c) 2002 Timo Sirainen + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#include "lib.h" +#include "unlink-lockfiles.h" + +#include <stdlib.h> +#include <signal.h> +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +void unlink_lockfiles(const char *dir, const char *pidprefix, + const char *otherprefix, time_t other_min_time) +{ + DIR *dirp; + struct dirent *d; + struct stat st; + char path[1024]; + unsigned int pidlen, otherlen; + + /* check for any invalid access files */ + dirp = opendir(dir); + if (dirp == NULL) + return; + + pidlen = pidprefix == NULL ? 0 : strlen(pidprefix); + otherlen = otherprefix == NULL ? 0 : strlen(otherprefix); + + while ((d = readdir(dirp)) != NULL) { + const char *fname = d->d_name; + + if (pidprefix != NULL && + strncmp(fname, pidprefix, pidlen) == 0 && + is_numeric(fname+pidlen, '\0')) { + /* found a lock file from our host - see if the PID + is valid (meaning it exists, and the it's with + the same UID as us) */ + if (kill(atoi(fname+pidlen), 0) == 0) + continue; /* valid */ + + i_snprintf(path, sizeof(path), "%s/%s", dir, fname); + (void)unlink(path); + } else if (otherprefix != 0 && + strncmp(fname, otherprefix, otherlen) == 0) { + i_snprintf(path, sizeof(path), "%s/%s", dir, fname); + if (stat(path, &st) == 0 && + st.st_mtime < other_min_time) { + (void)unlink(path); + } + } + } + + (void)closedir(dirp); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib/unlink-lockfiles.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,7 @@ +#ifndef __UNLINK_LOCKFILES_H +#define __UNLINK_LOCKFILES_H + +void unlink_lockfiles(const char *dir, const char *pidprefix, + const char *otherprefix, time_t other_min_time); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,9 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations +imap-login
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,24 @@ +pkglib_PROGRAMS = imap-login + +INCLUDES = \ + -I$(top_srcdir)/src/lib + +imap_login_LDADD = \ + ../lib/liblib.a \ + $(SSL_LIBS) + +imap_login_SOURCES = \ + auth-connection.c \ + client.c \ + client-authenticate.c \ + main.c \ + master.c \ + ssl-proxy.c + +noinst_HEADERS = \ + auth-connection.h \ + common.h \ + client.h \ + client-authenticate.h \ + master.h \ + ssl-proxy.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/auth-connection.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,352 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "hash.h" +#include "network.h" +#include "iobuffer.h" +#include "auth-connection.h" + +#include <unistd.h> +#include <dirent.h> +#include <sys/stat.h> + +#define MAX_INBUF_SIZE (AUTH_MAX_REQUEST_DATA_SIZE) +#define MAX_OUTBUF_SIZE \ + (sizeof(AuthContinuedRequestData) + AUTH_MAX_REQUEST_DATA_SIZE) + +struct _AuthConnection { + AuthConnection *next; + + char *path; + int fd; + IO io; + IOBuffer *inbuf, *outbuf; + + int auth_process; + AuthMethod available_auth_methods; + AuthReplyData in_reply; + + HashTable *requests; + + unsigned int init_received:1; + unsigned int in_reply_received:1; +}; + +AuthMethod available_auth_methods; + +static int auth_connects_failed; +static int request_id_counter; +static AuthConnection *auth_connections; + +static void auth_input(void *user_data, int fd, IO io); +static void auth_connect_missing(void); + +static AuthConnection *auth_connection_new(const char *path) +{ + AuthConnection *conn; + int fd; + + fd = net_connect_unix(path); + if (fd == -1) { + i_error("Can't connect to imap-auth at %s: %m", path); + auth_connects_failed = TRUE; + return NULL; + } + + conn = i_new(AuthConnection, 1); + conn->path = i_strdup(path); + conn->fd = fd; + conn->io = io_add(fd, IO_READ, auth_input, conn); + conn->inbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_HIGH, + MAX_INBUF_SIZE); + conn->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT, + MAX_OUTBUF_SIZE); + conn->requests = hash_create(default_pool, 100, NULL, NULL); + + conn->next = auth_connections; + auth_connections = conn; + return conn; +} + +static void request_destroy(AuthRequest *request) +{ + hash_remove(request->conn->requests, INT_TO_POINTER(request->id)); + i_free(request); +} + +static void request_abort(AuthRequest *request) +{ + request->callback(request, request->conn->auth_process, + AUTH_RESULT_INTERNAL_FAILURE, + "Authentication process died", 0, request->user_data); + request_destroy(request); +} + +static void request_hash_destroy(void *key __attr_unused__, void *value, + void *user_data __attr_unused__) +{ + request_abort(value); +} + +static void auth_connection_destroy(AuthConnection *conn, int reconnect) +{ + AuthConnection **pos; + char *path; + + for (pos = &auth_connections; *pos != NULL; pos = &(*pos)->next) { + if (*pos == conn) { + *pos = conn->next; + break; + } + } + + path = conn->path; + + hash_foreach(conn->requests, request_hash_destroy, NULL); + hash_destroy(conn->requests); + + (void)close(conn->fd); + io_remove(conn->io); + io_buffer_destroy(conn->inbuf); + io_buffer_destroy(conn->outbuf); + i_free(conn); + + if (reconnect) { + auth_connection_new(path); + i_free(path); + } +} + +static AuthConnection *auth_connection_get(AuthMethod method, unsigned int size, + const char **error) +{ + AuthConnection *conn; + int found; + + found = FALSE; + for (conn = auth_connections; conn != NULL; conn = conn->next) { + if ((conn->available_auth_methods & method)) { + if (io_buffer_get_space(conn->outbuf, size) != NULL) + return conn; + + found = TRUE; + } + } + + if (!found) + *error = "Unsupported authentication method"; + else { + *error = "Authentication servers are busy, wait.."; + i_warning("Authentication servers are busy"); + } + + return NULL; +} + +static void update_available_auth_methods(void) +{ + AuthConnection *conn; + + available_auth_methods = 0; + for (conn = auth_connections; conn != NULL; conn = conn->next) + available_auth_methods |= conn->available_auth_methods; +} + +static void auth_handle_init(AuthConnection *conn, AuthInitData *init_data) +{ + conn->auth_process = init_data->auth_process; + conn->available_auth_methods = init_data->auth_methods; + conn->init_received = TRUE; + + update_available_auth_methods(); +} + +static void auth_handle_reply(AuthConnection *conn, AuthReplyData *reply_data, + unsigned char *data) +{ + AuthRequest *request; + + request = hash_lookup(conn->requests, INT_TO_POINTER(reply_data->id)); + if (request == NULL) { + i_error("BUG: imap-auth sent us reply with unknown ID %u", + reply_data->id); + return; + } + + /* save the returned cookie */ + memcpy(request->cookie, reply_data->cookie, AUTH_COOKIE_SIZE); + + t_push(); + request->callback(request, request->conn->auth_process, + reply_data->result, data, reply_data->data_size, + request->user_data); + t_pop(); + + if (reply_data->result != AUTH_RESULT_CONTINUE) + request_destroy(request); +} + +static void auth_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + AuthConnection *conn = user_data; + AuthInitData init_data; + unsigned char *data; + unsigned int size; + + switch (io_buffer_read(conn->inbuf)) { + case 0: + return; + case -1: + /* disconnected */ + auth_connection_destroy(conn, TRUE); + return; + case -2: + /* buffer full - can't happen unless imap-auth is buggy */ + i_error("BUG: imap-auth sent us more than %d bytes of data", + MAX_INBUF_SIZE); + auth_connection_destroy(conn, TRUE); + return; + } + + data = io_buffer_get_data(conn->inbuf, &size); + + if (!conn->init_received) { + if (size == sizeof(AuthInitData)) { + memcpy(&init_data, data, sizeof(AuthInitData)); + conn->inbuf->skip += sizeof(AuthInitData); + + auth_handle_init(conn, &init_data); + } else if (size > sizeof(AuthInitData)) { + i_error("BUG: imap-auth sent us too much " + "initialization data (%u vs %u)", + size, sizeof(AuthInitData)); + auth_connection_destroy(conn, TRUE); + } + + return; + } + + if (!conn->in_reply_received) { + data = io_buffer_get_data(conn->inbuf, &size); + if (size < sizeof(AuthReplyData)) + return; + + memcpy(&conn->in_reply, data, sizeof(AuthReplyData)); + data += sizeof(AuthReplyData); + size -= sizeof(AuthReplyData); + conn->inbuf->skip += sizeof(AuthReplyData); + conn->in_reply_received = TRUE; + } + + if (size < conn->in_reply.data_size) + return; + + /* we've got a full reply */ + size = conn->in_reply.data_size; + conn->inbuf->skip += size; + conn->in_reply_received = FALSE; + + auth_handle_reply(conn, &conn->in_reply, data); +} + +int auth_init_request(AuthMethod method, AuthCallback callback, + void *user_data, const char **error) +{ + AuthConnection *conn; + AuthRequest *request; + AuthInitRequestData request_data; + + if (auth_connects_failed) + auth_connect_missing(); + + conn = auth_connection_get(method, sizeof(AuthInitRequestData), error); + if (conn == NULL) + return FALSE; + + /* create internal request structure */ + request = i_new(AuthRequest, 1); + request->method = method; + request->conn = conn; + request->id = ++request_id_counter; + request->callback = callback; + request->user_data = user_data; + + hash_insert(conn->requests, INT_TO_POINTER(request->id), request); + + /* send request to auth */ + request_data.type = AUTH_REQUEST_INIT; + request_data.method = request->method; + request_data.id = request->id; + if (io_buffer_send(request->conn->outbuf, &request_data, + sizeof(request_data)) < 0) + auth_connection_destroy(request->conn, TRUE); + return TRUE; +} + +void auth_continue_request(AuthRequest *request, const unsigned char *data, + unsigned int data_size) +{ + AuthContinuedRequestData request_data; + + /* send continued request to auth */ + memcpy(request_data.cookie, request->cookie, AUTH_COOKIE_SIZE); + request_data.type = AUTH_REQUEST_CONTINUE; + request_data.id = request->id; + request_data.data_size = data_size; + + if (io_buffer_send(request->conn->outbuf, &request_data, + sizeof(request_data)) < 0) + auth_connection_destroy(request->conn, TRUE); + else if (io_buffer_send(request->conn->outbuf, data, data_size) < 0) + auth_connection_destroy(request->conn, TRUE); +} + +static void auth_connect_missing(void) +{ + DIR *dirp; + struct dirent *dp; + struct stat st; + + auth_connects_failed = TRUE; + + /* we're chrooted into */ + dirp = opendir("."); + if (dirp == NULL) { + i_error("opendir(\".\") failed when trying to get list of " + "authentication servers: %m"); + return; + } + + while ((dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.') + continue; + + if (stat(dp->d_name, &st) == 0 && S_ISSOCK(st.st_mode)) { + if (auth_connection_new(dp->d_name) != NULL) + auth_connects_failed = FALSE; + } + } + + (void)closedir(dirp); +} + +void auth_connection_init(void) +{ + auth_connections = NULL; + request_id_counter = 0; + auth_connects_failed = FALSE; + + auth_connect_missing(); +} + +void auth_connection_deinit(void) +{ + AuthConnection *next; + + while (auth_connections != NULL) { + next = auth_connections->next; + auth_connection_destroy(auth_connections, FALSE); + auth_connections = next; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/auth-connection.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,36 @@ +#ifndef __AUTH_CONNECTION_H +#define __AUTH_CONNECTION_H + +typedef struct _AuthConnection AuthConnection; + +/* If result == AUTH_RESULT_INTERNAL_FAILURE, request may be NULL and + reply_data_size contains the error message. */ +typedef void (*AuthCallback)(AuthRequest *request, int auth_process, + AuthResult result, const unsigned char *reply_data, + unsigned int reply_data_size, void *user_data); + +struct _AuthRequest { + AuthMethod method; + AuthConnection *conn; + + int id; + unsigned char cookie[AUTH_COOKIE_SIZE]; + + AuthCallback callback; + void *user_data; + + unsigned int init_sent:1; +}; + +extern AuthMethod available_auth_methods; + +int auth_init_request(AuthMethod method, AuthCallback callback, + void *user_data, const char **error); + +void auth_continue_request(AuthRequest *request, const unsigned char *data, + unsigned int data_size); + +void auth_connection_init(void); +void auth_connection_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/client-authenticate.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,299 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "base64.h" +#include "iobuffer.h" +#include "temp-string.h" +#include "auth-connection.h" +#include "client.h" +#include "client-authenticate.h" +#include "master.h" + +typedef struct { + int method; + const char *name; + int plaintext; +} AuthMethodDesc; + +static AuthMethod auth_methods = 0; +static char *auth_methods_capability = NULL; + +static AuthMethodDesc auth_method_desc[AUTH_METHODS_COUNT] = { + { AUTH_METHOD_PLAIN, NULL, TRUE }, + { AUTH_METHOD_DIGEST_MD5, "DIGEST-MD5", FALSE } +}; + +const char *client_authenticate_get_capabilities(void) +{ + TempString *str; + int i; + + if (auth_methods == available_auth_methods) + return auth_methods_capability; + + auth_methods = available_auth_methods; + i_free(auth_methods_capability); + + str = t_string_new(128); + t_string_append_c(str, ' '); + + for (i = 0; i < AUTH_METHODS_COUNT; i++) { + if ((auth_methods & auth_method_desc[i].method) && + auth_method_desc[i].name != NULL) { + if (str->len > 0) + t_string_append_c(str, ' '); + t_string_append(str, "AUTH="); + t_string_append(str, auth_method_desc[i].name); + } + } + + auth_methods_capability = str->len == 1 ? NULL : + i_strdup_empty(str->str); + return auth_methods_capability; +} + +static AuthMethodDesc *auth_method_find(const char *name) +{ + int i; + + for (i = 0; i < AUTH_METHODS_COUNT; i++) { + if (auth_method_desc[i].name != NULL && + strcasecmp(auth_method_desc[i].name, name) == 0) + return &auth_method_desc[i]; + } + + return NULL; +} + +static void client_auth_abort(Client *client, const char *msg) +{ + client->auth_request = NULL; + + client_send_tagline(client, msg != NULL ? msg : + "NO Authentication failed."); + + /* get back to normal client input */ + if (client->io != NULL) + io_remove(client->io); + client->io = io_add(client->fd, IO_READ, client_input, client); + + client_unref(client); +} + +static void master_callback(MasterReplyResult result, void *user_data) +{ + Client *client = user_data; + + switch (result) { + case MASTER_RESULT_SUCCESS: + client_destroy(client, "Logged in."); + break; + case MASTER_RESULT_INTERNAL_FAILURE: + client_auth_abort(client, "Internal failure"); + break; + default: + client_auth_abort(client, NULL); + break; + } + + client_unref(client); +} + +static void client_send_auth_data(Client *client, const unsigned char *data, + unsigned int size) +{ + const char *base64_data; + + t_push(); + + base64_data = base64_encode(data, size); + io_buffer_send(client->outbuf, "+ ", 2); + io_buffer_send(client->outbuf, base64_data, strlen(base64_data)); + io_buffer_send(client->outbuf, "\r\n", 2); + + t_pop(); +} + +static int auth_callback(AuthRequest *request, int auth_process, + AuthResult result, const unsigned char *reply_data, + unsigned int reply_data_size, void *user_data) +{ + Client *client = user_data; + + switch (result) { + case AUTH_RESULT_CONTINUE: + client->auth_request = request; + return TRUE; + + case AUTH_RESULT_SUCCESS: + client->auth_request = NULL; + + master_request_imap(client->fd, auth_process, client->tag, + request->cookie, master_callback, client); + + /* disable IO until we're back from master */ + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + return FALSE; + + case AUTH_RESULT_FAILURE: + /* see if we have error message */ + if (reply_data_size > 0 && + reply_data[reply_data_size-1] == '\0') { + client_auth_abort(client, t_strconcat( + "NO Authentication failed: ", + (char *) reply_data, NULL)); + } else { + /* default error message */ + client_auth_abort(client, NULL); + } + return FALSE; + default: + client_auth_abort(client, t_strconcat( + "NO Authentication failed: ", reply_data, NULL)); + return FALSE; + } +} + +static void login_callback(AuthRequest *request, int auth_process, + AuthResult result, const unsigned char *reply_data, + unsigned int reply_data_size, void *user_data) +{ + Client *client = user_data; + + if (auth_callback(request, auth_process, result, + reply_data, reply_data_size, user_data)) { + auth_continue_request(request, client->plain_login, + client->plain_login_len); + + i_free(client->plain_login); + client->plain_login = NULL; + } +} + +int cmd_login(Client *client, const char *user, const char *pass) +{ + const char *error; + char *p; + unsigned int len, user_len, pass_len; + + if (disable_plaintext_auth) { + client_send_tagline(client, + "NO Plaintext authentication disabled."); + return TRUE; + } + + /* code it into user\0user\0password */ + user_len = strlen(user); + pass_len = strlen(pass); + len = user_len + 1 + user_len + 1 + pass_len; + + i_free(client->plain_login); + client->plain_login = p = i_malloc(len); + client->plain_login_len = len; + + memcpy(p, user, user_len); p += user_len; *p++ = '\0'; + memcpy(p, user, user_len); p += user_len; *p++ = '\0'; + memcpy(p, pass, pass_len); + + client_ref(client); + if (auth_init_request(AUTH_METHOD_PLAIN, + login_callback, client, &error)) { + /* don't read any input from client until login is finished */ + io_remove(client->io); + client->io = NULL; + return TRUE; + } else { + client_send_tagline(client, t_strconcat( + "NO Login failed: ", error, NULL)); + client_unref(client); + return TRUE; + } +} + +static void authenticate_callback(AuthRequest *request, int auth_process, + AuthResult result, + const unsigned char *reply_data, + unsigned int reply_data_size, void *user_data) +{ + Client *client = user_data; + + if (auth_callback(request, auth_process, result, + reply_data, reply_data_size, user_data)) + client_send_auth_data(client, reply_data, reply_data_size); +} + +static void client_auth_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + Client *client = user_data; + char *line; + int size; + + if (!client_read(client)) + return; + + line = io_buffer_next_line(client->inbuf); + if (line == NULL) + return; + + if (strcmp(line, "*") == 0) { + client_auth_abort(client, "NO Authentication aborted"); + return; + } + + size = base64_decode(line); + if (size < 0) { + /* failed */ + client_auth_abort(client, "NO Invalid base64 data"); + return; + } + + if (client->auth_request == NULL) { + client_auth_abort(client, "NO Don't send unrequested data"); + return; + } + + auth_continue_request(client->auth_request, (unsigned char *) line, + (unsigned int) size); +} + +int cmd_authenticate(Client *client, const char *method_name) +{ + AuthMethodDesc *method; + const char *error; + + if (*method_name == '\0') + return FALSE; + + method = auth_method_find(method_name); + if (method == NULL) { + client_send_tagline(client, + "NO Unsupported authentication method."); + return TRUE; + } + + if (method->plaintext && disable_plaintext_auth) { + client_send_tagline(client, + "NO Plaintext authentication disabled."); + return TRUE; + } + + client_ref(client); + if (auth_init_request(method->method, authenticate_callback, + client, &error)) { + /* following input data will go to authentication */ + io_remove(client->io); + client->io = io_add(client->fd, IO_READ, + client_auth_input, client); + } else { + client_send_tagline(client, t_strconcat( + "NO Authentication failed: ", error, NULL)); + client_unref(client); + } + + return TRUE; +} +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/client-authenticate.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,9 @@ +#ifndef __CLIENT_AUTHENTICATE_H +#define __CLIENT_AUTHENTICATE_H + +const char *client_authenticate_get_capabilities(void); + +int cmd_login(Client *client, const char *user, const char *pass); +int cmd_authenticate(Client *client, const char *method_name); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/client.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,352 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "hash.h" +#include "iobuffer.h" +#include "client.h" +#include "client-authenticate.h" +#include "ssl-proxy.h" + +#include <syslog.h> + +/* Disconnect client after idling this many seconds */ +#define CLIENT_LOGIN_IDLE_TIMEOUT 60 + +/* When max. number of simultaneous connections is reached, few of the + oldest connections are disconnected. Since we have to go through the whole + client hash, it's faster if we disconnect multiple clients. */ +#define CLIENT_DESTROY_OLDEST_COUNT 16 + +static HashTable *clients; +static Timeout to_idle; + +static int cmd_capability(Client *client) +{ + const char *capability; + + capability = t_strconcat("* CAPABILITY " CAPABILITY_STRING, + disable_plaintext_auth && !client->tls ? + " LOGINDISABLED" : "", + client_authenticate_get_capabilities(), + NULL); + client_send_line(client, capability); + client_send_tagline(client, "OK Capability completed."); + return TRUE; +} + +static int cmd_starttls(Client *client) +{ +#ifdef HAVE_SSL + int fd_ssl; + + if (client->tls) { + client_send_tagline(client, "BAD TLS is already active."); + return TRUE; + } + + client_send_tagline(client, "OK Begin TLS negotiation now."); + io_buffer_send_flush(client->outbuf); + + fd_ssl = ssl_proxy_new(client->fd); + if (fd_ssl != -1) { + client->tls = TRUE; + client->fd = fd_ssl; + client->inbuf->fd = fd_ssl; + client->outbuf->fd = fd_ssl; + } else { + client_send_line(client, " * BYE TLS handehake failed."); + client_destroy(client, "TLS handshake failed"); + } +#else + client_send_tagline(client, "BAD TLS support isn't enabled."); +#endif + return TRUE; +} + +static int cmd_noop(Client *client) +{ + client_send_tagline(client, "OK NOOP completed."); + return TRUE; +} + +static int cmd_logout(Client *client) +{ + client_send_line(client, "* BYE Logging out"); + client_send_tagline(client, "OK Logout completed."); + client_destroy(client, "Logged out"); + return TRUE; +} + +int client_read(Client *client) +{ + switch (io_buffer_read(client->inbuf)) { + case -2: + /* buffer full */ + client_send_line(client, "* BYE Input buffer full, aborting"); + client_destroy(client, "Disconnected: Input buffer full"); + return FALSE; + case -1: + /* disconnected */ + client_destroy(client, "Disconnected"); + return FALSE; + default: + /* something was read */ + return TRUE; + } +} + +static char *get_next_arg(char **line) +{ + char *start; + int quoted; + + while (**line == ' ') (*line)++; + + if (**line == '"') { + quoted = TRUE; + (*line)++; + + start = *line; + while (**line != '\0' && **line != '"') { + if (**line == '\\' && (*line)[1] != '\0') + (*line)++; + (*line)++; + } + + if (**line == '"') + *(*line)++ = '\0'; + string_remove_escapes(start); + } else { + start = *line; + while (**line != '\0' && **line != ' ') + (*line)++; + + if (**line == ' ') + *(*line)++ = '\0'; + } + + return start; +} + +static int client_command_execute(Client *client, char *line) +{ + char *cmd; + + cmd = get_next_arg(&line); + str_ucase(cmd); + + if (strcmp(cmd, "LOGIN") == 0) { + char *user, *pass; + + user = get_next_arg(&line); + pass = get_next_arg(&line); + return cmd_login(client, user, pass); + } + if (strcmp(cmd, "AUTHENTICATE") == 0) + return cmd_authenticate(client, get_next_arg(&line)); + if (strcmp(cmd, "CAPABILITY") == 0) + return cmd_capability(client); + if (strcmp(cmd, "STARTTLS") == 0) + return cmd_starttls(client); + if (strcmp(cmd, "NOOP") == 0) + return cmd_noop(client); + if (strcmp(cmd, "LOGOUT") == 0) + return cmd_logout(client); + + return FALSE; +} + +void client_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + Client *client = user_data; + char *line; + + client->last_input = ioloop_time; + + i_free(client->tag); + client->tag = i_strdup("*"); + + if (!client_read(client)) + return; + + client_ref(client); + io_buffer_cork(client->outbuf); + + while ((line = io_buffer_next_line(client->inbuf)) != NULL) { + /* split the arguments, make sure we have at + least tag + command */ + i_free(client->tag); + client->tag = i_strdup(get_next_arg(&line)); + + if (*client->tag == '\0' || + !client_command_execute(client, line)) { + /* error */ + client_send_tagline(client, "BAD Error in IMAP command " + "received by server."); + } + } + + if (client_unref(client)) + io_buffer_send_flush(client->outbuf); +} + +static void client_hash_destroy_oldest(void *key, void *value __attr_unused__, + void *user_data) +{ + Client *client = key; + Client **destroy_clients = user_data; + int i; + + for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) { + if (destroy_clients[i] == NULL || + destroy_clients[i]->created > client->created) { + memmove(destroy_clients+i+1, destroy_clients+i, + sizeof(Client *) * + (CLIENT_DESTROY_OLDEST_COUNT - i-1)); + destroy_clients[i] = client; + break; + } + } +} + +static void client_destroy_oldest(void) +{ + Client *destroy_clients[CLIENT_DESTROY_OLDEST_COUNT]; + int i; + + memset(destroy_clients, 0, sizeof(destroy_clients)); + hash_foreach(clients, client_hash_destroy_oldest, destroy_clients); + + for (i = 0; i < CLIENT_DESTROY_OLDEST_COUNT; i++) { + client_destroy(destroy_clients[i], + "Disconnected: Connection queue full"); + } +} + +Client *client_create(int fd, IPADDR *ip) +{ + Client *client; + + if (max_logging_users > CLIENT_DESTROY_OLDEST_COUNT && + hash_size(clients) >= max_logging_users) { + /* reached max. users count, kill few of the + oldest connections */ + client_destroy_oldest(); + } + + client = i_new(Client, 1); + client->created = ioloop_time; + client->refcount = 1; + + memcpy(&client->ip, ip, sizeof(IPADDR)); + client->fd = fd; + client->io = io_add(fd, IO_READ, client_input, client); + client->inbuf = io_buffer_create(fd, default_pool, + IO_PRIORITY_DEFAULT, 8192); + client->outbuf = io_buffer_create(fd, default_pool, + IO_PRIORITY_DEFAULT, 1024); + client->last_input = ioloop_time; + hash_insert(clients, client, client); + + client_send_line(client, "* OK " PACKAGE " ready."); + return client; +} + +void client_destroy(Client *client, const char *reason) +{ + if (reason != NULL) + client_syslog(client, reason); + + hash_remove(clients, client); + + io_buffer_close(client->inbuf); + io_buffer_close(client->outbuf); + + if (client->io != NULL) { + io_remove(client->io); + client->io = NULL; + } + + net_disconnect(client->fd); + client->fd = -1; + + client_unref(client); +} + +void client_ref(Client *client) +{ + client->refcount++; +} + +int client_unref(Client *client) +{ + if (--client->refcount > 0) + return TRUE; + + io_buffer_destroy(client->inbuf); + io_buffer_destroy(client->outbuf); + + i_free(client->tag); + i_free(client->plain_login); + i_free(client); + return FALSE; +} + +void client_send_line(Client *client, const char *line) +{ + io_buffer_send(client->outbuf, line, strlen(line)); + io_buffer_send(client->outbuf, "\r\n", 2); +} + +void client_send_tagline(Client *client, const char *line) +{ + client_send_line(client, t_strconcat(client->tag, " ", line, NULL)); +} + +void client_syslog(Client *client, const char *text) +{ + char host[MAX_IP_LEN]; + + if (net_ip2host(&client->ip, host) == -1) + host[0] = '\0'; + + syslog(LOG_INFO, "%s [%s]", text, host); +} + +static void client_hash_check_idle(void *key, void *value __attr_unused__, + void *user_data __attr_unused__) +{ + Client *client = key; + + if (ioloop_time - client->last_input >= CLIENT_LOGIN_IDLE_TIMEOUT) { + client_send_line(client, "* BYE Disconnected for inactivity."); + client_destroy(client, "Disconnected: Inactivity"); + } +} + +static void idle_timeout(void *user_data __attr_unused__, + Timeout timeout __attr_unused__) +{ + hash_foreach(clients, client_hash_check_idle, NULL); +} + +void clients_init(void) +{ + clients = hash_create(default_pool, 128, NULL, NULL); + to_idle = timeout_add(1000, idle_timeout, NULL); +} + +static void client_hash_destroy(void *key, void *value __attr_unused__, + void *user_data __attr_unused__) +{ + client_destroy(key, NULL); +} + +void clients_deinit(void) +{ + hash_foreach(clients, client_hash_destroy, NULL); + hash_destroy(clients); + + timeout_remove(to_idle); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/client.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,42 @@ +#ifndef __CLIENT_H +#define __CLIENT_H + +#include "network.h" + +struct _Client { + time_t created; + int refcount; + IPADDR ip; + + int fd; + IO io; + IOBuffer *inbuf, *outbuf; + + time_t last_input; + char *tag; + + char *plain_login; + unsigned int plain_login_len; + + AuthRequest *auth_request; + + unsigned int tls:1; +}; + +Client *client_create(int fd, IPADDR *ip); +void client_destroy(Client *client, const char *reason); + +void client_ref(Client *client); +int client_unref(Client *client); + +void client_send_line(Client *client, const char *line); +void client_send_tagline(Client *client, const char *line); +void client_syslog(Client *client, const char *text); + +int client_read(Client *client); +void client_input(void *user_data, int fd, IO io); + +void clients_init(void); +void clients_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/common.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,14 @@ +#ifndef __COMMON_H +#define __COMMON_H + +#include "lib.h" +#include "../auth/auth-interface.h" + +typedef struct _Client Client; +typedef struct _AuthRequest AuthRequest; + +extern IOLoop ioloop; +extern int disable_plaintext_auth; +extern unsigned int max_logging_users; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/main.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,143 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "ioloop.h" +#include "lib-signals.h" +#include "restrict-access.h" +#include "auth-connection.h" +#include "master.h" +#include "client.h" +#include "ssl-proxy.h" + +#include <stdlib.h> +#include <syslog.h> + +IOLoop ioloop; +int disable_plaintext_auth; +unsigned int max_logging_users; + +static IO io_imap, io_imaps; + +static void sig_quit(int signo __attr_unused__) +{ + io_loop_stop(ioloop); +} + +static void login_accept(void *user_data __attr_unused__, int listen_fd, + IO io __attr_unused__) +{ + IPADDR addr; + int fd; + + fd = net_accept(listen_fd, &addr, NULL); + if (fd == -1) + return; + + (void)client_create(fd, &addr); +} + +static void login_accept_ssl(void *user_data __attr_unused__, int listen_fd, + IO io __attr_unused__) +{ + Client *client; + IPADDR addr; + int fd, fd_ssl; + + fd = net_accept(listen_fd, &addr, NULL); + if (fd == -1) + return; + + fd_ssl = ssl_proxy_new(fd); + if (fd_ssl == -1) + net_disconnect(fd); + else { + client = client_create(fd_ssl, &addr); + client->tls = TRUE; + } +} + +static void main_init(void) +{ + const char *logfile, *value; + + lib_init_signals(sig_quit); + + logfile = getenv("IMAP_LOGFILE"); + if (logfile == NULL) { + /* open the syslog immediately so chroot() won't + break logging */ + openlog("imap-login", LOG_NDELAY, LOG_MAIL); + + i_set_panic_handler(i_syslog_panic_handler); + i_set_fatal_handler(i_syslog_fatal_handler); + i_set_error_handler(i_syslog_error_handler); + i_set_warning_handler(i_syslog_warning_handler); + } else { + /* log failures into specified log file */ + i_set_failure_file(logfile, "imap-login"); + i_set_failure_timestamp_format(DEFAULT_FAILURE_STAMP_FORMAT); + } + + disable_plaintext_auth = getenv("DISABLE_PLAINTEXT_AUTH") != NULL; + + value = getenv("MAX_LOGGING_USERS"); + max_logging_users = value == NULL ? 0 : strtoul(value, NULL, 10); + + /* Initialize SSL proxy before dropping privileges so it can read + the certificate and private key file. */ + ssl_proxy_init(); + + restrict_access_by_env(); + + auth_connection_init(); + master_init(); + clients_init(); + + io_imap = io_imaps = NULL; + + if (net_getsockname(LOGIN_IMAP_LISTEN_FD, NULL, NULL) == 0) { + /* we're listening for imap */ + io_imap = io_add(LOGIN_IMAP_LISTEN_FD, IO_READ, + login_accept, NULL); + } + + if (net_getsockname(LOGIN_IMAPS_LISTEN_FD, NULL, NULL) == 0) { + /* we're listening for imaps */ + io_imaps = io_add(LOGIN_IMAPS_LISTEN_FD, IO_READ, + login_accept_ssl, NULL); + } +} + +static void main_deinit(void) +{ + if (lib_signal_kill != 0) + i_warning("Killed with signal %d", lib_signal_kill); + + if (io_imap != NULL) io_remove(io_imap); + if (io_imaps != NULL) io_remove(io_imaps); + + clients_deinit(); + master_deinit(); + auth_connection_deinit(); + + ssl_proxy_deinit(); + + closelog(); +} + +int main(int argc __attr_unused__, char *argv[] __attr_unused__) +{ + /* NOTE: we start rooted, so keep the code minimal until + restrict_access_by_env() is called */ + lib_init(); + ioloop = io_loop_create(); + + main_init(); + io_loop_run(ioloop); + main_deinit(); + + io_loop_destroy(ioloop); + lib_deinit(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/master.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,129 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "ioloop.h" +#include "network.h" +#include "fdpass.h" +#include "master.h" + +typedef struct _WaitingRequest WaitingRequest; + +struct _WaitingRequest { + WaitingRequest *next; + + int id; + MasterCallback callback; + void *user_data; +}; + +static IO io_master; +static WaitingRequest *requests, **next_request; + +static unsigned int master_pos; +static char master_buf[sizeof(MasterReply)]; + +static void push_request(int id, MasterCallback callback, void *user_data) +{ + WaitingRequest *req; + + req = i_new(WaitingRequest, 1); + req->id = id; + req->callback = callback; + req->user_data = user_data; + + *next_request = req; + next_request = &req->next; +} + +static void pop_request(MasterReply *reply) +{ + WaitingRequest *req; + + req = requests; + if (req == NULL) { + i_error("Master sent us unrequested reply for id %d", + reply->id); + return; + } + + if (reply->id != req->id) { + i_fatal("Master sent invalid id for reply " + "(got %d, expecting %d)", reply->id, req->id); + } + + req->callback(reply->result, req->user_data); + + requests = req->next; + if (requests == NULL) + next_request = &requests; + + i_free(req); +} + +void master_request_imap(int fd, int auth_process, const char *login_tag, + unsigned char cookie[AUTH_COOKIE_SIZE], + MasterCallback callback, void *user_data) +{ + MasterRequest req; + + i_assert(fd > 1); + + memset(&req, 0, sizeof(req)); + req.id = fd; + req.auth_process = auth_process; + memcpy(req.cookie, cookie, AUTH_COOKIE_SIZE); + + if (strlen(login_tag) >= sizeof(req.login_tag)) + strcpy(req.login_tag, "*"); + else + strcpy(req.login_tag, login_tag); + + if (fd_send(LOGIN_MASTER_SOCKET_FD, + fd, &req, sizeof(req)) != sizeof(req)) + i_fatal("fd_send() failed: %m"); + + push_request(req.id, callback, user_data); +} + +static void master_input(void *user_data __attr_unused__, int fd, + IO io __attr_unused__) +{ + int ret; + + ret = net_receive(fd, master_buf + master_pos, + sizeof(master_buf) - master_pos); + if (ret < 0) { + /* master died, kill ourself too */ + io_loop_stop(ioloop); + return; + } + + master_pos += ret; + if (master_pos < sizeof(master_buf)) + return; + + /* reply is now read */ + pop_request((MasterReply *) master_buf); + master_pos = 0; +} + +void master_init(void) +{ + requests = NULL; + next_request = &requests; + + master_pos = 0; + io_master = io_add(LOGIN_MASTER_SOCKET_FD, IO_READ, master_input, NULL); +} + +void master_deinit(void) +{ + WaitingRequest *next; + + while (requests != NULL) { + next = requests->next; + i_free(requests); + requests = next; + } + io_remove(io_master); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/master.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,16 @@ +#ifndef __MASTER_H +#define __MASTER_H + +#include "../master/master-interface.h" + +typedef void (*MasterCallback)(MasterReplyResult result, void *user_data); + +/* Request IMAP process for given cookie. */ +void master_request_imap(int fd, int auth_process, const char *login_tag, + unsigned char cookie[AUTH_COOKIE_SIZE], + MasterCallback callback, void *user_data); + +void master_init(void); +void master_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/ssl-proxy.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,334 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "ioloop.h" +#include "network.h" +#include "ssl-proxy.h" + +#ifdef HAVE_SSL + +#include <stdlib.h> +#include <gnutls/gnutls.h> + +typedef struct { + GNUTLS_STATE state; + int fd_ssl, fd_plain; + IO io_ssl, io_plain; + + unsigned char outbuf_plain[1024]; + unsigned int outbuf_pos_plain; + + unsigned int send_left_ssl, send_left_plain; +} SSLProxy; + +#define DH_BITS 1024 + +const int protocol_priority[] = + { GNUTLS_TLS1, GNUTLS_SSL3, 0 }; +const int kx_priority[] = + { GNUTLS_KX_RSA, GNUTLS_KX_DHE_RSA, 0 }; +const int cipher_priority[] = + { GNUTLS_CIPHER_RIJNDAEL_CBC, GNUTLS_CIPHER_3DES_CBC, 0 }; +const int comp_priority[] = + { GNUTLS_COMP_ZLIB, GNUTLS_COMP_NULL, 0 }; +const int mac_priority[] = + { GNUTLS_MAC_SHA, GNUTLS_MAC_MD5, 0 }; + +static GNUTLS_CERTIFICATE_SERVER_CREDENTIALS x509_cred; +static GNUTLS_DH_PARAMS dh_params; + +static void ssl_input(void *user_data, int handle, IO io); +static void plain_input(void *user_data, int handle, IO io); +static void ssl_proxy_destroy(SSLProxy *proxy); + +static int proxy_recv_ssl(SSLProxy *proxy, void *data, unsigned int size) +{ + int rcvd; + + rcvd = gnutls_record_recv(proxy->state, data, size); + if (rcvd > 0) + return rcvd; + + if (rcvd == 0) { + /* disconnected */ + ssl_proxy_destroy(proxy); + return -1; + } + + if (!gnutls_error_is_fatal(rcvd)) + return 0; + + /* fatal error occured */ + i_warning("Error reading from SSL client: %s", gnutls_strerror(rcvd)); + ssl_proxy_destroy(proxy); + return -1; +} + +static int proxy_send_ssl(SSLProxy *proxy, const void *data, unsigned int size) +{ + int sent; + + sent = gnutls_record_send(proxy->state, data, size); + if (sent >= 0) + return sent; + + if (!gnutls_error_is_fatal(sent)) + return 0; + + /* error occured */ + i_warning("Error sending to SSL client: %s", gnutls_strerror(sent)); + ssl_proxy_destroy(proxy); + return -1; +} + +static void ssl_proxy_destroy(SSLProxy *proxy) +{ + gnutls_deinit(proxy->state); + + (void)net_disconnect(proxy->fd_ssl); + (void)net_disconnect(proxy->fd_plain); + + io_remove(proxy->io_ssl); + io_remove(proxy->io_plain); + + i_free(proxy); +} + +static void ssl_output(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + SSLProxy *proxy = user_data; + int sent; + + sent = net_transmit(proxy->fd_plain, + proxy->outbuf_plain + proxy->outbuf_pos_plain, + proxy->send_left_plain); + if (sent < 0) { + /* disconnected */ + ssl_proxy_destroy(proxy); + return; + } + + proxy->send_left_plain -= sent; + proxy->outbuf_pos_plain += sent; + + if (proxy->send_left_plain > 0) + return; + + /* everything is sent, start reading again */ + io_remove(proxy->io_ssl); + proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy); +} + +static void ssl_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + SSLProxy *proxy = user_data; + int rcvd, sent; + + rcvd = proxy_recv_ssl(proxy, proxy->outbuf_plain, + sizeof(proxy->outbuf_plain)); + if (rcvd <= 0) + return; + + sent = net_transmit(proxy->fd_plain, proxy->outbuf_plain, + (unsigned int) rcvd); + if (sent == rcvd) + return; + + if (sent < 0) { + /* disconnected */ + ssl_proxy_destroy(proxy); + return; + } + + /* everything wasn't sent - don't read anything until we've + sent it all */ + proxy->outbuf_pos_plain = 0; + proxy->send_left_plain = rcvd - sent; + + io_remove(proxy->io_ssl); + proxy->io_ssl = io_add(proxy->fd_ssl, IO_WRITE, ssl_output, proxy); +} + +static void plain_output(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + SSLProxy *proxy = user_data; + int sent; + + /* FIXME: (void*) 1 is horrible kludge, but there's no need for us + to store the data as gnutls does it already, maybe it needes an + api change or some clarification how to do it better.. */ + sent = proxy_send_ssl(proxy, (void *) 1, proxy->send_left_ssl); + if (sent <= 0) + return; + + proxy->send_left_ssl -= sent; + if (proxy->send_left_ssl > 0) + return; + + /* everything is sent, start reading again */ + io_remove(proxy->io_plain); + proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy); +} + +static void plain_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + SSLProxy *proxy = user_data; + char buf[1024]; + int rcvd, sent; + + rcvd = net_receive(proxy->fd_plain, buf, sizeof(buf)); + if (rcvd < 0) { + /* disconnected */ + gnutls_bye(proxy->state, 1); + ssl_proxy_destroy(proxy); + return; + } + + sent = proxy_send_ssl(proxy, buf, (unsigned int) rcvd); + if (sent < 0 || sent == rcvd) + return; + + /* everything wasn't sent - don't read anything until we've + sent it all */ + proxy->send_left_ssl = rcvd - sent; + + io_remove(proxy->io_plain); + proxy->io_plain = io_add(proxy->fd_ssl, IO_WRITE, plain_output, proxy); +} + +static GNUTLS_STATE initialize_state(void) +{ + GNUTLS_STATE state; + + gnutls_init(&state, GNUTLS_SERVER); + + gnutls_protocol_set_priority(state, protocol_priority); + gnutls_cipher_set_priority(state, cipher_priority); + gnutls_compression_set_priority(state, comp_priority); + gnutls_kx_set_priority(state, kx_priority); + gnutls_mac_set_priority(state, mac_priority); + + gnutls_cred_set(state, GNUTLS_CRD_CERTIFICATE, x509_cred); + + /*gnutls_certificate_server_set_request(state, GNUTLS_CERT_REQUEST);*/ + + gnutls_dh_set_prime_bits(state, DH_BITS); + return state; +} + +int ssl_proxy_new(int fd) +{ + SSLProxy *proxy; + GNUTLS_STATE state; + int ret, sfd[2]; + + state = initialize_state(); + gnutls_transport_set_ptr(state, fd); + + net_set_nonblock(fd, FALSE); /* FIXME: blocks! */ + if ((ret = gnutls_handshake(state)) < 0) { + gnutls_deinit(state); + return -1; + } + net_set_nonblock(fd, TRUE); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfd) == -1) { + i_error("socketpair() failed: %m"); + gnutls_deinit(state); + return -1; + } + + proxy = i_new(SSLProxy, 1); + proxy->state = state; + proxy->fd_ssl = fd; + proxy->fd_plain = sfd[0]; + + proxy->io_ssl = io_add(proxy->fd_ssl, IO_READ, ssl_input, proxy); + proxy->io_plain = io_add(proxy->fd_plain, IO_READ, plain_input, proxy); + + return sfd[1]; +} + +static void generate_dh_primes(void) +{ + gnutls_datum prime, generator; + int ret; + + /* Generate Diffie Hellman parameters - for use with DHE + kx algorithms. These should be discarded and regenerated + once a day, once a week or once a month. Depends on the + security requirements. */ + if ((ret = gnutls_dh_params_init(&dh_params)) < 0) { + i_fatal("gnutls_dh_params_init() failed: %s", + gnutls_strerror(ret)); + } + + ret = gnutls_dh_params_generate(&prime, &generator, DH_BITS); + if (ret < 0) { + i_fatal("gnutls_dh_params_generate() failed: %s", + gnutls_strerror(ret)); + } + + ret = gnutls_dh_params_set(dh_params, prime, generator, DH_BITS); + if (ret < 0) { + i_fatal("gnutls_dh_params_set() failed: %s", + gnutls_strerror(ret)); + } + + free(prime.data); + free(generator.data); +} + +void ssl_proxy_init(void) +{ + const char *certfile, *keyfile; + int ret; + + certfile = getenv("SSL_CERT_FILE"); + keyfile = getenv("SSL_KEY_FILE"); + + if (certfile == NULL) + i_fatal("SSL_CERT_FILE environment not set"); + if (keyfile == NULL) + i_fatal("SSL_KEY_FILE environment not set"); + + if ((ret = gnutls_global_init() < 0)) { + i_fatal("gnu_tls_global_init() failed: %s", + gnutls_strerror(ret)); + } + + if ((ret = gnutls_certificate_allocate_cred(&x509_cred)) < 0) { + i_fatal("gnutls_certificate_allocate_cred() failed: %s", + gnutls_strerror(ret)); + } + + ret = gnutls_certificate_set_x509_key_file(x509_cred, certfile, keyfile, + GNUTLS_X509_FMT_PEM); + if (ret < 0) { + i_fatal("Can't load certificate files %s and %s: %s", + certfile, keyfile, gnutls_strerror(ret)); + } + + generate_dh_primes(); + gnutls_certificate_set_dh_params(x509_cred, dh_params); +} + +void ssl_proxy_deinit(void) +{ + gnutls_certificate_free_cred(x509_cred); + gnutls_global_deinit(); +} + +#else + +/* no SSL support */ + +int ssl_proxy_new(int fd) { return -1; } +void ssl_proxy_init(void) {} +void ssl_proxy_deinit(void) {} + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/login/ssl-proxy.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,12 @@ +#ifndef __SSL_PROXY_H +#define __SSL_PROXY_H + +/* establish SSL connection with the given fd, returns a new fd which you + must use from now on, or -1 if error occured. Unless -1 is returned, + the given fd must be simply forgotten. */ +int ssl_proxy_new(int fd); + +void ssl_proxy_init(void); +void ssl_proxy_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/.cvsignore Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,9 @@ +*.la +*.lo +*.o +.deps +.libs +Makefile +Makefile.in +so_locations +imap-master
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/Makefile.am Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,24 @@ +sbin_PROGRAMS = imap-master + +INCLUDES = \ + -I$(top_srcdir)/src/lib \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DPKG_RUNDIR=\""$(localstatedir)/run/$(PACKAGE)"\" \ + -DPKG_LIBDIR=\""$(pkglibdir)"\" + +imap_master_LDADD = \ + ../lib/liblib.a + +imap_master_SOURCES = \ + auth-process.c \ + imap-process.c \ + login-process.c \ + main.c \ + settings.c + +noinst_HEADERS = \ + auth-process.h \ + common.h \ + login-process.h \ + master-interface.h \ + settings.h
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/auth-process.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,329 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "network.h" +#include "iobuffer.h" +#include "restrict-access.h" +#include "auth-process.h" + +#include <stdlib.h> +#include <unistd.h> +#include <pwd.h> +#include <sys/stat.h> + +typedef struct _WaitingRequest WaitingRequest; + +struct _AuthProcess { + AuthProcess *next; + + char *name; + pid_t pid; + int fd; + IO io; + IOBuffer *outbuf; + + unsigned int reply_pos; + char reply_buf[sizeof(AuthCookieReplyData)]; + + WaitingRequest *requests, **next_request; +}; + +struct _WaitingRequest { + WaitingRequest *next; + int id; + + AuthCallback callback; + void *user_data; +}; + +static Timeout to; +static AuthProcess *processes; + +static void auth_process_destroy(AuthProcess *p); + +static void push_request(AuthProcess *process, int id, + AuthCallback callback, void *user_data) +{ + WaitingRequest *req; + + req = i_new(WaitingRequest, 1); + req->id = id; + req->callback = callback; + req->user_data = user_data; + + *process->next_request = req; + process->next_request = &req->next; +} + +static void pop_request(AuthProcess *process, AuthCookieReplyData *reply) +{ + WaitingRequest *req; + + req = process->requests; + if (req == NULL) { + i_warning("imap-auth %ld sent us unrequested reply for id %d", + (long)process->pid, reply->id); + return; + } + + if (reply->id != req->id) { + i_fatal("imap-auth %ld sent invalid id for reply " + "(got %d, expecting %d)", + (long)process->pid, reply->id, req->id); + } + + /* auth process isn't trusted, validate all data to make sure + it's not trying to exploit us */ + if (!VALIDATE_STR(reply->user) || !VALIDATE_STR(reply->mail) || + !VALIDATE_STR(reply->home)) { + i_error("auth: Received corrupted data"); + auth_process_destroy(process); + return; + } + + process->requests = req->next; + if (process->requests == NULL) + process->next_request = &process->requests; + + req->callback(reply, req->user_data); + + i_free(req); +} + +static void auth_process_input(void *user_data, int fd, IO io __attr_unused__) +{ + AuthProcess *p = user_data; + int ret; + + ret = net_receive(fd, p->reply_buf + p->reply_pos, + sizeof(p->reply_buf) - p->reply_pos); + if (ret < 0) { + /* disconnected */ + auth_process_destroy(p); + return; + } + + p->reply_pos += ret; + if (p->reply_pos < sizeof(p->reply_buf)) + return; + + /* reply is now read */ + pop_request(p, (AuthCookieReplyData *) p->reply_buf); + p->reply_pos = 0; +} + +static AuthProcess *auth_process_new(pid_t pid, int fd, const char *name) +{ + AuthProcess *p; + + PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_AUTH); + + p = i_new(AuthProcess, 1); + p->name = i_strdup(name); + p->pid = pid; + p->fd = fd; + p->io = io_add(fd, IO_READ, auth_process_input, p); + p->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT, + sizeof(AuthCookieRequestData)*100); + + p->next_request = &p->requests; + + p->next = processes; + processes = p; + return p; +} + +static void auth_process_destroy(AuthProcess *p) +{ + AuthProcess **pos; + WaitingRequest *next; + + for (pos = &processes; *pos != NULL; pos = &(*pos)->next) { + if (*pos == p) { + *pos = p->next; + break; + } + } + + for (; p->requests != NULL; p->requests = next) { + next = p->requests->next; + + p->requests->callback(NULL, p->requests->user_data); + i_free(p->requests); + } + + (void)unlink(t_strconcat(set_login_dir, "/", p->name, NULL)); + + io_buffer_destroy(p->outbuf); + io_remove(p->io); + (void)close(p->fd); + i_free(p->name); + i_free(p); +} + +static pid_t create_auth_process(AuthConfig *config) +{ + static const char *argv[] = { NULL, NULL }; + const char *path; + struct passwd *pwd; + pid_t pid; + int fd[2], listen_fd; + + if ((pwd = getpwnam(config->user)) == NULL) + i_fatal("Auth user doesn't exist: %s", config->user); + + /* create communication to process with a socket pair + FIXME: pipe() would work as well, would it be better? */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) { + i_error("socketpair() failed: %m"); + return -1; + } + + pid = fork(); + if (pid < 0) { + (void)close(fd[0]); + (void)close(fd[1]); + i_error("fork() failed: %m"); + return -1; + } + + if (pid != 0) { + /* master */ + auth_process_new(pid, fd[0], config->name); + (void)close(fd[1]); + return pid; + } + + /* create socket for listening auth requests from imap-login */ + path = t_strconcat(set_login_dir, "/", config->name, NULL); + (void)unlink(path); + (void)umask(0177); /* we want 0600 mode for the socket */ + listen_fd = net_listen_unix(path); + if (listen_fd == -1) + i_fatal("Can't listen in UNIX socket %s: %m", path); + + /* set correct permissions */ + (void)chown(path, set_login_uid, set_login_gid); + + /* move master communication handle to 0 */ + if (dup2(fd[1], 0) < 0) + i_fatal("login: dup2() failed: %m"); + + /* move login communication handle to 1 */ + if (dup2(listen_fd, 1) < 0) + i_fatal("login: dup2() failed: %m"); + + /* set /dev/null handle into 2, so if something is printed into + stderr it can't go anywhere where it could cause harm */ + if (dup2(null_fd, 2) < 0) + i_fatal("login: dup2() failed: %m"); + + (void)close(listen_fd); + + (void)close(fd[0]); + (void)close(fd[1]); + + clean_child_process(); + + /* setup access environment - needs to be done after + clean_child_process() since it clears environment */ + restrict_access_set_env(config->user, pwd->pw_uid, pwd->pw_gid, + config->chroot); + + /* set other environment */ + putenv((char *) t_strdup_printf("AUTH_PROCESS=%d", (int) getpid())); + putenv((char *) t_strconcat("METHODS=", config->methods, NULL)); + putenv((char *) t_strconcat("REALMS=", config->realms, NULL)); + putenv((char *) t_strconcat("USERINFO=", config->userinfo, NULL)); + putenv((char *) t_strconcat("USERINFO_ARGS=", config->userinfo_args, + NULL)); + /* hide the path, it's ugly */ + argv[0] = strrchr(config->executable, '/'); + if (argv[0] == NULL) argv[0] = config->executable; else argv[0]++; + + execv(config->executable, (char **) argv); + + i_fatal("execv(%s) failed: %m", argv[0]); + return -1; +} + +AuthProcess *auth_process_find(int id) +{ + AuthProcess *p; + + for (p = processes; p != NULL; p = p->next) { + if (p->pid == id) + return p; + } + + return NULL; +} + +void auth_process_request(AuthProcess *process, int id, + unsigned char cookie[AUTH_COOKIE_SIZE], + AuthCallback callback, void *user_data) +{ + AuthCookieRequestData req; + + req.id = id; + memcpy(req.cookie, cookie, AUTH_COOKIE_SIZE); + + if (io_buffer_send(process->outbuf, &req, sizeof(req)) < 0) + auth_process_destroy(process); + + push_request(process, id, callback, user_data); +} + +static int auth_process_get_count(const char *name) +{ + AuthProcess *p; + int count = 0; + + for (p = processes; p != NULL; p = p->next) { + if (strcmp(p->name, name) == 0) + count++; + } + + return count; +} + +void auth_processes_cleanup(void) +{ + AuthProcess *p; + + for (p = processes; p != NULL; p = p->next) + (void)close(p->fd); +} + +static void auth_processes_start_missing(void *user_data __attr_unused__, + Timeout timeout __attr_unused__) +{ + AuthConfig *config; + int count; + + config = auth_processes_config; + for (; config != NULL; config = config->next) { + count = auth_process_get_count(config->name); + for (; count < config->count; count++) + (void)create_auth_process(config); + } +} + +void auth_processes_init(void) +{ + processes = NULL; + to = timeout_add(1000, auth_processes_start_missing, NULL); +} + +void auth_processes_deinit(void) +{ + AuthProcess *next; + + timeout_remove(to); + + while (processes != NULL) { + next = processes->next; + auth_process_destroy(processes); + processes = next; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/auth-process.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,24 @@ +#ifndef __AUTH_PROCESS_H +#define __AUTH_PROCESS_H + +/* cookie_reply is NULL if some error occured */ +typedef void (*AuthCallback)(AuthCookieReplyData *cookie_reply, + void *user_data); + +typedef struct _AuthProcess AuthProcess; + +/* Find process for given id */ +AuthProcess *auth_process_find(int id); + +/* Request information about given cookie */ +void auth_process_request(AuthProcess *process, int id, + unsigned char cookie[AUTH_COOKIE_SIZE], + AuthCallback callback, void *user_data); + +/* Close any fds used by auth processes */ +void auth_processes_cleanup(void); + +void auth_processes_init(void); +void auth_processes_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/common.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,47 @@ +#ifndef __COMMON_H +#define __COMMON_H + +#include "lib.h" +#include "hash.h" +#include "settings.h" + +#include "../auth/auth-interface.h" +#include "master-interface.h" + +enum { + PROCESS_TYPE_UNKNOWN, + PROCESS_TYPE_AUTH, + PROCESS_TYPE_LOGIN, + PROCESS_TYPE_IMAP, + + PROCESS_TYPE_MAX +}; + +const char *process_type_names[PROCESS_TYPE_MAX]; + +extern HashTable *pids; +extern int null_fd, imap_fd, imaps_fd; + +/* processes */ +#define PID_GET_PROCESS_TYPE(pid) \ + POINTER_TO_INT(hash_lookup(pids, INT_TO_POINTER(pid))) + +#define PID_ADD_PROCESS_TYPE(pid, type) \ + hash_insert(pids, INT_TO_POINTER(pid), INT_TO_POINTER(type)) + +#define PID_REMOVE_PROCESS_TYPE(pid) \ + hash_remove(pids, INT_TO_POINTER(pid)) + +void clean_child_process(void); + +MasterReplyResult +create_imap_process(int socket, const char *user, uid_t uid, gid_t gid, + const char *home, int chroot, const char *env[]); +void imap_process_destroyed(pid_t pid); + +/* misc */ +#define VALIDATE_STR(str) \ + validate_str(str, sizeof(str)) +int validate_str(const char *str, int max_len); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/imap-process.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,148 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "restrict-access.h" + +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <syslog.h> +#include <grp.h> + +static unsigned int imap_process_count = 0; + +static int validate_uid_gid(uid_t uid, gid_t gid) +{ + if (uid == 0) { + i_error("imap process isn't allowed for root"); + return FALSE; + } + + if (uid != 0 && gid == 0) { + i_error("imap process isn't allowed to be in group 0"); + return FALSE; + } + + if (uid < set_first_valid_uid || (set_last_valid_uid != 0 && + uid > set_last_valid_uid)) { + i_error("imap process isn't allowed to use UID %ld", + (long) uid); + return FALSE; + } + + if (gid < set_first_valid_gid || (set_last_valid_gid != 0 && + gid > set_last_valid_gid)) { + i_error("imap process isn't allowed to use " + "GID %ld (UID is %ld)", (long) gid, (long) uid); + return FALSE; + } + + return TRUE; +} + +static int validate_chroot(const char *dir) +{ + char *const *chroot_dirs; + + if (*dir == '\0') + return TRUE; + + if (set_valid_chroot_dirs == '\0') + return FALSE; + + chroot_dirs = t_strsplit(set_valid_chroot_dirs, ":"); + while (*chroot_dirs != NULL) { + if (strncmp(dir, *chroot_dirs, strlen(*chroot_dirs)) == 0) + return TRUE; + chroot_dirs++; + } + + return FALSE; +} + +MasterReplyResult +create_imap_process(int socket, const char *user, uid_t uid, gid_t gid, + const char *home, int chroot, const char *env[]) +{ + static char *argv[] = { NULL, "-s", NULL }; + pid_t pid; + int i, j, err; + + if (imap_process_count == set_max_imap_processes) { + i_error("Maximum number of imap processes exceeded"); + return MASTER_RESULT_INTERNAL_FAILURE; + } + + if (!validate_uid_gid(uid, gid)) + return MASTER_RESULT_FAILURE; + + if (chroot && !validate_chroot(home)) + return MASTER_RESULT_FAILURE; + + pid = fork(); + if (pid < 0) { + i_error("fork() failed: %m"); + return MASTER_RESULT_INTERNAL_FAILURE; + } + + if (pid != 0) { + /* master */ + imap_process_count++; + PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_IMAP); + (void)close(socket); + return MASTER_RESULT_SUCCESS; + } + + clean_child_process(); + + /* move the imap socket into stdin, stdout and stderr fds */ + for (i = 0; i < 3; i++) { + if (dup2(socket, i) < 0) { + err = errno; + for (j = 0; j < i; j++) + (void)close(j); + (void)close(socket); + i_fatal("imap: dup2() failed: %m"); + } + } + (void)close(socket); + + /* setup environment */ + while (env[0] != NULL && env[1] != NULL) { + putenv((char *) t_strconcat(env[0], "=", env[1], NULL)); + env += 2; + } + + putenv((char *) t_strconcat("HOME=", home, NULL)); + + if (set_maildir_copy_with_hardlinks) + putenv("COPY_WITH_HARDLINKS=1"); + if (set_maildir_check_content_changes) + putenv("CHECK_CONTENT_CHANGES=1"); + if (umask(set_umask) != set_umask) + i_fatal("Invalid umask: %o", set_umask); + + /* setup access environment - needs to be done after + clean_child_process() since it clears environment */ + restrict_access_set_env(user, uid, gid, chroot ? home : NULL); + + /* hide the path, it's ugly */ + argv[0] = strrchr(set_imap_executable, '/'); + if (argv[0] == NULL) argv[0] = set_imap_executable; else argv[0]++; + + execv(set_imap_executable, argv); + err = errno; + + for (i = 0; i < 3; i++) + (void)close(i); + + i_fatal("execv(%s) failed: %m", set_imap_executable); + + /* not reached */ + return 0; +} + +void imap_process_destroyed(pid_t pid __attr_unused__) +{ + imap_process_count--; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/login-process.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,301 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "network.h" +#include "iobuffer.h" +#include "fdpass.h" +#include "restrict-access.h" +#include "login-process.h" +#include "auth-process.h" +#include "master-interface.h" + +#include <stdlib.h> +#include <unistd.h> +#include <syslog.h> + +typedef struct { + int refcount; + + pid_t pid; + int fd; + IO io; + IOBuffer *outbuf; + unsigned int destroyed:1; +} LoginProcess; + +typedef struct { + LoginProcess *process; + int login_id; + int auth_id; + int fd; + + char login_tag[LOGIN_TAG_SIZE]; +} LoginAuthRequest; + +static int auth_id_counter; +static Timeout to; +static HashTable *processes = NULL; + +static void login_process_destroy(LoginProcess *p); +static void login_process_unref(LoginProcess *p); + +static void auth_callback(AuthCookieReplyData *cookie_reply, void *user_data) +{ + const char *env[] = { + "MAIL", NULL, + "LOGIN_TAG", NULL, + NULL + }; + LoginAuthRequest *request = user_data; + LoginProcess *process; + MasterReply reply; + + env[1] = cookie_reply->mail; + env[3] = request->login_tag; + + if (cookie_reply == NULL || !cookie_reply->success) + reply.result = MASTER_RESULT_FAILURE; + else { + reply.result = create_imap_process(request->fd, + cookie_reply->user, + cookie_reply->uid, + cookie_reply->gid, + cookie_reply->home, + cookie_reply->chroot, env); + } + + /* reply to login */ + reply.id = request->login_id; + + process = request->process; + if (io_buffer_send(process->outbuf, &reply, sizeof(reply)) < 0) + login_process_destroy(process); + + (void)close(request->fd); + login_process_unref(process); + i_free(request); +} + +static void login_process_input(void *user_data, int fd __attr_unused__, + IO io __attr_unused__) +{ + LoginProcess *p = user_data; + AuthProcess *auth_process; + LoginAuthRequest *authreq; + MasterRequest req; + int client_fd, ret; + + ret = fd_read(p->fd, &req, sizeof(req), &client_fd); + if (ret != sizeof(req)) { + if (ret == 0) { + /* disconnected, ie. the login process died */ + } else if (ret > 0) { + /* req wasn't fully read */ + i_error("login: fd_read() couldn't read all req"); + } else { + i_error("login: fd_read() failed: %m"); + } + + login_process_destroy(p); + return; + } + + /* login process isn't trusted, validate all data to make sure + it's not trying to exploit us */ + if (!VALIDATE_STR(req.login_tag)) { + i_error("login: Received corrupted data"); + login_process_destroy(p); + return; + } + + /* ask the cookie from the auth process */ + authreq = i_new(LoginAuthRequest, 1); + p->refcount++; + authreq->process = p; + authreq->login_id = req.id; + authreq->auth_id = ++auth_id_counter; + authreq->fd = client_fd; + strcpy(authreq->login_tag, req.login_tag); + + auth_process = auth_process_find(req.auth_process); + if (auth_process == NULL) { + i_error("login: Authentication process %u doesn't exist", + req.auth_process); + auth_callback(NULL, &authreq); + } else { + auth_process_request(auth_process, authreq->auth_id, req.cookie, + auth_callback, authreq); + } +} + +static LoginProcess *login_process_new(pid_t pid, int fd) +{ + LoginProcess *p; + + PID_ADD_PROCESS_TYPE(pid, PROCESS_TYPE_LOGIN); + + p = i_new(LoginProcess, 1); + p->refcount = 1; + p->pid = pid; + p->fd = fd; + p->io = io_add(fd, IO_READ, login_process_input, p); + p->outbuf = io_buffer_create(fd, default_pool, IO_PRIORITY_DEFAULT, + sizeof(MasterReply)*10); + + hash_insert(processes, INT_TO_POINTER(pid), p); + return p; +} + +static void login_process_destroy(LoginProcess *p) +{ + if (p->destroyed) + return; + p->destroyed = TRUE; + + io_buffer_close(p->outbuf); + io_remove(p->io); + (void)close(p->fd); + + hash_remove(processes, INT_TO_POINTER(p->pid)); + login_process_unref(p); +} + +static void login_process_unref(LoginProcess *p) +{ + if (--p->refcount > 0) + return; + + io_buffer_destroy(p->outbuf); + i_free(p); +} + +static pid_t create_login_process(void) +{ + static const char *argv[] = { NULL, NULL }; + pid_t pid; + int fd[2]; + + if (set_login_uid == 0) + i_fatal("Login process must not run as root"); + + /* create communication to process with a socket pair */ + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) == -1) { + i_error("socketpair() failed: %m"); + return -1; + } + + pid = fork(); + if (pid < 0) { + (void)close(fd[0]); + (void)close(fd[1]); + i_error("fork() failed: %m"); + return -1; + } + + if (pid != 0) { + /* master */ + login_process_new(pid, fd[0]); + (void)close(fd[1]); + return pid; + } + + /* move communication handle */ + if (dup2(fd[1], LOGIN_MASTER_SOCKET_FD) < 0) + i_fatal("login: dup2() failed: %m"); + + /* move the listen handle */ + if (dup2(imap_fd, LOGIN_IMAP_LISTEN_FD) < 0) + i_fatal("login: dup2() failed: %m"); + + /* move the SSL listen handle */ + if (dup2(imaps_fd, LOGIN_IMAPS_LISTEN_FD) < 0) + i_fatal("login: dup2() failed: %m"); + + /* imap_fd and imaps_fd are closed by clean_child_process() */ + + (void)close(fd[0]); + (void)close(fd[1]); + + clean_child_process(); + + /* setup access environment - needs to be done after + clean_child_process() since it clears environment */ + restrict_access_set_env(set_login_user, set_login_uid, set_login_gid, + set_login_chroot ? set_login_dir : NULL); + + if (!set_login_chroot) { + /* no chrooting, but still change to the directory */ + if (chdir(set_login_dir) < 0) { + i_fatal("chdir(%s) failed: %m", + set_login_dir); + } + } + + if (set_ssl_cert_file != NULL) { + putenv((char *) t_strconcat("SSL_CERT_FILE=", + set_ssl_cert_file, NULL)); + } + + if (set_ssl_key_file != NULL) { + putenv((char *) t_strconcat("SSL_KEY_FILE=", + set_ssl_key_file, NULL)); + } + + if (set_disable_plaintext_auth) + putenv("DISABLE_PLAINTEXT_AUTH=1"); + + putenv((char *) t_strdup_printf("MAX_LOGGING_USERS=%d", + set_max_logging_users)); + + /* hide the path, it's ugly */ + argv[0] = strrchr(set_login_executable, '/'); + if (argv[0] == NULL) argv[0] = set_login_executable; else argv[0]++; + + execv(set_login_executable, (char **) argv); + + i_fatal("execv(%s) failed: %m", argv[0]); + return -1; +} + +static void login_hash_cleanup(void *key __attr_unused__, void *value, + void *user_data __attr_unused__) +{ + LoginProcess *p = value; + + (void)close(p->fd); +} + +void login_processes_cleanup(void) +{ + hash_foreach(processes, login_hash_cleanup, NULL); +} + +static void login_processes_start_missing(void *user_data __attr_unused__, + Timeout timeout __attr_unused__) +{ + /* create max. one process every second, that way if it keeps + dying all the time we don't eat all cpu with fork()ing. */ + if (hash_size(processes) < set_login_processes_count) + (void)create_login_process(); +} + +void login_processes_init(void) +{ + auth_id_counter = 0; + processes = hash_create(default_pool, 128, NULL, NULL); + to = timeout_add(1000, login_processes_start_missing, NULL); +} + +static void login_hash_destroy(void *key __attr_unused__, void *value, + void *user_data __attr_unused__) +{ + login_process_destroy(value); +} + +void login_processes_deinit(void) +{ + timeout_remove(to); + + hash_foreach(processes, login_hash_destroy, NULL); + hash_destroy(processes); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/login-process.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,9 @@ +#ifndef __CHILD_LOGIN_H +#define __CHILD_LOGIN_H + +void login_processes_cleanup(void); + +void login_processes_init(void); +void login_processes_deinit(void); + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/main.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,258 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "common.h" +#include "ioloop.h" +#include "lib-signals.h" +#include "network.h" + +#include "auth-process.h" +#include "login-process.h" + +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <syslog.h> +#include <sys/stat.h> +#include <sys/wait.h> + +const char *process_names[PROCESS_TYPE_MAX] = { + "unknown", + "auth", + "login", + "imap" +}; + +static const char *logfile = NULL; +static IOLoop ioloop; +static Timeout to_children; + +HashTable *pids; +int null_fd, imap_fd, imaps_fd; + +int validate_str(const char *str, int max_len) +{ + int i; + + for (i = 0; i < max_len; i++) { + if (str[i] == '\0') + return TRUE; + } + + return FALSE; +} + +void clean_child_process(void) +{ + extern char **environ; + + /* remove all environment, we don't need them */ + if (environ != NULL) + *environ = NULL; + + /* set the failure log */ + if (logfile != NULL) + putenv((char *) t_strconcat("IMAP_LOGFILE=", logfile, NULL)); + + (void)close(null_fd); + (void)close(imap_fd); + (void)close(imaps_fd); + + /* close fds for auth/login processes */ + login_processes_cleanup(); + auth_processes_cleanup(); + + closelog(); +} + +static void sig_quit(int signo __attr_unused__) +{ + io_loop_stop(ioloop); +} + +static void children_check_timeout(void *user_data __attr_unused__, + Timeout timeout __attr_unused__) +{ + const char *process_type_name; + pid_t pid; + int status, process_type; + + while ((pid = waitpid(-1, &status, WNOHANG)) > 0) { + /* get the type and remove from hash */ + process_type = PID_GET_PROCESS_TYPE(pid); + PID_REMOVE_PROCESS_TYPE(pid); + + if (process_type == PROCESS_TYPE_IMAP) + imap_process_destroyed(pid); + + /* write errors to syslog */ + process_type_name = process_names[process_type]; + if (WIFEXITED(status)) { + status = WEXITSTATUS(status); + if (status != 0) { + i_error("child %d (%s) returned error %d", + (int)pid, process_type_name, status); + } + } else if (WIFSIGNALED(status)) { + i_error("child %d (%s) killed with signal %d", + (int)pid, process_type_name, WTERMSIG(status)); + } + } + + if (pid == -1 && errno != EINTR && errno != ECHILD) + i_warning("waitpid() failed: %m"); +} + +static IPADDR *resolve_ip(const char *name) +{ + IPADDR *ip; + int ret, ips_count; + + if (set_imap_listen == NULL || *set_imap_listen == '\0') + return NULL; + + ret = net_gethostbyname(name, &ip, &ips_count); + if (ret != 0) + i_fatal("Can't resolve address: %s", set_imap_listen); + + if (ips_count < 1) + i_fatal("No IPs for address: %s", set_imap_listen); + + return ip; +} + +static void open_fds(void) +{ + IPADDR *imap_ip, *imaps_ip; + + imap_ip = resolve_ip(set_imap_listen); + imaps_ip = resolve_ip(set_imaps_listen); + + if (imaps_ip == NULL && set_imaps_listen == NULL) + imaps_ip = imap_ip; + + null_fd = open("/dev/null", O_RDONLY); + if (null_fd == -1) + i_fatal("Can't open /dev/null: %m"); + + imap_fd = set_imap_port == 0 ? dup(null_fd) : + net_listen(imap_ip, &set_imap_port); + if (imap_fd == -1) { + i_fatal("listen(%d) failed: %ms", set_imap_port); + } + + imaps_fd = set_ssl_cert_file == NULL || set_ssl_key_file == NULL || + set_imaps_port == 0 ? dup(null_fd) : + net_listen(imaps_ip, &set_imaps_port); + if (imaps_fd == -1) { + i_fatal("listen(%d) failed: %m", set_imaps_port); + } +} + +static void main_init(const char *logfile) +{ + lib_init_signals(sig_quit); + + /* deny file access from everyone else except owner */ + (void)umask(0077); + + if (logfile == NULL) + logfile = getenv("IMAP_LOGFILE"); + + if (logfile == NULL) { + /* open the syslog immediately so chroot() won't + break logging */ + openlog("imap-master", LOG_NDELAY, LOG_MAIL); + + i_set_panic_handler(i_syslog_panic_handler); + i_set_fatal_handler(i_syslog_fatal_handler); + i_set_error_handler(i_syslog_error_handler); + i_set_warning_handler(i_syslog_warning_handler); + } else { + /* log failures into specified log file */ + i_set_failure_file(logfile, "imap-master"); + i_set_failure_timestamp_format(DEFAULT_FAILURE_STAMP_FORMAT); + } + + pids = hash_create(default_pool, 128, NULL, NULL); + to_children = timeout_add(100, children_check_timeout, NULL); + + auth_processes_init(); + login_processes_init(); +} + +static void main_deinit(void) +{ + if (lib_signal_kill != 0) + i_warning("Killed with signal %d", lib_signal_kill); + + login_processes_deinit(); + auth_processes_deinit(); + + timeout_remove(to_children); + + (void)close(null_fd); + (void)close(imap_fd); + (void)close(imaps_fd); + + hash_destroy(pids); + closelog(); +} + +static void daemonize(void) +{ + pid_t pid; + + pid = fork(); + if (pid < 0) + i_fatal("fork() failed: %m"); + + if (pid != 0) + _exit(0); +} + +int main(int argc, char *argv[]) +{ + /* parse arguments */ + const char *configfile = SYSCONFDIR "/" PACKAGE ".conf"; + int foreground = FALSE; + int i; + + lib_init(); + + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-F") == 0) { + /* foreground */ + foreground = TRUE; + } else if (strcmp(argv[i], "-c") == 0) { + /* config file */ + i++; + if (i == argc) i_fatal("Missing config file argument"); + configfile = argv[i]; + } else if (strcmp(argv[i], "-l") == 0) { + /* log file */ + i++; + if (i == argc) i_fatal("Missing log file argument"); + logfile = argv[i]; + } else { + i_fatal("Unknown argument: %s", argv[1]); + } + } + + /* read and verify settings before forking */ + settings_read(configfile); + open_fds(); + + if (!foreground) + daemonize(); + + ioloop = io_loop_create(); + + main_init(logfile); + io_loop_run(ioloop); + main_deinit(); + + io_loop_destroy(ioloop); + lib_deinit(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/master-interface.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,32 @@ +#ifndef __MASTER_INTERFACE_H +#define __MASTER_INTERFACE_H + +#include "../auth/auth-interface.h" + +#define LOGIN_TAG_SIZE 32 + +#define LOGIN_MASTER_SOCKET_FD 0 +#define LOGIN_IMAP_LISTEN_FD 1 +#define LOGIN_IMAPS_LISTEN_FD 2 + +typedef enum { + MASTER_RESULT_INTERNAL_FAILURE, + MASTER_RESULT_SUCCESS, + MASTER_RESULT_FAILURE +} MasterReplyResult; + +typedef struct { + int id; + + int auth_process; + unsigned char cookie[AUTH_COOKIE_SIZE]; + + char login_tag[LOGIN_TAG_SIZE]; +} MasterRequest; + +typedef struct { + int id; + MasterReplyResult result; +} MasterReply; + +#endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/settings.c Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,335 @@ +/* Copyright (C) 2002 Timo Sirainen */ + +#include "lib.h" +#include "iobuffer.h" +#include "settings.h" + +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <pwd.h> +#include <sys/stat.h> + +typedef enum { + SET_STR, + SET_INT, + SET_BOOL +} SettingType; + +typedef struct { + const char *name; + SettingType type; + void *ptr; +} Setting; + +static Setting settings[] = { + { "login_executable", SET_STR, &set_login_executable }, + { "login_user", SET_STR, &set_login_user }, + { "login_dir", SET_STR, &set_login_dir }, + { "login_chroot", SET_BOOL,&set_login_chroot }, + { "login_processes_count", SET_INT, &set_login_processes_count }, + + { "max_logging_users", SET_INT, &set_max_logging_users }, + { "imap_executable", SET_STR, &set_imap_executable }, + { "valid_chroot_dirs", SET_STR, &set_valid_chroot_dirs }, + { "max_imap_processes", SET_INT, &set_max_imap_processes }, + { "imap_listen", SET_STR, &set_imap_listen }, + { "imaps_listen", SET_STR, &set_imaps_listen }, + { "imap_port", SET_INT, &set_imap_port }, + { "imaps_port", SET_INT, &set_imaps_port }, + { "ssl_cert_file", SET_STR, &set_ssl_cert_file }, + { "ssl_key_file", SET_STR, &set_ssl_key_file }, + { "disable_plaintext_auth", + SET_BOOL,&set_disable_plaintext_auth }, + { "first_valid_uid", SET_INT, &set_first_valid_uid }, + { "last_valid_uid", SET_INT, &set_last_valid_uid }, + { "first_valid_gid", SET_INT, &set_first_valid_gid }, + { "last_valid_gid", SET_INT, &set_last_valid_gid }, + { "maildir_copy_with_hardlinks", + SET_BOOL,&set_maildir_copy_with_hardlinks }, + { "maildir_check_content_changes", + SET_BOOL,&set_maildir_check_content_changes }, + { "umask", SET_INT, &set_umask }, + + { NULL, 0, NULL } +}; + +/* login */ +char *set_login_executable = PKG_LIBDIR "/imap-login"; +char *set_login_user = "imapd"; +char *set_login_dir = PKG_RUNDIR; + +int set_login_chroot = TRUE; +unsigned int set_login_processes_count = 1; +unsigned int set_max_logging_users = 256; + +uid_t set_login_uid; /* generated from set_login_user */ +gid_t set_login_gid; /* generated from set_login_user */ + +/* imap */ +char *set_imap_executable = PKG_LIBDIR "/imap"; +char *set_valid_chroot_dirs = NULL; +unsigned int set_max_imap_processes = 1024; + +char *set_imap_listen = NULL; +char *set_imaps_listen = NULL; +unsigned int set_imap_port = 143; +unsigned int set_imaps_port = 993; + +char *set_ssl_cert_file = NULL; +char *set_ssl_key_file = NULL; +int set_disable_plaintext_auth = FALSE; + +unsigned int set_first_valid_uid = 500, set_last_valid_uid = 0; +unsigned int set_first_valid_gid = 1, set_last_valid_gid = 0; + +int set_maildir_copy_with_hardlinks = FALSE; +int set_maildir_check_content_changes = FALSE; +unsigned int set_umask = 0077; + +/* auth */ +AuthConfig *auth_processes_config = NULL; + +static void get_login_uid(void) +{ + struct passwd *pw; + + if ((pw = getpwnam(set_login_user)) == NULL) + i_fatal("Login user doesn't exist: %s", set_login_user); + + set_login_uid = pw->pw_uid; + set_login_gid = pw->pw_gid; +} + +static void settings_initialize(void) +{ + Setting *set; + + /* strdup() all default settings */ + for (set = settings; set->name != NULL; set++) { + if (set->type == SET_STR) { + char **str = set->ptr; + *str = i_strdup(*str); + } + } +} + +static void settings_verify(void) +{ + get_login_uid(); + + if (access(set_login_executable, X_OK) == -1) { + i_fatal("Can't use login executable %s: %m", + set_login_executable); + } + + if (access(set_imap_executable, X_OK) == -1) { + i_fatal("Can't use imap executable %s: %m", + set_imap_executable); + } + + /* since it's under /var/run by default, it may have been deleted */ + if (mkdir(set_login_dir, 0700) == 0) + (void)chown(set_login_dir, set_login_uid, set_login_gid); + if (access(set_login_dir, X_OK) == -1) + i_fatal("Can't access login directory %s: %m", set_login_dir); + + if (set_login_processes_count < 1) + i_fatal("login_processes_count must be at least 1"); + if (set_first_valid_uid < set_last_valid_uid) + i_fatal("first_valid_uid can't be larger than last_valid_uid"); + if (set_first_valid_gid < set_last_valid_gid) + i_fatal("first_valid_gid can't be larger than last_valid_gid"); +} + +static AuthConfig *auth_config_new(const char *name) +{ + AuthConfig *auth; + + auth = i_new(AuthConfig, 1); + auth->name = i_strdup(name); + auth->executable = i_strdup(PKG_LIBDIR "/imap-auth"); + auth->count = 1; + + auth->next = auth_processes_config; + auth_processes_config = auth; + return auth; +} + +static const char *parse_new_auth(const char *name) +{ + AuthConfig *auth; + + for (auth = auth_processes_config; auth != NULL; auth = auth->next) { + if (strcmp(auth->name, name) == 0) { + return "Authentication process already exists " + "with the same name"; + } + } + + (void)auth_config_new(name); + return NULL; +} + +static const char *parse_auth(const char *key, const char *value) +{ + AuthConfig *auth = auth_processes_config; + const char *p; + char **ptr; + + if (auth == NULL) + return "Authentication process name not defined yet"; + + /* check the easy string values first */ + if (strcmp(key, "auth_methods") == 0) + ptr = &auth->methods; + else if (strcmp(key, "auth_realms") == 0) + ptr = &auth->realms; + else if (strcmp(key, "auth_executable") == 0) + ptr = &auth->executable; + else if (strcmp(key, "auth_user") == 0) + ptr = &auth->user; + else if (strcmp(key, "auth_chroot") == 0) + ptr = &auth->chroot; + else + ptr = NULL; + + if (ptr != NULL) { + i_strdup_replace(ptr, value); + return NULL; + } + + if (strcmp(key, "auth_userinfo") == 0) { + /* split it into userinfo + userinfo_args */ + for (p = value; *p != ' ' && *p != '\0'; ) + p++; + + i_free(auth->userinfo); + auth->userinfo = i_strndup(value, (unsigned int) (p-value)); + + while (*p == ' ') p++; + + i_free(auth->userinfo_args); + auth->userinfo_args = i_strdup(p); + return NULL; + } + + if (strcmp(key, "auth_count") == 0) { + if (!sscanf(value, "%i", &auth->count)) + return t_strconcat("Invalid number: ", value, NULL); + return NULL; + } + + + return t_strconcat("Unknown setting: ", key, NULL); +} + +static const char *parse_setting(const char *key, const char *value) +{ + Setting *set; + + if (strcmp(key, "auth") == 0) + return parse_new_auth(value); + if (strncmp(key, "auth_", 5) == 0) + return parse_auth(key, value); + + for (set = settings; set->name != NULL; set++) { + if (strcmp(set->name, key) == 0) { + switch (set->type) { + case SET_STR: + i_strdup_replace((char **) set->ptr, value); + break; + case SET_INT: + /* use %i so we can handle eg. 0600 + as octal value with umasks */ + if (!sscanf(value, "%i", (int *) set->ptr)) + return t_strconcat("Invalid number: ", + value, NULL); + break; + case SET_BOOL: + if (strcasecmp(value, "yes") == 0) + *((int *) set->ptr) = TRUE; + else if (strcasecmp(value, "no") == 0) + *((int *) set->ptr) = FALSE; + else + return t_strconcat("Invalid boolean: ", + value, NULL); + break; + } + return NULL; + } + } + + return t_strconcat("Unknown setting: ", key, NULL); +} + +#define IS_WHITE(c) ((c) == ' ' || (c) == '\t') + +void settings_read(const char *path) +{ + IOBuffer *inbuf; + const char *errormsg; + char *line, *key, *p; + int fd, linenum; + + settings_initialize(); + + fd = open(path, O_RDONLY); + if (fd == -1) + i_fatal("Can't open configuration file %s: %m", path); + + linenum = 0; + inbuf = io_buffer_create_file(fd, default_pool, 2048); + for (;;) { + line = io_buffer_next_line(inbuf); + if (line == NULL) { + if (io_buffer_read(inbuf) <= 0) + break; + continue; + } + linenum++; + + /* skip whitespace */ + while (IS_WHITE(*line)) + line++; + + /* ignore comments or empty lines */ + if (*line == '#' || *line == '\0') + continue; + + /* all lines must be in format "key = value" */ + key = line; + while (!IS_WHITE(*line) && *line != '\0') + line++; + if (IS_WHITE(*line)) { + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + } + + if (*line != '=') { + errormsg = "Missing value"; + } else { + /* skip whitespace after '=' */ + *line++ = '\0'; + while (IS_WHITE(*line)) line++; + + /* skip trailing whitespace */ + p = line + strlen(line); + while (p > line && IS_WHITE(p[-1])) + p--; + *p = '\0'; + + errormsg = parse_setting(key, line); + } + + if (errormsg != NULL) { + i_fatal("Error in configuration file %s line %d: %s", + path, linenum, errormsg); + } + }; + + io_buffer_destroy(inbuf); + (void)close(fd); + + settings_verify(); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/master/settings.h Fri Aug 09 12:15:38 2002 +0300 @@ -0,0 +1,57 @@ +#ifndef __SETTINGS_H +#define __SETTINGS_H + +/* login */ +extern char *set_login_executable; +extern char *set_login_user; +extern char *set_login_dir; +extern int set_login_chroot; +extern unsigned int set_login_processes_count; +extern unsigned int set_max_logging_users; + +extern uid_t set_login_uid; +extern gid_t set_login_gid; + +/* imap */ +extern char *set_imap_executable; +extern char *set_valid_chroot_dirs; +extern unsigned int set_max_imap_processes; + +extern char *set_imap_listen; +extern char *set_imaps_listen; +extern unsigned int set_imap_port; +extern unsigned int set_imaps_port; + +extern char *set_ssl_cert_file; +extern char *set_ssl_key_file; +extern int set_disable_plaintext_auth; + +extern unsigned int set_first_valid_uid, set_last_valid_uid; +extern unsigned int set_first_valid_gid, set_last_valid_gid; + +extern int set_maildir_copy_with_hardlinks; +extern int set_maildir_check_content_changes; +extern unsigned int set_umask; + +/* auth */ +typedef struct _AuthConfig AuthConfig; + +struct _AuthConfig { + AuthConfig *next; + + char *name; + char *methods; + char *realms; + char *userinfo, *userinfo_args; + char *executable; + char *user; + char *chroot; + + int count; +}; + +extern AuthConfig *auth_processes_config; + +void settings_read(const char *path); + +#endif