diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 |
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 |
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /imap | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2 | |
Initial checkin
Imported from subversion report, converted to git. Updated all paths in
scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'imap')
71 files changed, 37546 insertions, 0 deletions
diff --git a/imap/.gitignore b/imap/.gitignore new file mode 100644 index 0000000..4b8bc07 --- /dev/null +++ b/imap/.gitignore @@ -0,0 +1,37 @@ +/BUGS +/README +/README.proxy +/README.proxy.html +/courierpop3d.8 +/courierpop3d.8.in +/courierpop3d.html +/courierpop3d.html.in +/imapd +/imapd-ssl.dist +/imapd.8 +/imapd.8.in +/imapd.cnf +/imapd.cnf.openssl +/imapd.dist +/imapd.html +/imapd.html.in +/imapd.pam +/imaplogin +/mkimapdcert +/mkimapdcert.8 +/mkimapdcert.8.in +/mkimapdcert.html +/mkimapdcert.html.in +/mkpop3dcert +/mkpop3dcert.8 +/mkpop3dcert.8.in +/mkpop3dcert.html +/mkpop3dcert.html.in +/pop3d +/pop3d-ssl.dist +/pop3d.cnf +/pop3d.cnf.openssl +/pop3d.dist +/pop3d.pam +/pop3login +/testsuitefix.pl diff --git a/imap/BUGS.html b/imap/BUGS.html new file mode 100644 index 0000000..62758dd --- /dev/null +++ b/imap/BUGS.html @@ -0,0 +1,158 @@ +<?xml version="1.0" encoding="iso-8859-1"?> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> + <title>Courier-IMAP: BUGS</title> +</head> + +<body> +<p>This is not a list of Courier-IMAP bugs, rather this is a list of bugs in +either the IMAP4REV1 RFC, RFC2060, or various IMAP clients. Whether it's the +RFC that's broken, or the various IMAP clients, is not always clear.</p> + +<h2>Pine</h2> +<ol> + <li>Pine chokes on whitespace between BODY and [</li> + <li>Pine's implementation of the Drafts folder is buggy, and prevents + compatibility with SqWebmail's Drafts folder, which is a permanent + folder. Pine expects to be able to delete and create Drafts at will. You + can go into Pine's setup, and change postponed-msgs to Drafts, but then + if you use Pine and sqwebmail concurrently, SqWebmail will complain about + Pine's deleting the Drafts folder from it. Pine should NOT delete the + drafts folder when it is empty, and should not prompt someone to open a + postponed message if the postponed messages folder is empty.</li> + <li>Occasionally Pine sends a FETCH request with an invalid UID. This + usually happens after you resume a postponed message, and send it. It + looks like other IMAP servers simply ignore this error condition, however + Courier-IMAP will return an error message, which Pine shows briefly on + the status line. This is similar to the Netscape Communicator bug (see + below), but not as bad.</li> + <li>Pine fails to quote certain special characters in the login userid and + password.</li> + <li>The CHILDREN draft extension breaks Pine at least up to 4.21. It is + necessary to set IMAP_OBSOLETE_CLIENT flag in the configuration file to + make Pine work. The problem happens when a message gets sent, when Pine + sends the following to the server: + <pre>READ: ATOM: 0000000a +READ: ATOM: LIST +READ: ATOM: INBOX. +READ: ATOM: sent-mail +READ: EOL +WRITE: * LIST (\HasNoChildren) "." "INBOX.sent-mail"^M +0000000a OK LIST completed^M + +READ: ATOM: 0000000b +READ: ATOM: CREATE +READ: ATOM: INBOX.sent-mail +READ: EOL +WRITE: 0000000b NO Cannot create this folder.^M</pre> + <p>Basically Pine is checking if the sent-mail folder exists. It sends + the command "(tag) LIST INBOX. sent-mail". Apparently the \HasNoChildren + reply causes Pine to toss its cookies, think that the folder does not + exist, and attempt to create it, which obviously fails.</p> + <p>Previous versions of Courier-IMAP used the \NoInferiors tag instead of + \HasNoChildren. Pine appears to handle that just fine:</p> + <pre>READ: ATOM: 0000000b +READ: ATOM: LIST +READ: ATOM: INBOX. +READ: ATOM: sent-mail +READ: EOL +WRITE: * LIST (\Noinferiors) "." "INBOX.sent-mail"^M +0000000b OK LIST completed^M + +READ: ATOM: 0000000c +READ: ATOM: APPEND +READ: ATOM: INBOX.sent-mail +WRITE: + OK^M</pre> + <p>\NoInferiors is technically a wrong flag to use here, but that's what, + apparently is needed to keep Pine happy. Set the IMAP_OBSOLETE_CLIENT + flag to get this legacy behavior.</p> + </li> + <li>Pine sends a non-empty reference in the LIST command, specifying the + folder directory. RFC 2060 clearly indicates that a non-empty reference + is non-standard behavior: + + <blockquote> + A non-empty reference name argument is the name of a mailbox or a level + of mailbox hierarchy, and indicates a context in which the mailbox name + is interpreted in an implementation-defined manner.</blockquote> + <p>This is another Pine bug.</p> + </li> +</ol> + +<h2>Netscape Communicator</h2> + +<p>Netscape Communicator is ancient history. Upgrade to Mozilla, or +Thunderbird.</p> + +<h2>RFC 2060</h2> + +<p>The IMAP4REV1 RFC is not clear as to what LIST should return for a mailbox +that can contain both messages and other mailboxes. The RFC strongly implies +that the response should be one LIST reply without \Noinferiors and \Noselect +tags.</p> + +<p>However, Pine does not appear to gracefully handle that. It does handle +the navigation part more or less reasonably. The mailbox name is shown with +the hierarchy delimiter inside brackets. Enter opens the contents of the +mailbox, the > key displays the subfolder collection.</p> + +<p>However, when copying the message there does not appear to be a way to +select the folder itself. Only the Enter key responds, which shows the +subfolder collection.</p> + +<p>Pine behaves better if this situation is handled by returning two replies +with the same mailbox name. One reply contains the \Noinferiors tag, the +second reply contains the \Noselect tag. Pine will display the folder twice +in the folder collections window. The first entry is displayed like any +other folder, and pressing Enter selects the folder. The second entry is +displayed with the hierarchy separator appended to it, and pressing Enter +shows the subfolder collection.</p> + +<h2>Outlook Express</h2> + +<p>Even when you tell it not to use "subscriptions", Outlook Express still +sends spurious Subscribe/Unsubscribe commands, which forced me to implement a +stub for those function.</p> + +<p>OE also persistent problems updating the number of unread messages in each +folder, or visually highlighting new messages in the folder. Perhaps I'm not +familiar with OE. It's possible that there might be a bug in Courier-IMAP +too, but neither Pine, nor Communicator, exhibit these symptoms.</p> + +<p>There have been reports that OE 5.0 does not work at all due to the fact +that the silly thing expects to see UID as the first parameter in the +response to a FETCH.</p> + +<p>No, I am not going to add spaghetti code to push UID to the front of the +structured response to a FETCH. Ask Microsoft to actually read the RFCs +before implementing them.</p> + +<h2>StarOffice 5.1a and others</h2> + +<p>StarOffice's IMAP server doesn't have a good grasp on what IMAP responses +should be like. For some silly reason, it insists on a period at the end of +a freeform message. Nothing even close is required by RFC 2060, and I'm not +going to change Courier-IMAP in order to accomodate such silliness. If you +need to use StarOffice's IMAP client, try the following patch:</p> +<pre>--- imapd.c.orig Fri Jan 7 02:47:27 2000 ++++ imapd.c Sun Jan 16 22:31:16 2000 +@@ -1131,7 +1131,7 @@ + writes(tag); + writes(" OK "); + writes(cmdbuf); +- writes(" completed\r\n"); ++ writes(" completed.\r\n"); + } + writeflush(); + return (0);</pre> +<hr /> + +<p>Pine is a trademark of the University Of Washington</p> + +<p>Outlook Express is a trademark of the Microsoft Corporation</p> + +<p>Staroffice is a trademark of Sun Microsystems</p> +<hr /> +</body> +</html> diff --git a/imap/ChangeLog b/imap/ChangeLog new file mode 100644 index 0000000..5d21ca2 --- /dev/null +++ b/imap/ChangeLog @@ -0,0 +1,3702 @@ +2013-07-04 Sam Varshavchik <mrsam@courier-mta.com> + + * Autotool chain update. + + * imapd.c (mddelete): Reject DELETE folder if the maildir is a symlink. + +2013-04-27 Rolf Eike Beer <dakon@users.sf.net> + + * imapd.c (doId): Implement the IMAP ID EXTENSION. + +4.13.0 + +2013-03-09 Sam Varshavchik <mrsam@courier-mta.com> + + * imap: add explicit mkdirs on PIDFILE directories to startup scripts. + +2013-02-20 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822: workaround for invalid utf-8 input making libidn go off the + rails. + +4.12.0 + +2013-01-12 Gordon Messmer <yinyang@eburg.com> + + * tcpd/libcouriergnutls.c (get_server_cert): Fix size of malloced + buffer. + +2012-12-20 Sam Varshavchik <mrsam@courier-mta.com> + + * configure.in: Fix typo in configure check for setvbuf(). + +4.11.0 + +2012-09-05 Sam Varshavchik <mrsam@courier-mta.com> + + * maildir/maildiraclt.c (maildir_acl_delete): Fix double-free on error + path of an ENOMEM. + +2012-06-22 Sam Varshavchik <mrsam@courier-mta.com> + + * courier.spec.in: Fix the RPM spec to work around /bin symlink + breakage in F17. + + * tcpd/tcpd.c: Update libgnutls API, replace obsoleted functions + with current API. Obsoletes most TLS_* variables that set protocol + priorities, replaced with Gnutls-specific single TLS_PRIORITY + setting (existing TLS_* variables are still used by the OpenSSL + alternative). Updated *-ssl configuration files accordingly. + +2012-06-21 Eray Aslan <eray.aslan@caf.com.tr> + + * tcpd: Compilation changes for gnutls 3 + +2012-04-22 Sam Varshavchik <mrsam@courier-mta.com> + + * liblock/mail.c (dotlock_exists): Quell a compiler warning. + +2012-02-23 Osamu Aoki <osamu@debian.org> + + * Miscellaneous spelling fixes. + +4.10.0 + +2011-11-13 Sam Varshavchik <mrsam@courier-mta.com> + + * imapd.c (main): Open IMAPDEBUGFILE only if it exists already. + (do_expunge): Optionally log deletions. Based on a patch by + William Yodlowsky <wyodlows@andromeda.rutgers.edu>. + + * pop3dserver.c (cleanup): Optionally log deletions. Based on a patch by + William Yodlowsky <wyodlows@andromeda.rutgers.edu>. + + * imapd.dist.in (IMAP_MOVE_EXPUNGE_TO_TRASH): Officially document how + IMAPDEBUGFILE works. + +2011-10-28 Sam Varshavchik <mrsam@courier-mta.com> + + * imapscanclient.c: Postpone Y2038K for a while, for uid validities. + +2011-10-22 Sam Varshavchik <mrsam@courier-mta.com> + + * liblock/mail.c (dotlock_exists): Handle getting here because of a + recycled pid. + + * unicode/unicode.c (init_default_chset): Handle NULL from setlocale(). + +2011-09-06 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc2045/reformime.c (main2): Fixed segfault on some arches from an + initial null given to strtok. + +2011-08-14 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc2045/reformime.c (main2): On ia64 and arm, argv is in readonly + memory. + +2011-08-04 Michiel Boland <michiel@boland.org> + + * pop3dserver.c (openpop3dlist): Try a few times to reopen pop3dsizelist + if it fails with ESTALE. + +2011-07-11 Sam Varshavchik <mrsam@courier-mta.com> + + * pop3d.dist.in: Add AUTHSERVICE settings. They've been supported + all along. + +2011-06-19 Sam Varshavchik <mrsam@courier-mta.com> + + * Fix gcc 4.6 warnings + + * courier-imap.spec.in: switch to systemd. Remove script used when + upgrading from ancient pre-sysconftool versions. + + +2011-05-25 Sam Varshavchik <mrsam@courier-mta.com> + + * Fix autoconf warnings. + +4.9.3 + +2011-05-22 Sam Varshavchik <mrsam@courier-mta.com> + + * msgenvelope.c (msgappends): Fix a fatal error upon encountering + 8-bit header content. Heuristically try to interpret it as UTF-8, and + just ignore invalid UTF-8 sequences. + +4.9.2 + +2011-05-17 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc2045/rfc2045cdecode.c: Tolerate lowercase hexadecimal characters + in quoted-printable-encoded content. + +2011-05-06 Thomas Jacob <jacob@internet24.de> + + * unicode/unicode.c: Compilation fixes. + +4.9.1 + +2011-04-11 Sam Varshavchik <mrsam@courier-mta.com> + + * tcpd/libcouriertls.c (tls_create): Remove SSLv2_method(). + (tls_create): Remove "!SSLv2" from the default TLS_CIPHER_LIST. + +2011-04-07 Sam Varshavchik <mrsam@courier-mta.com> + + * mkimapdcert.in (prefix): Create imapd.pem with 600 perms. + + * mkpop3dcert.in (prefix): Create pop3d.pem with 600 perms. + +4.9.0 + +2011-03-19 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.h: Added unicode_isspace(). + + * unicode/unicode_wordbreak.c: Implementation of tr29. + + * unicode/unicode.h: unicode_lb_set_opts(), plus derivatives: set + artbirary linebreaking options. Two options that tailor the unicode + linebreaking algorithm. + +2011-03-06 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.c (convert_flush_iconv): Save errno in case it + gets clobbered by the callback function. + +2011-02-26 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.h (mail): Added iterator-based conversion functions + to/from unicode chars. + +2011-02-24 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.c (save_unicode): Optimize buffering of + unicode characters. + +2011-02-17 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.h: Adjust unicode_wcwidth() to return 0 for BK, CR, + LF, CM, NL, WJ, ZW characters. + +2011-02-16 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.h: Internal implementation if tr14, linebreaking + rules. + +2011-01-24 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822/rfc2047.c (rfc2047_encode_callback): Rewrite broken logic. + + * unicode/unicode.c (deinit_iconv): Incomplete multibyte character + remaining at the end of conversion was falsely being reported as + a callback failure, rather than a conversion failure. + +2011-01-22 Sam Varshavchik <mrsam@courier-mta.com> + + * Check if -liconv is needed to get iconv. + + * Clean up leftover unicode-related crud in configure scripts. + +2011-01-22 Gordon Messmer <yinyang@eburg.com> + + * rfc1035/rfc1035mksocket.c (rfc1035_mksocket): Set IPV6_V6ONLY + socket option to OFF for IPv6 sockets, if the system default is on. + +2011-01-22 Sam Varshavchik <mrsam@courier-mta.com> + + * Removed the last remains of the old unicode mapping code. + + * unicode/unicode.c (init_default_chset): Import additional logic + from the cone tree: CHARSET and MM_CHARSET environment variables + override locale's charset indication. If unable to determine locale, + parse lang.codeset@modifier string in LANG. + + * rfc822/encodeautodetect.c (libmail_encode_autodetect): Remove obsolete + unicode API. Determine encoding with heuristics based entirely on + the content. Remove charset arg, replace with "use7bit", to force + qp or base64, instead of 8bit. Take a binaryflag param that gets set + to indicate whether base64 was selected based on binary content. + + * pcp/pcp.c (usage): Unicode API update. + + * rfc822/rfc2047.c (rfc2047_encode_str): Removed + rfc2047_encode_callback_base64, invoked from rfc2047_encode_str(). + Rewrite rfc2047_encode_str() to use the new unicode API. + +2011-01-22 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822/rfc2047u.c: Unicode API updates. + + * unicode/unicode.h: Replace unicode_x_imap_modutf7 with a #define. + Eliminted unicode_x_smap_modutf7, replaced with + unicode_x_imap_modutf7, a space, and blacklisted chars. + + * unicode/unicodecpp.C (convert_tocase): C++ binding for + libmail_u_convert_tocase(). + + * unicode/unicode.c (init_default_chset): Map GNU libc nl_langinfo() + return of "ANSI_X3.4*" to "US-ASCII". + +2011-01-17 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode_ultcase.c (unicode_tc): Convert test character + to lowercase first, before converting to titlecase -- UnicodeData + does not specify an uppercase char's titlecase, directly. + + * maildir/maildirsearch.h (mail): New C++ binding method - + getSearchLen(). + +2011-01-16 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.h: Define C++ binding for libmail_u_convert(), + mail::iconvert. + + * maildir/maildirsearch.h (mail): Convert the C++ binding to use + the unicode-aware, case-insensitive, whitespace-collapsing algorithm. + + +2011-01-15 Sam Varshavchik <mrsam@courier-mta.com> + + * unicode/unicode.c (unicode_default_chset): New function calculates + the default system unicode charset based on the global locale. + (libmail_u_convert_init): Support a phony character set used to + encode unicode folder names for IMAP (and SMAP). Passing the + phony character set name to libmail_u_convert_init() encodes or + decodes to the phony character set. For converting to the phony + character set, libmail_u_convert_init() sets up an iconv conversion to + UCS-2, then wraps it into a module that converts UCS-2 to the phony + character set. For converting from the phony character set, + libmail_u_convert_init() sets up an iconv conversion from UCS-2, then + wraps it into a module that converts the phony character set to UCS-2. + (libmail_u_convert_tocbuf_init): A wrapper to libmail_u_convert_init() + that converts to native UCS-4, and saves it into a malloc-ed + unicode_char array. + (libmail_u_convert_tocbuf_toutf8_init): Convenience wrapper for + converting to UTF-8. + (libmail_u_convert_tocbuf_fromutf8_init): Convenience wrapper for + converting from UTF-8. + (libmail_u_convert_toutf8): Convenience wrapper that converts + a text string to UTF-8 and returns a malloced buffer with the + converted string. + (libmail_u_convert_fromutf8): Convenience wrapper that converts + a UTF-8 string to another charset, and returns a malloced buffer with + the converted string. + (libmail_u_convert_tobuf): Convenience wrapper that converts a + character string and returns the converted string in a malloced + buffer. + (libmail_u_convert_fromu_init): Convenience wrapper that sets up + a conversion of text to native unicode_chars. + (libmail_u_convert_uc): Convenience wrapper for passing unicode chars + through libmail_u_convert(). + + * rfc2045/rfc2646create.c (rfc2646create_alloc): Replace unicode + struct pointer argument with a character set string name. + Use libmail_u_convert_fromu_init() and libmail_u_convert_tou_init() + instead of the obsolete unicode functions. + + * rfc2045/rfc2231.c: Replace the unicode struct pointer argument with + a character set string. Use libmail_u_convert_tobuf() instead of the + obsolete unicode functions. + + * rfc2045/rfc2045decodemimesectionu.c (rfc2045_decodetextmimesection): + Add a return value indication of a failed conversion to the requested + character set. + + * maildir/maildirinfo.c: Use libmail_u_convert_tobuf() instead of the + obsoleted unicode functions. + +2011-01-09 Sam Varshavchik <mrsam@courier-mta.com> + + * all: Replace all usage of internal unicode tables with iconv. + Courier-IMAP now supports searching text given in any character set + that's supported by iconv. The internal unicode library is still used + for converting unicode to/from lower/uppercase, for functionality + not directly available in iconv. Pretty much a rewrite of the entire + search/sort/thread implementation. + +2011-01-08 Sam Varshavchik <mrsam@courier-mta.com> + + * all: Major rewrite of rfc2045 parsing functions, adding an abstraction + layer for reading the contents of the parsed rfc 2045 message, rather + than reading from a plain file descriptor. + + * smap.c: New rfc2045src abstraction layer API update. + + * rfc822/rfc822_parsedt.c: Eliminate the dependency on ctype, replaced + them with macros. + + * rfc822/rfc822hdr.c (rfc822hdr_namecmp): Factor out rfc822hdr_namecmp + from rfc822hdr_is_addr, and make it usable, generically. + +4.8.1 + +2010-08-21 Sam Varshavchik <mrsam@courier-mta.com> + + * Makefile.am (imapd.cnf): Compatibility fixes. + +2010-08-15 Sam Varshavchik <mrsam@courier-mta.com> + + * maildir/maildirwatch.c (maildirwatch_alloc): Use alarm() to kill + the process if FAMOpen() takes more than 15 seconds to return. + Should prevent a hung process if FAM/Gamin is fubared. + +2010-07-11 Sam Varshavchik <mrsam@courier-mta.com> + + * pop3dserver.c: Make printed() and putchar() macros. + +2010-07-01 Sam Varshavchik <mrsam@courier-mta.com> + + * imapd.cnf pop3d.cnf (default_md): added default_md = sha1 + +2010-06-28 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822/rfc822.c (rfc822_print_common_nameaddr): Prevent segfault if + address decode fails. + + * Fix make check failure when libidn is not available. + +2010-05-31 Sam Varshavchik <mrsam@courier-mta.com> + + * Rebuilt man pages with updated stylesheets. + +4.8.0 + +2010-05-30 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/configure.in: Check if explicit linking with libgpg-error is + required. + +2010-03-20 "Stefan Hornburg (Racke)" <racke@linuxia.de> + + * imapd.c (main): Fix typo in alert message. + +2010-03-18 Hugo Monteiro <hugo.monteiro@fct.unl.pt> + + * Big quota patch (with some changes). + +2010-03-12 Sam Varshavchik <mrsam@courier-mta.com> + + * imapd.c (main): Dummy FAM/Gamin initialization, report an error + during login, upon a failure. + +2010-03-10 Sam Varshavchik <mrsam@courier-mta.com> + + * imapd.c (imapenhancedidle): Make FAM error more meaningful. + +2010-02-25 Bernard Quatermass <bernard@quatermass.co.uk> + + * pop3dserver.c (main): "disableinsecurepop3" account option disables + non-SSL logins. + + * imapd.c (chkdisabled): "disableinsecureimap" account option disables + non-SSL logins. + +4.7.0 + +2009-11-22 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822/rfc822.c: Removed rfc822_praddr(). + + * rfc822/rfc822_getaddr.c (rfc822_getaddr): Implement rfc822_getaddr() + by calling rfc822_display_addr_tobuf(), instead of rfc822_praddr(). + + * rfc822/testsuite.c (doaddr): Remove rfc822_addrlist() and + rfc822_namelist(). + +2009-11-21 Sam Varshavchik <mrsam@courier-mta.com> + + * msgenvelope.c (doenva): Replace rfc822_getname() by + rfc822_display_name_tobuf() with a NULL character set. + + * rfc822/rfc822_getaddr.c: Remove rfc822_prname() and + rfc822_prname_orlist(), replaced by rfc822_display_name() with a NULL + character set. + + * rfc822/rfc2047u.c (rfc822_display_name): Semantical change -- + without an explicit name, display the address as the name. If the + requested character set is NULL, do not decode RFC2047-encoded content, + return it as is. + +2009-11-17 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc2045/rfc2045reply.c (mkreply): Fix logic for locating the + name used for salutation. + + * rfc822/rfc2047u.c (rfc2047_print_unicodeaddr): Fix several formatting + issues with deprecated RFC 822 distribution lists: spurious comma + adter the last address, pass the space after the ':' as a separator + character. + + * rfc822/rfc2047.c (counts2/save): Fix line-wrapping of encoded + addresses. + + * rfc822/rfc2047u.c (rfc822_display_addr_tobuf): New function. + +2009-11-14 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822/rfc822.c (rfc822_print_common): Rewrite. + + * rfc822/rfc2047u.c (rfc822_display_name_int): Fixed various rules for + encoding names to be more MIME compliant. + (rfc822_display_addr_str): Renamed from rfc822_display_addr(), for a + consistent API. + (rfc822_display_addr): New function, decode the wire format of a single + address. Names are MIME decoded, addresses are IDN-decoded. + (rfc2047_print_unicodeaddr): Do not output a dummy name for an + address without one. + (rfc822_display_addr_str_tobuf): New function, version of + rfc822_display_addr_str() that collects the output into a buffer. + + * rfc822/rfc2047.c (rfc822_encode_domain): New function -- IDN-encode + a domain, with an optional "user@". + (rfc2047_encode_header_addr): Renamed rfc2047_encode_header(), for a + consistent API. + (rfc2047_encode_header_tobuf): New function, encode a header from + displayed format to wire format. Names are encoded using RFC 2047, + addresses using IDN. + +2009-11-08 Sam Varshavchik <mrsam@courier-mta.com> + + * rfc822/rfc2047.h: Expose raw RFC 2047 decoding function, + rfc2047_decoder(). + + * rfc822/rfc822hdr.c (rfc822hdr_is_addr): New function. + + * rfc822/rfc822.c (tokenize): Tweak the logic for collecting RFC 2047 + atoms. + + * rfc822/rfc2047u.c (rfc822_display_name): New function, + replaces rfc2047_print(). + (rfc822_display_name_tobuf): New function, + replaces rfc2047_print(). + (rfc822_display_namelist): New function, + replaces rfc822_namelist(). + (rfc822_display_addr): New function, replaces rfc2047_print(). + (rfc2047_print_unicodeaddr): Renamed from rfc2047_print_unicode(). + (rfc822_display_hdrvalue): New function, replaces rfc2047_decode(), + rfc2047_decode_simple(), rfc2047_decode_enhanced(). + (rfc822_display_hdrvalue_tobuf): New function, ditto. + + * rfc822/rfc2047.c: Removed rfc2047_decode(), rfc2047_decode_simple(), + rfc2047_decode_enhanced(), rfc2047_print(). + + * rfc822/Makefile.am: Link against GNU IDN library. + + * thread.c (thread_ref_callback): SUBJECT for THREAD REFERENCES wasn't + getting converted to UTF-8 at the right point, moved it up into + search.c + + * search.c (fill_search_header): SUBJECT search key was being + MIME-decoded twice. + +4.6.0 + +2009-09-05 Gordon Messmer <yinyang@eburg.com> + + * outbox.c (imapd_sendmsg): Renamed sendmsg() to avoid library name + clash. + +2009-08-12 "Hanno Böck" <hanno@hboeck.de> + + * tls: change the default OpenSSL configuration to disable anonymous + authentication ciphers. + +4.5.1 + +2009-06-27 Sam Varshavchik <mrsam@courier-mta.com> + + * all: gcc 4.4 fixes + +2009-05-28 Sam Varshavchik <mrsam@courier-mta.com> + + * fetch.c (open_cached_fp): If the message file cannot be opened, + create a dummy message in its place. + +4.5.0 + +2009-05-09 Sam Varshavchik <mrsam@courier-mta.com> + + * Documentation refresh. + +2009-03-23 Fernando Gozalo <fgozalo@csi.uned.es> + + * pop3dserver.c: s/POP3_STLS/POP3_TLS/, for correct logging. + +2008-12-02 Sam Varshavchik <mrsam@courier-mta.com> + + * imaprefs.c (dorefcreate): Clean up usage of rfc822_threadsearchmsg(). + A malloc() failure wasn't checked correctly. + +2008-10-11 Dmitry Osipov <ffar@mail.ru> + + * rfc2045/rfc2045_fromfd.c: Optimize header parsing. + +2008-09-20 Sam Varshavchik <mrsam@courier-mta.com> + + * search.c: Empty MIME entities would always match any SEARCH. Reported + by Dmitry Osipov. + +2008-09-18 Federico Cuello <fedux@lugmen.org.ar> + + * tcpd/configure.in: --without-gnutls forces suppression of GnuTLS when + OpenSSL is not present. + +2008-08-26 Alessandro Vesely <vesely@tana.it> + + * tcpd/libcouriergnutls.c (tls_connect): Fix client certificate request + settings. + +2008-08-24 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriergnutls.c (set_cert): Add support for certificates + signed by an intermediate CA. + +2008-07-17 Mr. Sam <mrsam@courier-mta.com> + + * imapwrite.c (write_error_exit): Dump fatal errors to stderr. + +4.4.1 + +2008-07-14 Sergey <grubberr@gmail.com> + + * configure.in: Compilation with db was broken. +4.4.0 + +2008-07-11 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirquota.sgml: Update descriptions of maildir quotas. + +2008-06-29 Mr. Sam <mrsam@courier-mta.com> + + * pop3login.c (starttls): Use the couriertls library to invoke + couriertls, instead of doing it ourselves. SASL authentication did + not correctly handle the special case of "=" passed as the initial + response to indicate an empty initial response. Switch to the new + auth_sasl_ex() API. + + * pop3dcapa.c (pop3_externalauth): Check the environment variables + for subject fields set by couriertls. Advertise the AUTH EXTERNAL + capability, if they are set. + + * imaplogin.c (starttls): Use the couriertls library to invoke + couriertls, instead of doing it ourselves. + + * capability.c (imap_externalauth): Check the environment variables + for subject fields set by couriertls. Advertise the AUTH=EXTERNAL + capability, if they are set. + + * authenticate_auth.c (authenticate): SASL authentication did not + correctly handle the special case of "=" passed as the initial response + to indicate an empty initial response. Switch to the new auth_sasl_ex() + API. + + * tcpd/libcouriergnutls.c (oid_name_list): Rename "email" to + "emailAddress", for compatibility with OpenSSL. + + * tcpd/libcouriergnutls.c (verify_client): Check for the required + certificate occured in the wrong spot. + + * tcpd/libcouriergnutls.c (tls_connect): Add call to + gnutls_certificate_set_subject() to actually request the client cert. + + * tcpd/libcouriergnutls.c (tls_certificate_verified): Return an + indication if the peer's certificate is good. + + * tcpd/libcouriertls.c (tls_certificate_verified): Ditto. Minor bug + fixes. + + * tcpd/starttls.c (dump_to_fp): Parse Subject: header dumped by + couriertls, export it in environment variables. + + * tcpd/tlsclient.c (couriertls_get_subject): Retrieve individual + Subject: fields. + + * tcpd/tlsclient.c (couriertls_export_subject_environment): Export + parsed Subject: fields to the environment. + +2008-06-21 Mr. Sam <mrsam@courier-mta.com> + + * Optimize IMAP keyword implementation. Faster results in exchange + for a concurrency tradeoff if two concurrent sessions attempt to + update keywords for the same message at the same time -- only one will + win. + +2008-06-13 Mr. Sam <mrsam@courier-mta.com> + + * rfc822_getaddr.c: Backslashed special characters in address names + weren't being dequoted correctly by rfc822_getname() and + rfc822_getname_orlist(). + +2008-05-11 Mr. Sam <mrsam@courier-mta.com> + + * pop3login.c (main): Add a timeout to the POP3 login stage + +2008-05-08 Bernd Wurst <bernd@bwurst.org> + + * gdbmobj/Makefile.am (libgdbmobj_la_LIBADD): Fix makefile + +2008-05-08 Mr. Sam <mrsam@courier-mta.com> + + * bdbobj/Makefile.am: same patch. Also, clean up the rest of the + makefiles. + +2008-05-04 Kouhei Sutou <kou@cozmixng.org> + + * imapd.rc.in, pop3d.rc.in: Fix SSL initialization + +2008-04-09 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriertls.c (cache_get): Fix TLS session caching. + +4.3.1 + +2008-03-12 Gordon Messmer <yinyang@eburg.com> + + * tcpd/libcouriertls.c (tls_create): Default TLS_PROTOCOL=SSL23, + TLS_CIPHER_LIST=SSLv3:TLSv1:!SSLv2:HIGH:!LOW:!MEDIUM:!EXP:!NULL@STRENGTH + +2008-03-09 Brian Candler <B.Candler@pobox.com> + + * imapd.c (is_smap): Compilation failure fix. + +2008-03-07 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec - fix SSL build prerequisites. + +2008-03-07 Bernd Wurst <bernd@bwurst.org> + + * many: update description of SSL/TLS-related settings in several + configuration files. + +2008-01-26 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (main): New setting in the IMAP config file, + IMAP_MAILBOX_SANITY_CHECK -- disable sanity check on the homedir + and maildir ownership. + +2008-01-04 Mr. Sam <mrsam@courier-mta.com> + + * all: Explicitly make stderr line-buffered. + +2007-12-08 "Stefan Hornburg (Racke)" <racke@linuxia.de> + + * imapd.c (main): Suppress spurious error message + +4.3.0 + +2007-11-21 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriergnutls.c (tls_connect): TLS_MIN_DH_BITS setting, + invokes gnutls_dh_set_prime_bits(). Fix some bugs. + +2007-11-18 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriergnutls.c (tls_transfer): Fix some bugs. + +2007-11-18 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriergnutls.c: Fix SSL session caching bug. + +2007-11-10 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_expunge): Remove \Draft flag from messages moved + to the trash folder upon expunge from the original folder. + (main): Additional sanity check to check for common courier-authlib + misconfigurations. + +2007-11-04 Mr. Sam <sam@email-scan.com> + + * Added support for GnuTLS. + +2007-10-29 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (chk_clock_skew): Make skew check more reliable. + +2007-10-23 Mr. Sam <mrsam@courier-mta.com> + + * couriertls: code cleanup. + +4.2.1 + +2007-10-09 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (do_imapscan_maildir2): Avoid a double-fclose() + in marginal situations. + +2007-10-06 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/starttls.c (main): Prime the OpenSSL entropy pool on platforms + that don't have a ready source available. + +2007-10-03 Mr. Sam <mrsam@courier-mta.com> + + * Provide a default TCPREMOTEPORT when started from the command line. + +4.2.0 + +2007-09-25 "Johnny C. Lam" <jlam-courier@buildlink.org> + + * liblock/lockdaemon.c (OPEN_MAX): Use OPEN_MAX, instead of hardcoded + 99 + +2007-09-25 Mr. Sam <mrsam@courier-mta.com> + + * liblock/lockdaemon.c (OPEN_MAX): Even better, use + sysconf(_SC_OPEN_MAX), where available. + +2007-09-21 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (chk_clock_skew): Try to autodetect clock skew. + +2007-09-20 Adam Hasselbalch Hansen <ahh@one.com> + + * Include remote port number in IMAP and POP3 logs. + +2007-08-29 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tcpd.c (doit): Ignore SIGPIPE errors in couriertcpd, preventing + couriertcpd from being terminated if the stderr logger crashes. + +2007-07-26 Mr. Sam <sam@email-scan.com> + + * COPYING: GPL 3 + + * mailboxlist.c (folder_entry): Optimization: skip over folders + not in the current scope, avoiding unnecessary overhead of checking + their ACLs. + +2007-07-01 Mr. Sam <sam@email-scan.com> + + * configure.in (mydatadir): Updated PAM config file for Fedora 7. + +2007-06-30 Mr. Sam <mrsam@courier-mta.com> + + * Misc fixes for automake 1.10 + +4.1.3 + +2007-04-16 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirkeywords2.c (maildir_kwImport): Properly clean up + after a malloc failure. + + * maildir/maildirshared.c (do_maildir_shared_sync): Plug a leak + when syncing old-style shared folders. + +2007-04-15 Mr. Sam <mrsam@courier-mta.com> + + * unicode/eucjp.c (c2u): Check for malloc failure. + (u2c): Ditto. + + * unicode/shiftjis.c (c2u): Ditto. + + * unicode/shiftjis.c (u2c): Ditto. + + * unicode/ksx1001.c (c2u_euckr_doconv): Ditto. + + * unicode/ksx1001.c (u2c_euckr_doconv): Ditto. + + * maildir/maildirnewshared2.c (maildir_shared_cache_read): Clarify + calling convention, and check for invalid arguments to this function. + + * maildir/maildirkeywords2.c (doReadKeywords2): Eliminate dead code. + +2007-04-05 Mr. Sam <mrsam@courier-mta.com> + + * Update man pages and documentation to Docbook XML V4.4 + +2007-02-25 Mr. Sam <mrsam@courier-mta.com> + + * More configure script cleanup + +2007-02-25 Kurt Roeckx <kurt@roeckx.be> + + * Clean up configure scripts + +2007-02-25 Mr. Sam <mrsam@courier-mta.com> + + * rfc822/rfc822.c (parseaddr): rfc822a_alloc() would corrupt and + misparse RFC2047-encoded atoms. + +4.1.2 + +2006-09-19 Josip Rodin <joy@entuzijast.net> + + * maildirmake: Clarify some error messages. + + * maildir/maildirmake.sgml: Documentation fixup. + + * maildrop/maildropex.sgml: Documentation fixup. + +2006-09-05 Mr. Sam <mrsam@courier-mta.com> + + * imap: message files created by the IMAP server will use the umask + setting. + +2006-06-23 Peter Bieringer <pb@bieringer.de> + + * mainloop.c (sigexit): Try to log bandwidth usage before getting + killed by a signal. + +2006-05-29 Sergiy Zhuk <serge@yahoo-inc.com> + + * imapd.c (quotainfo_out): Fix 64-bit issue with quota indication. + +2006-05-26 Mr. Sam <mrsam@courier-mta.com> + + * all: Fix many compiler warnings. + +4.1.1 + +2006-04-14 Mr. Sam <mrsam@courier-mta.com> + + * liblock/lockdaemon.c: Fixed some compiler warnings. + +2006-04-13 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriertls.c (tls_create): Fix segfault if non-cert files + were found in the cert dir. + +2006-03-25 "Serhij Dubyk / Сергій Дубик" <dubyk@library.lviv.ua> + + * unicode: Added koi8-u character set. + +2006-03-17 Brian Candler <B.Candler@pobox.com> + + * imapd.c (quotainfo_out): Fix quota calculations on 32 bit platforms + with 64 bit off_t. + +2006-03-04 Gordon Messmer <yinyang@eburg.com> + + * soxwrap/sconnect.c (s_connect): Don't assume that connect() of + a non-blocking socket always fails with EINPROGRESS + +4.1.0 + +2006-02-23 Alain NAKACHE <alain@alinto.net> + + * imapd.dist.in (IMAP_ACL): New capability to control announcements + of IMAP ACL support (on by default). + +2006-02-07 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirquota.c (statsubdir): Fix quota race condition + check optimization. + +2006-01-22 Mr. Sam <mrsam@courier-mta.com> + + * Makefile/configure: upgrade libtool/autoconf/automake toolchain. + Workaround for broken libtool's mkinstalldirs macro. + + * tcpd/libcouriertls.c: Fix gcc 4 warning. + + * tcpd/tcpd.c: Ditto. + + * tcpd/tlspasswordcache.c: Ditto. + + * rfc822/rfc2047.c (encodebase64): Ditto. + + * rfc822/rfc822.c (parseaddr): Ditto. + + * libhmac/hmac.c (dohashkey): Ditto. + +2006-01-12 Mr. Sam <mrsam@courier-mta.com> + + * thread.c (dosortmsgs): Skip going through the motions of outputing + the results of a SORT if # of sorted messages is 0. + +2006-01-12 John Morrissey <jwm@horde.net> + + * maildir/maildirquota.c (do_deliver_warning): Fix quota warning + message delivery. + +2006-01-03 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (quotainfo_out): Adjusted casting to work better with large + quotas. + +2005-10-04 Sergiy Zhuk <serge@yahoo-inc.com> + + * imapd.c (do_imap_command): Have RENAME also create + courierimapuidlist. + + * maildir/maildirrename.c: Ditto - reset courierimapuidlist in + renamed directories. + +2005-11-21 Mr. Sam <mrsam@courier-mta.com> + + * smapsnapshot.c (restore_snapshot2): Fix some file descriptor leaks. + +2005-11-15 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045rewrite.c: Cleanup. Remove duplicate quoted-printable + implementation, use one in rfc822/encode.c + + * rfc822/encode.c (quoted_printable): encode spaces that precede a + newline. + +2005-10-04 Sergiy Zhuk <serge@yahoo-inc.com> + + * imapd.c (do_imap_command): Have RENAME also create + courierimapuidlist. + +2005-09-30 Sergiy Zhuk <serge@yahoo-inc.com> + + * imapd.c (do_imap_command): Have CREATE create courierimapuidllist. + +2005-09-30 Mr. Sam <mrsam@courier-mta.com> + + * imap/pop3: log total bytes sent/received in IMAP and POP3 sessions + (based on a patch by Peter Bieringer <pb@bieringer.de>. + +4.0.6 + +2005-09-23 Mr. Sam <mrsam@courier-mta.com> + + * maildir/Makefile.am: skip parts of make check if libpcre is not + installed + +2005-09-22 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirpurgetmp.c (maildir_purge): When autopurging messages + from a folder based on their timestamp, scan 'new' in addition to + 'cur', otherwise mail delivered to the folder directly never gets + purged, unless the folder is open. + +4.0.5 + +2005-09-07 Mr. Sam <mrsam@courier-mta.com> + + * maildir/Makefile.am (testmaildirfilter_LDADD): Move -lpcre to LDADD, + from LDFLAGS. + +2005-08-20 Mr. Sam <mrsam@courier-mta.com> + + * waitlib/waitlib.c: probe for sigprocmask(), and use it if available. + +2005-08-16 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_msgset): Fix handling of invalid sequence number of 0. + +2005-08-11 Mr. Sam <mrsam@courier-mta.com> + + * search.c (search_oneatatime): Fix NOT operator on content searches. + +4.0.4 + +2005-07-16 Mr. Sam <mrsam@courier-mta.com> + + * configure.in: Update to automake 1.9, autoconf 2.59, libtool 1.5.6, + gettext 0.14.1. + +2005-07-15 Mr. Sam <mrsam@courier-mta.com> + + * imap: Implemented account groups, administrator group. + +2005-07-04 Mr. Sam <mrsam@courier-mta.com> + + * pop3d.dist.in: Add PLAIN to POP3AUTH_ORIG. + +2005-07-01 Brian Candler <B.Candler@pobox.com> + + * imap: add LOGGEROPTS and SSLLOGGEROPTS configuration settings + which are passed to courierlogger to set the application name for + syslog. + + * INSTALL.html.in: Update documentation, add DEFDOMAIN setting to + imapd and pop3d config files. + +2005-07-01 Mr. Sam <mrsam@courier-mta.com> + + * INSTALL.html.in: --with-syslog was moved to courier-authlib. + +2005-06-29 Mr. Sam <mrsam@courier-mta.com> + + * mkimapdcert.in: Set umask before creating the cert file, to close + off a theoretical snoop. + * mkpop3dcert.in: Ditto. + + * imapd.c: New OUTBOX_MULTIPLE_SEND option. + +2005-06-29 Brian Candler <B.Candler@pobox.com> + + * tcpd/tcpd.c: Added --accesslocal option. + +2005-06-18 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in (License): Replaces obsolete Copyright: header. + * smap: SMAP EXPUNGE >100 msg ranges was broken. + +2005-06-11 Mr. Sam <mrsam@courier-mta.com> + + * COPYING.GPL: New FSF address. + +2005-06-03 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in: Clean up spec file. + +2005-05-18 "Hrvoje Habjanić" <hrvoje.habjanic@zg.t-com.hr> + + * Reorder include files, to compile on SunOS & OSFx. + + * Workaround for bugs on some platforms' select(). + + * soxwrap needs a configure check for socklen_t. + +4.0.3 + +2005-05-08 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.sysvinit: Remove init lock file upon stop. + +2005-04-03 Giulio Cervera <giulio@edspa.it> + + * PROXY_HOSTNAME: new setting in imap and pop3 config file overrides + gethostname() when checking if a proxy connection is required. + +4.0.2 + +2005-02-26 Mr. Sam <mrsam@courier-mta.com> + + * msgbodystructure.c (msgbodystructure): Fix MSGBODYSTRUCTURE response + for a corrupted content-type of "text". + +2005-02-19 Mr. Sam <mrsam@courier-mta.com> + + * courierauthdebug.h: Macro dprintf conflicts with new glibc. + +2005-02-16 Mr. Sam <mrsam@courier-mta.com> + + * Various: Replace "env -" with "env -i" -- portability. + + * configure.in: Fix sourcing of ./conftest2. + +2005-02-08 Mr. Sam <mrsam@courier-mta.com> + + * Makefile.am: Fix README.proxy getting clobbered by make clean. + +2005-01-27 Andres Salomon <dilinger@voxel.net> + + * rfc2045/reformime.sgml: Fix typo. + +4.0.1 + +2005-01-04 Andreas Erhart <andi@zollhaus.net> + + * imaplogin.c (login_callback): Fix imap proxy code. + +4.0.0 + +2004-12-04 Brian Candler <B.Candler@pobox.com> + + * imap: fix disconnection syslog messages. + +2004-11-29 Brian Candler <B.Candler@pobox.com> + + * pop3dserver.c (main): Fix error message when chdir fails. + +2004-11-24 Mr. Sam <mrsam@courier-mta.com> + + * imapd.dist.in: Add IMAP_UMASK, which resets umask in the imapd + startup script. Derived from Ted Zlatanov <tzz@lifelogs.com>'s patch. + +2004-11-20 Brian Candler <B.Candler@pobox.com> + + * Additional debugging cleanup. + +2004-11-09 Brian Candler <B.Candler@pobox.com> + + * sharedindexsplit: Various fixes, imported from courier-authlib. + +2004-11-12 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec: Don't grab ownership of /var/run. Drop + redefinition of the %configure macro. + + * maildir: maildir_newshared_disabled turns off new-style shared + folders. maildir_acl_disabled disables ACL support. + + Account option "disableshared=1" disables virtual shared folders + and ACLs. + +2004-11-09 Brian Candler <B.Candler@pobox.com> + + * imap/pop3: minor cleanup. + +2004-11-05 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (main): Authenticated address is in AUTHENTICATED, + not AUTHADDR, now. + +2004-11-03 Mr. Sam <mrsam@courier-mta.com> + + * Makefile.am (SUBDIRS): Ok, only build in bdbobj/gdbmobj according + to what autoconf finds. + +2004-10-30 Mr. Sam <mrsam@courier-mta.com> + + * imaplogin.c (login_imap): Implemented IMAP proxying. + + * pop3login.c (login_pop3): Implemented POP3 proxying. + +2004-10-20 Mr. Sam <mrsam@courier-mta.com> + + * Factored out courier-authlib. + +3.0.8 + +2004-09-06 Mr. Sam <mrsam@courier-mta.com> + + * imaptoken.c (bye_msg): IMAP disconnect message includes session + length and STLS mode, for logging purposes. + + * pop3dserver.c (acctout): POP3 disconnect message includes session + length, and STLS mode, for logging purposes. + +2004-09-01 Marc Horowitz <marc@mit.edu> + + * search.c (fill_search_veryquick): Negative SEARCH UID does not + get short-circuited. + +2004-08-12 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): [READ-WRITE] and [READ-ONLY] reflect + ACL rights. + +2004-08-06 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Fix usage of mysql_config + +2004-08-05 Mr. Sam <mrsam@courier-mta.com> + + * testsuite (IMAP_SHAREDINDEXFILE): Fix a bash-ism. + +3.0.7 + +2004-07-27 Mr. Sam <mrsam@courier-mta.com> + + * Makefile.am (EXTRA_DIST): Removed FAQ (see the main Courier FAQ + on http://www.courier-mta.org/FAQ.html). + (EXTRA_DIST): Moved README.imapkeywords.html to the maildir + subdirectory. + +2004-07-21 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysqllib.c (auth_mysql_getuserinfo): Fix options field. + +3.0.6 + +2004-07-18 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (fnamcmp): Ignore :s when comparing filenames + during maildir scan (fixes anomalous cases with nonstandard filenames). + +2004-07-12 Mr. Sam <mrsam@courier-mta.com> + + * outbox.c (is_outbox): Make OUTBOX work again. + +2004-06-27 Mr. Sam <mrsam@courier-mta.com> + + * all: Update GNU toolchain. + + * rfc1035/rfc1035qptr.c (ptr): IPv6 reverse DNS lookups switch to + ip6.arpa. + +2004-06-18 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaplib.c: Conditionally define LDAP_OPT_SUCCESS if + not defined by <ldap.h>. + +2004-06-18 Laurent Wacrenier <lwa@teaser.fr> + + * maildir/maildirquota.c (docount): Fix quota parsing bug on 64bit + off_t systems. + +2004-06-13 John Morrissey <jwm@horde.net> + + * imap: Add MAILDIRPATH setting to imapd-ssl and pop3d-ssl + configuration files. + +2004-06-13 Mr. Sam <mrsam@courier-mta.com> + + * smap.c (dump_hdrs): Added FETCH RAWHEADERS command. + +2004-06-12 Mr. Sam <mrsam@courier-mta.com> + + * liblog/logger.c (startchild): Close stderr after initializing + the monitored process. Fixed hanging file descriptor to the terminal. + +3.0.5 + +2004-06-06 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045.h: Clean up and re-factor out MIME header parsing + into a new function: rfc2045_parse_mime_header. + +2004-05-24 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Add #include <sys/types.h> in probe for + -lresolv. + +2004-05-19 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirquota.c (do_deliver_warning): Make quota warning + message work correctly with NFS. + (do_maildir_openquotafile): Make quota calculations 64bit-safe (based + on patch from Michael Kefeder <ml@weird-birds.org>). + +2004-05-18 Mr. Sam <mrsam@courier-mta.com> + + * mailboxlist.c (maildir_shared_index_file): Complain if shared + index file does not exist. + +2004-05-17 Hatuka*nezumi - IKEDA Soji <nezumi@jca.apc.org> + + * all: Improve MIME encoding of message headers for East Asian + character sets. + +2004-05-17 Mr. Sam <mrsam@courier-mta.com> + + * capability.c (imapcapability): Announce XMAGICTRASH capability + + * imapd.c (do_imap_command): Fix #shared in NAMESPACE reply + +3.0.4 + +2004-05-09 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authpgsqllib.c: fixed dash-extensions + +2004-05-09 Jeff Potter <jpotter-courier@codepuppy.com> + + * authlib/authmysqllib.c: fixed dash-extensions + +2004-05-09 Mr. Sam <mrsam@courier-mta.com> + + * makedat/makedat.in: Skip subdirectories named "CVS". Allows all + directory-based lists (makesmtpaccess, etc...) to be managed via CVS. + +2004-05-08 Brian Candler <B.Candler@pobox.com> + + * pop3dserver.c (do_retr): If error occured during RETR drop the + connection (only sane thing to do). + +2004-05-02 Brian Candler <B.Candler@pobox.com> + + * authlib: additional debugging. + + * maildir/maildirquota.c: additional debugging. + +2004-05-07 Mr. Sam <mrsam@courier-mta.com> + + * rfc1035/rfc1035qa.c (we_have_that_ipv4): Fix pointer comparison. + + * rfc1035/rfc1035dumprrdata.c (rfc1035_dumprrdata): Fix pointer + comparison. + + * maildir/maildirkw.c (doit_locked): Fix pointer comparison. + + * maildir/maildirfilter.h: Compilation fix. + +2004-04-24 Brian Candler <B.Candler@pobox.com> + + * authlib/authdaemond.c (authnext): Do not report unknown + authentication modules -- unwanted noise. + +2004-04-24 Mr. Sam <mrsam@courier-mta.com> + + * liblog/courierlogger.sgml: courierlogger man page. + +2004-04-23 Brian Candler <B.Candler@pobox.com> + + * liblog/courierlogger: New courierlogger. + + * pop3dserver.c (scancur): Report an error if authentication module + started pop3d in a wrong directory. + + * Makefile.am: Do not overwrite existing PAM configuration files. + +2004-04-20 Brian Candler <B.Candler@pobox.com> + + * authlib/authmysqllib.c (auth_mysql_getuserinfo): More SQL fixes. + + * maildir/maildirgetnew.c (do_maildir_getnew): Fix infinite loop if + rename() syscall fails. + +2004-04-19 Brian Candler <B.Candler@pobox.com> + + * authlib/authtest.c: Additional logging messages. + + * Documentation fixes. + +2004-04-18 Mr. Sam <mrsam@courier-mta.com> + + * liblog: new directory for courierlogger, moved out of tcpd. + +2004-04-17 Brian Candler <B.Candler@pobox.com> + + * all: additional logging. + +2004-04-11 Brian Candler <B.Candler@pobox.com> + + * all: additional logging messages. Set log level via --with-syslog + +2004-04-11 Mr. Sam <mrsam@courier-mta.com> + + * thread.c (thread_ref_callback): MIME-decode the subject header + to UTF-8 before running the THREAD REFERENCES algorithm. + + * rfc822/rfc2047.c (a_rfc2047_encode_str): Improve compliance with + RFC 2047 for MIME-encoded recipient lists. + (rfc2047_encode_callback): New argument: qp_allow - function that + indicates acceptable characters in QP-encoded words. + (rfc2047_encode_str): Ditto. + (rfc2047_qp_allow_any, rfc2047_qp_allow_comment) + (rfc2047_qp_allow_word): Possible arguments to qp_allow for various + situations. + +2004-04-09 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045cdecode.c (do_decode_base64): Long overdue - use + a precomputed base64 decoding table. + + * rfc822/encode.c: Moved rfc2045/rfc2045encode.c here, renamed all + functions to use the libmail_ prefix. + +3.0.3 + +2004-03-31 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_expunge): Fix expunge from Trash when + IMAP_MOVE_EXPUNGE_TO_TRASH is set. + +2004-03-25 Brian Candler <B.Candler@pobox.com> + + * imap/imapd.dist.in: Clarify AUTHMODULES setting. + + * imap/pop3d.dist.in: Clarify AUTHMODULES setting. + +2004-03-25 Ondrej Jombik <nepto@pobox.sk> + + * fetch.c (fetchitem): Fix sigsegv caused by null ptr deref on open + error. + +2004-03-21 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (getacl_cb): Fix negative rights for ACL1. + (acl2_identifier): Ditto. + +2004-03-17 Mr. Sam <mrsam@courier-mta.com> + + * testsuite (OPTIONS): Fix a bash-ism. + + * smaptestsuite (IMAP_SHAREDINDEXFILE): Ditto. + +3.0.2 + +2004-03-16 Matthew Kanar <mattkanar@hotmail.com> + + * authlib/authvchkpw.c: Fix password changes. + +2004-03-16 Oliver Lehmann <oliver@FreeBSD.org> + + * sv-make_timezonelist.pl: Timezone file location on FreeBSD. + +2004-03-13 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (acl_read_folder): Add the 'x' right for filesystem-based + shared folders, even though it's not true; but it's needed for ACL1 + compatibility. + +2004-03-11 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: netinet/in.h needs sys/types.h on xBSD. + + * courier/configure.in: Ditto. + +2004-03-10 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirkeywords.h: Sun C++ compiler fix. + +3.0.1 + +2004-03-09 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirrename.c (scan_aclhier2_rename): Check dirp is not + NULL before closing it. + * maildir/maildirrename.c (scan_maildir_rename): Ditto. + + * maildir/maildirmake2.c (maildir_del): xBSD portability fix. + +3.0.0 + +2004-03-02 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: BSD needs netinet/in.h before resolv.h + +2004-02-18 Mr. Sam <mrsam@commodore.email-scan.com> + + * Various fixes for the X86-64 platform. + +2004-02-15 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildiraclt.c (maildir_acl_canlistrights): New function + moved from imap code, to be shared by sqwebmail. + +2004-02-11 Mr. Sam <mrsam@courier-mta.com> + + * smap.c (do_listcmd): Fix SMAP ACL. + + * unicode/big5.c (c2u_doconv): C portability fix. + gb2312: ditto. + +2004-02-09 IKEDA Soji <nezumi@jca.apc.org> + + * Big5, euc-jp, gb2312, ksx1001, shiftjis: let these functions handle + their own conversion errors. + +2004-02-07 IKEDA Soji <nezumi@jca.apc.org> + + * Big5: Add non-hanzi maps. Add ETen extension. Add Big5-HKSCS + charset. + + * Gb2312: Add non-hanzi maps. + + * Let iso2022-jp functions handle their own conversion errors. + +2004-02-07 Jan Zimmerschied <jan@z4fun.de> + + * RPM spec file for SUSE. + +2004-02-02 IKEDA Soji <nezumi@jca.apc.org> + + * iso2022jp.h: Maps for CJK Compatibility Ideographs has been added. + + * ksx1001.c: New character sets: ISO-2022-KR, EUC-KR, CP949, + ISO-2022-JP-1. + +2004-02-01 Mr. Sam <mrsam@courier-mta.com> + + * fetch.c (fetchitem): Optimization: do not open the message file + for UID and FLAGS fetches - not necessary. + + * unicode/iso88597.c: Updated from unicode.org. + + * unicode/unicode_ultcasetab.c: Updated from unicode.org + +2004-02-01 IKEDA Soji <nezumi@jca.apc.org> + + * iso2022jp.pl / iso2022jp.h: JIS X 0208 mapping has been updated + based on latest regional standard (JIS X 0208:1997) and some vendor + standards. Errorneous (lacking or false) mappings has been fixed. + Support for older JIS X 0208 (JIS C 6226:1978) mapping was added. + JIS X 0212:1990 mapping was added. + + * iso2022jp.c: Converters became (upper-)compatible with ISO-2022-JP + (RFC1468 / JIS X 0208:1997 Annex 2) and ISO-2022-JP-1 (RFC2237). + Buffer overflow vulnerability (when Unicode character is out + of BMP range) has been closed. Convert error handling was implemented. + + * shiftjis.c: Broken SHIFT_JIS converters has been fixed and became + (upper-)compatible with Shifted Encoding Method (JIS X 0208:1997 + Annex 1). Buffer overflow vulnerability (when Unicode character is out + of BMP range) has been closed. Convert error handling was implemented. + + * eucjp.c: New converters for EUC-JP. + +2004-01-24 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirinfo.c: replace getenv("AUTHENTICATED") with an + extra parameter. + +2004-01-23 Alessandro Vesely <vesely@tana.it> + + * tcpd/tcpd.c (parseaddr): One-liner. + +2004-01-23 Jan Zimmerschied <jan@z4fun.de> + + * Add MAILDIRPATH to config files, that defines the default Maildir + location in the startup script. Fix autoconf paths in the config + files. + +2004-01-23 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysqllib.c (auth_mysql_getuserinfo): It's really + named MYSQL_AUXOPTION_FIELD. + +2004-01-19 Mr. Sam <mrsam@courier-mta.com> + + * Cleanup: move shared.[ch] and mailbox.[ch] modules to libmaildir.a, + for potential reuse by sqwebmail. + +2004-01-18 Mr. Sam <mrsam@courier-mta.com> + + * Implement account options: disableimap, disablepop3, sharedgroup. + + * Courier-IMAP: Move courierlogger from libexecdir to sbindir + + * authdaemond: replace stderr diagnostics to syslog(). authdaemond's + stderr doesn't go anywhere. + +2004-01-13 Mr. Sam <mrsam@courier-mta.com> + + * Rebuild all docbook SGML stuff. + + * Fix SMAP testsuite. + +2004-01-13 Michael Bowe <mbowe@pipeline.com.au> + + * authvchkpw update. + +2004-01-12 Mr. Sam <mrsam@courier-mta.com> + + * mailboxlist.c (list_newshared_shortcut): Fix list #shared.%.%, for + Mozilla. + + * imapd.c (do_imap_command): ACL fixes. Quota fixes. + +2004-01-11 Mr. Sam <mrsam@courier-mta.com> + + * smap.c: Fix SMAP ACL implementation. + + * imapd.c (aclstore): Fix gcc-ism. + +2004-01-10 Michael Bowe <mbowe@pipeline.com.au> + + * authvchkpw update. + +2004-01-10 Mr. Sam <mrsam@courier-mta.com> + + * imap: Implement virtual shared folders + ACL. + +2003-12-27 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirmake2.c (maildir_make): Fix memory leak. + + * maildir/maildirkeywords2.c (doReadKeywords2): Fix memory leak + when keywords are not enabled. + +2003-12-25 Mr. Sam <mrsam@courier-mta.com> + + * unicode/unicode.h: Formal unicode structure for IMAP's modified-UTF7 + coding. + +2003-12-23 Mr. Sam <mrsam@courier-mta.com> + + * rfc1035/configure.in: Fix --with-ipv6 option parsing. + +2003-12-20 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): Drop kludge for Sun's StarOffice. + +2003-12-13 Mr. Sam <mrsam@courier-mta.com> + + * smap.c: Remove legacy SHARED support from SMAP1. + +2003-12-12 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (do_imapscan_maildir2): autocreate + courierimapkeywords directory if a folder doesn't have it. + +2003-12-07 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (set_namespace_options): Drop #allfolders hack. Conflicts + with new ACL code. + * README.html (href): Remove #allfolders description. + +2003-12-03 Mr. Sam <mrsam@courier-mta.com> + + * fetchinfo.c (alloc_headerlist): Allow header names to be + quoted (+more). + +2003-11-30 Mr. Sam <mrsam@courier-mta.com> + + * mailboxlist.c (mailbox_scan): Replaced mailbox_list() with a + callback-based mailbox_scan(), fixing empty reference name case + along the way. The mailboxes are listed in the callback function. + +2003-11-29 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirdelfolder.c: maildir_mddelete superceded by + maildir_del(). + + +2003-11-28 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirshared2.c (maildir_shareddir): Move maildir_shareddir + from maildirshared.c, so that a reference to this function does not + pull in the entire db dependency tree. + +2.2.1 + +2003-12-19 Mr. Sam <mrsam@courier-mta.com> + + * unicode: Fix toupper_func/tolower_func/totitle_func for shiftjis, + big5, utf8, utf7, iso2022jp: function may return a NULL even when + requested to ignore conversion errors. + +2003-12-17 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysqllib.c (get_variable): Fix warning. + +2003-12-09 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirrename.c (validrename): Rename foo to foo.bar is not + kosher. Compile list of directories to rename first, sort, then + rename. + +2003-11-27 Mr. Sam <mrsam@courier-mta.com> + + * (imapd|pop3d)(-ssl)?.rc: check for missing make install-configure. + +2003-11-18 Tim Rice <tim@multitalents.net> + + * rfc822/configure.in: Fix MSG_WARN. + +2003-11-15 Mr. Sam <mrsam@courier-mta.com> + + * Update to automake 1.7.8, autoconf 2.57, libtool-1.5, gettext-0.12.1 + +2003-10-30 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirlock.c (maildir_lock): Fix double-free() call. + +2.2.0 + +2003-10-27 Mr. Sam <mrsam@courier-mta.com> + + * search.c (search_body_func): Ignore charset conversion errors when + doing a SEARCH (GIGO principle). + +2003-10-25 Jon Nelson <jnelson-courier@jamponi.net> + + * imapd.c (main): Reset umask before opening the debug file. + +2003-10-21 Mr. Sam <mrsam@courier-mta.com> + + * Makefile.am (userdb.8): Respect $(EXEEXT) + + * tcpd: Respect $(EXEEXT) + + * authlib: Respect $(EXEEXT) + + * libhmac: Respect $(EXEEXT) + + * rfc2045/testsuite: Fix enable-mimecharset breaking make check. + +2003-10-20 Mr. Sam <mrsam@courier-mta.com> + + * rfc822/rfc2047u.c (rfc2047_print_unicode): Unicode-aware version of + rfc2047_print(). + +2003-10-13 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (imap_addRemoveKeywords): Remove duplicate message sequence + numbers from message list (causes infinite loop due in keyword + support for +FLAGS/-FLAGS). + * maildir/maildirkeywords2.c (read_updates): Ditto. + +2003-10-05 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (do_imapscan_maildir2): Workaround for filesystems + that keel over if files are deleted in the directory that's being + read at the same time. + * maildir/maildirgetnew.c (do_maildir_getnew): Ditto. + + * pop3dserver.c (main): Replaced scannew with maildir_getnew(). + + * pop3dserver: Fix upgrades from pre-courierpop3dsizelist. + +2003-10-02 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c: When IMAP_MOVE_EXPUNGE_TO_TRASH is set, be smart and + do not move to Trash a message that's been expunged after it was + copied to another folder. + +2003-09-29 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (store_mailbox): Optimize APPEND performance. Based on a + patch by Anssi Raittila <Anssi.Raittila@tecnomen.fi>. + +2003-09-25 Mr. Sam <mrsam@courier-mta.com> + + * bdbobj/bdbobj.c (bdbobj_open): Fix dbf_open call for db 3.x + +2003-09-22 Mr. Sam <mrsam@courier-mta.com> + + * smap.c: getenv("MAILDIRQUOTA") as last argument to + maildir_quota_add_start(). + +2003-09-22 Brian Candler <B.Candler@pobox.com> + + * imapd.c: getenv("MAILDIRQUOTA") as last argument to + maildir_quota_add_start(). + +2003-09-21 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.sysvinit.in: Fix splat-bang. + + * userdb/makeuserdb.in: Make sure makeuserdb emits a trailing + newline after each processed file. +2.1.2 + +2003-09-01 Sergio Gelato <Sergio.Gelato@astro.su.se> + + * Set timestamp on new message file after it is moved from tmp to new. + +2003-08-27 Mr. Sam <mrsam@courier-mta.com> + + * smap.c (applyflags): Fix SMAP STORE KEYWORDS again. + +2003-08-26 Mr. Sam <mrsam@courier-mta.com> + + * smap.c (smap): Fix SMAP STORE KEYWORDS. + + * tcpd/starttls.c (connect_completed): Initialize + TLS_CONNECTED_PROTOCOL before running the wrapped child process. + + * tcpd/libcouriertls.c (tls_connecting): New function checks if + SSL/TLS negotiation is still in progress on a non-blocking socket. + +2.1.1 + +2003-08-18 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (addRemoveKeywords): Do nothing unless keywords are + enabled (fix CPU spin if IMAP_KEYWORDS are manually disabled). + + * unicode/unicode.h: Introduce new character set flag: UNICODE_USASCII + which designates whether the character set is a proper us-ascii + superset. All character sets are marked as UNICODE_USASCII except the + following character sets: IBM864/CP864, UTF-7, ISO-2022-JP. +2.1 + +2003-08-17 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authpgsqllib.c (auth_pgsql_getuserinfo): Fix memory + corruption with custom select clause is enabled and long userids. + + * authlib/authmysqllib.c (auth_mysql_getuserinfo): Fix memory + corruption with custom select clause is enabled and long userids. + +2003-08-09 Mr. Sam <mrsam@courier-mta.com> + + * imap/smap: attempt to detect whether someone else modified + keywords while we were doing +KEYWORDS or -KEYWORDS, and, if so, + rerun these commands. + +2003-08-03 Mr. Sam <mrsam@courier-mta.com> + + * Added the maildirkw command+man page. + +2003-08-02 Mr. Sam <mrsam@courier-mta.com> + + * bdbobj/bdbobj.c (bdbobj_open): Fix for DB 4.1 API. + +2003-08-01 Mr. Sam <mrsam@courier-mta.com> + + * Moved keyword support code into libmaildir.a. Major cleanup. + +2003-07-31 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c: Keyword support cleanup. + + * imapd.c (bye): Clean up after an aborted APPEND. + +2003-07-29 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Add notice to forward all vpopmail + questions to the vpopmail mailing list. + +2003-07-24 James A Baker <jabaker@mac.com> + + * authlib/README.authmysql.html: Cleanup. + + * authlib/README.authpostgres.html: Cleanup. + + * imap/FAQ.html: Cleanup. + + * imap/README.html: Cleanup. + + * maildir/README.maildirfilter.html: Cleanup. + + * maildir/README.sharedfolders.html: Cleanup. + +2003-07-24 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c: Create an API layer for the keyword read/update + functionality in imapscan_readKeywords(), divorcing the implementation + from the IMAP-specific data structure, in preparation for moving + the entire logic into the generic maildir library. + + * smap.c (createSearch): Added SMAP SEARCH [range] command. + +2003-07-22 Mr. Sam <mrsam@courier-mta.com> + + * smap.c (createSearch): Added SMAP SEARCH ALL command. + +2003-07-20 Mr. Sam <mrsam@courier-mta.com> + + * testsuite: Replace find with chmod -R, more portable. + +2003-07-19 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c: Move #define of KEYWORDDIR to maildirwatch.h + + * imapd: Plug several memory leaks found by valgrind. One of the + leaks is in libfam. Reported the bug upstream. + + * maildirwatch: Use a single FAM connection, where possible, in order + to minimize libfam leak. Open another FAM connection only if the + first one breaks. + + * pop3dserver.c (print_uidl): Redo UIDL computation once again. + Version 2 UIDL format is upwards compatible from version 0/1 + courierpop3dsizelist formats, preserving legacy UIDLs. See comments + for the description of error recovery from out-of-disk space + (quota-related) issues. + +2003-07-18 Brian Candler <B.Candler@pobox.com> + + * smaptestsuite: Ignore leading space from wc -l, differs between + platforms. + +2003-07-17 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c: Rename new keyword update files after installing + a new :list file. + + * tcpd/tcpd.c (mksockets): Use AF_INET for binds to IPv4 addresses, + even on IPv6-capable machines. + +2003-07-16 Mr. Sam <mrsam@courier-mta.com> + + * imap/smap: Implement custom keywords. Custom keywords may be + enabled/disabled in the configuration file. Additionally, error + checking has been tightened up: stuff that used to be tolerated, + such as invalid messages flags, is now reported as an error. + +2003-07-14 Mr. Sam <mrsam@courier-mta.com> + + * search.c (fill_search_preparse): Make SEARCH parse message flags and + keywords only once, instead of once for each message. + (search_oneatatime): Move main loop of search iteration into a + separate function, which evaluates the search criteria for a given + message only (leave indentation alone, will reindent later). + +2003-07-13 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (doNoop): Drop automatic FETCH FLAGS of new messages. + +2003-07-09 Mr. Sam <mrsam@courier-mta.com> + + * rfc822/imaprefs.c (rfc822_threadmsgrefs): New function takes + an array of References: headers, instead of a single References: + string (merged from the cone tree). + +2003-07-08 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (sortmsgs): Use time for a new POP3 UID counter. + +2.0 + +2003-06-30 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tcpd.c: Typo prevents IPv6 sockets from being created on BSD. + +2003-06-22 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authdaemond.c (start): Fix authdaemon idle processing. + + * pop3dserver.c (cmpmsgs): Ignore maildir message flags when sorting + message lists (eliminated dup POP3 downloads after 2003-06-18). + +2003-06-20 Mr. Sam <mrsam@courier-mta.com> + + * smap.c: Do not use BUFSIZ to size I/O buffers (make check will fail + if BUFSIZ != 8192). + +2003-06-17 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c: Generate shorter message UIDs. UIDs based on the + filename may be too long for some clients. Save a counter in + courierpop3dsizelist that is, essentially, an IMAP UID. + +2003-06-12 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tlspasswordcache.c (tlspassword_save): Use EIO instead of + EPROTO (which is not defined everywhere). + + * tcpd/configure.in: More thorough check for OpenSSL 0.9.7. + +2003-06-11 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldap.schema: Remove duplicate definition of + virtualdomainuser. + + * maildir/maildirwatch.c (maildirwatch_alloc): Fix test for error + return from getcwd(). + +2003-06-05 Mr. Sam <mrsam@courier-mta.com> + + * tcpd: merge tlspasswordcache from cone's tree. + +2003-05-31 Mr. Sam <mrsam@courier-mta.com> + + * smap.c (hashdr): Fix c++-ism. + +2003-05-29 Mr. Sam <mrsam@courier-mta.com> + + * thread.c (thread_ref_callback): Extra argument to rfc822_threadmsg() + specifies message date as time_t. + +2003-05-27 Mr. Sam <mrsam@courier-mta.com> + + * Added experimental SMAP protocol. + + * rfc2045/rfc2045header.c: eat leading space when folding header lines. + + +2003-05-19 Mr. Sam <mrsam@courier-mta.com> + + * maildir/README.maildirquota.html: Clarify that lines in maildirsize + are padded to 14 character lengths. + +1.7.3 + +2003-05-14 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirdelfolder.c (delsubdir): Sometimes we don't get + an EISDIR, even though we should <sigh>... + +2003-05-14 Stefan Hornburg <racke@linuxia.de> + + * userdb/makeuserdb.sgml: Fix command names. + +2003-05-14 Mr. Sam <mrsam@courier-mta.com> + + * unicode/utf7.c: UTF-7 mapping. + +1.7.2 + +2003-05-01 Mr. Sam <mrsam@courier-mta.com> + + * authlib: split DEFAULTDELIVERY from MAILDIR for LDAP, MySQL, Postgres + (used by courier instead of MAILDIR) + +2003-04-29 Mr. Sam <mrsam@courier-mta.com> + + * Replace U+0x00A0 in SGML documentation with spaces. + +2003-04-28 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (scannew): Do not append :2, to msgs that already + have it. + +2003-04-25 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirdelfolder.c (maildir_mddelete): A more "thorough" + folder delete. + +2003-04-23 Rodrigo Severo <rodrigo.lists@fabricadeideias.com> + + * authlib/authmysqlrc: Fix comments + +2003-04-21 Mr. Sam <mrsam@courier-mta.com> + + * Updated toolchain to automake 1.6.3, autoconf 2.57, + libtool 1.4.3, gettext 0.11.4, new Docbook style sheets. + + * bdbobj/bdbobj2.c (bdbobj_nextkey): Eliminate 0-length malloc. + +2003-04-18 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysqllib.c (validateMyPassword): Rename + validate_password to validateMyPassword (MySQL 4 conflict). + +2003-04-12 Mr. Sam <mrsam@courier-mta.com> + + * All beta and releases will now be signed by + http://www.courier-mta.com/KEYS.bin + +2003-04-11 Mr. Sam <mrsam@courier-mta.com> + + * tcpd: configure and makefile cleanup + +2003-04-09 Mr. Sam <mrsam@courier-mta.com> + + * authlib/README.authpostgres.html: Documentation updates. + +2003-04-04 Mr. Sam <mrsam@courier-mta.com> + + * autoconf 2.57 fixes. + +2003-04-03 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045.c (content_location): Plug a leak. + + * unicode/unicode.c (unicode_xconvert): Plug a memory leak. + + * liblock/mail.c (ll_mail_free): Plug a memory leak. + +2003-03-22 Alain NAKACHE <alain@cal.fr> + + * authenticate_auth.c (authenticate): Add AUTHSERVICE functionality + to IMAP SASL, and POP3 plain+SASL authentication methods. + * pop3login.c (main): Ditto. + +2003-03-21 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirpurgetmp.c (maildir_purge): Fix maildir_purge("."); + +2003-03-20 Mr. Sam <mrsam@courier-mta.com> + + * rfc2047.c (rfc2047_encode_callback): Fix MIME encoding of "_". + +2003-03-19 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (imapscanfail): Explicit error message referring + to potential fam daemon problems. + (imapscanfail): Include login id in error message. + + * unicode/iso2022jp.c (read_jis_char): Fix various bugs that result + in crashes, as a result of invalid character sequences. + + +1.7.1 + +2003-03-11 Mr. Sam <mrsam@courier-mta.com> + + * rfc1035/configure.in: Grok Solaris 8 IPv6 header files. + +2003-03-10 "HÃ¥vard Lygre" <hklygre@online.no> + + * authlib/authmysqllib.c (auth_mysql_getuserinfo): Fix NULL ptr result + from mysql. + +2003-03-10 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (do_imapscan_maildir2): Create tmp file for the new + courierimapuiddb in the folder's tmp dir. + + * authlib/preauthvchkpw.c (auth_vchkpw_pre): Disable open_smtp_relay() + until fixed by authvchkpw devs. + +2003-03-07 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriertls.c (tls_transfer): Fix error recovery - an aborted + connection negotiation may result in an infinite loop with some + versions of OpenSSL. + +2003-03-06 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirpurgetmp.c (maildir_purge): Adjust quota when + autopurging Trash --with-trashquota. + + * rfc2045/configure.in: rename config.h to rfc2045_config.h + + * unicode/configure.in: rename config.h to unicode_config.h + +2003-03-01 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/reformime.c (read_message): Use rfc2045_parse_partial() to + properly size-up content without trailing newlines. + + * rfc2045/rfc2646create.c (rfc2646create_free): Emit the trailing + newline. + + * configure.in: Conditionally probe if SA_NOCLDWAIT could be used. + + * pcpd.c (start): Use SA_NOCLDWAIT, if blessed by configure. + +2003-02-28 Mr. Sam <mrsam@courier-mta.com> + + * storeinfo.c (do_copy_quota_calc): Fix quota calculation when + COPYing a deleted message. + +2003-02-26 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (savepop3dlist): Recover from out-of-disk-space + situation. + + * imapd.dist.in (IMAP_TRASHFOLDERNAME): Explain how purging works. + + * authlib/success.c (authsuccess): More clear error message. + +2003-02-22 Toshikazu Ichikawa <ichikawa@toshikazu.org> + + * unicode/iso2022jp.pl: iso2022jp update. + +2003-02-19 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_expunge): Autocreate INBOX.Trash only if + IMAP_MOVE_EXPUNGE_TO_TRASH is enabled. + +2003-02-17 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): Transform a DELETE of a shared folder + to an UNSUBSCRIBE. + (do_imap_command): STATUS won't autosubscribe to a shared folder. + + * authenticate_auth.c (send_auth_reply): Fix SASL response to empty + client input. + +2003-02-15 Mr. Sam <mrsam@courier-mta.com> + + * msgenvelope.c (msgenvelope): Default reply-to and sender + headers in ENVELOPE reply to the from header (partially reverting + 2002-08-26, which fixed Pine's misbehavior, but broke Mulberry). + +2003-02-12 Mr. Sam <mrsam@courier-mta.com> + + * INSTALL: Document that --with-trashquota breaks make check. + imap/configure.in: If any option is used which is known to break + make check, issue a friendly error message telling the organic + entity to rerun configure without those options. + + * imaplogin.c (main): Add [CAPABILITY] tag to the initial greeting. + + * imapd.c (do_imap_command): Recalculate maildir quota after + a DELETE or RENAME (new function: maildir_quote_recalculate). + +2003-02-09 dave@britiany.com + + * search.c (search_internal): Optimize SORT by not parsing the + entire message, when we only want the headers. + +2003-02-09 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirwatch.c: Fix timeout. + +2003-01-30 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045encode.c: clean up base64/qp encoding. + + * tcpd/configure.in: Handle kerberized openssl 0.9.7 + + * configure.in: Fix search path for binaries. + +1.7.0 + +2003-01-25 Mr. Sam <mrsam@courier-mta.com> + + * Final maildir creation revision. + + * authlib/authmysqllib.c (append_username): Same fix as authpgsqllib, + even though mysql does not need it. + +2003-01-23 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authpgsqllib.c (append_username): Escape 's too. + + * imapd.c (main): Implement IMAP UIDPLUS (RFC 2359) + (do_imap_command): Reset renamed folders' UIDVs. + + * imapd.dist.in (IMAP_CAPABILITY): Add UIDPLUS + +2003-01-20 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildircreate.c (maildir_tmpcreate_fd): Move maildir create + retry loop into maildir_tmpcreate_fd. + +2003-01-19 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildircreate.c: Removed old maildir creation code. + + * maildir/maildircreateh.c: Optimizations. + +2003-01-17 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (maildir_mkfilename): Fix maildir_mkfilename(). + + * storeinfo.c (copy_message): Use fixed maildir_mkfilename(). + + * imapscanclient.c (imapscan_maildir): Convert to the new maildir + creation library. + (do_imapscan_maildir2): Ditto. + + * maildir/maildirshared.c: Convert to the new maildir creation + library. + +2003-01-16 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (savepop3dlist): Converted to the new maildir + creation library. + + * maildir/deliverquota: Initial implementation of new maildir + creation library. Converted deliverquota. + +2003-01-14 Mr. Sam <mrsam@courier-mta.com> + + * imap/storeinfo.c (copy_message): Use maildir_movetmpnew. + + * imap/imapd.c (store_mailbox): Use maildir_movetmpnew. + + * maildir/deliverquota.c (deliver): Use maildir_movetmpnew. + + * maildir/maildircreateh.c (maildir_try_create_hostname): Include + microseconds in message filename. + (maildir_movetmpnew): Encapsulate move from tmp to new by trying + link first, and only if it fails with exdev try rename. + + +2003-01-13 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (sortmsgs): Only update courierpop3dsizeslist when + necessary. + +2003-01-08 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (main): imapd --version prints my version. + + * pop3dserver.c: Create courierpop3dsizelist to cache POP3-sizes of + individual messages. + +2003-01-03 "Thomas T. Thai" <tom@minnesota.com> + + * Custom query patch for authpgsql + +1.6.2 + +2002-12-23 Mr. Sam <mrsam@courier-mta.com> + + * configure.in: Use ulimit -v instead of ulimit -d, if available. + + * rfc2047.c (rfc2047_encode_callback): Fix hang on + locales where isspace(U+0x00A0) is true. + +2002-12-20 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): Clarify DELETE error message. + (do_imap_command): Prohibit RENAME of currently open mailbox. + +2002-12-18 Mr. Sam <mrsam@courier-mta.com> + + * imap: move hier_rename into libmaildir.a, for recycling. + + * testsuite: Use 'touch -t' + +2002-12-11 Brian Candler <B.Candler@pobox.com> + + * tcpd/tcpd.c: couriertcpd -denymsg option + +2002-12-11 John D. Rowell <jdrowell@exerciseyourbrain.com> + + * md5/md5_hash.c: Rename md5_has to md5_hash_courier (namespace + conflict with postgres). + +2002-11-25 Mark Anthony Lisher <markal@markal.net> + + * imaptoken.c (nexttoken_nouc_okbracket): LOGIN parsing patch. + +2002-11-23 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in: Include UTF-8, now that new Red Hat installs + use UTF-8. + +2002-11-23 John Morrissey <jwm@horde.net> + + * Move quota warning code from deliverquota to libmaildir.a, + to be reused by maildrop. + +2002-11-17 Mr. Sam <mrsam@courier-mta.com> + + * unicode/mkultcase.pl: Fix titlecase mapping. + + * unicode: all unicode functions now receive the ptr to the unicode + structure as their first argument. Added a flags field to the unicode + structure to describe charset properties (multibyte, utf, uses shift + sequences...) + +2002-11-17 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.sysvinit.in: Fix pop3 cert file check. + +1.6.1 + +2002-11-07 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (imapscan_maildir): Recover if FAM dies. + +2002-10-31 Aki Immonen <aki@golftalma.fi> + + * imapd.c (main): Configurable Trash Folder Name. + +2002-10-29 Mr. Sam <mrsam@courier-mta.com> + + * courier.spec.in (BuildPreReq): Fix build prereqs. + +1.6 + +2002-10-25 Mr. Sam <mrsam@courier-mta.com> + + * pop3dcapa.c (pop3dcapa): It's STLS. + + * imap/pop3 - fixed initscripts, again. + +2002-10-23 Mr. Sam <mrsam@courier-mta.com> + + * thread.c (printthread): Fix THREAD REFERENCES. + +2002-10-15 Mr. Sam <mrsam@courier-mta.com> + + * authlib/debug.c (auth_debug_login_init): Make it work if + DEBUG_LOGIN_ENV is not set. + +2002-10-14 Olivier Girondel <olivier.girondel@cw.com> + + * authlib/authldaplib.c (authldap_read_config): LDAP_FILTER fix. + +2002-10-13 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (imapenhancedidle): Added libfam-based IDLE implementation. + Updated man page. + +2002-10-07 Mr. Sam <mrsam@courier-mta.com> + + * Major toolchain upgrade - gcc 3.2, automake 1.6, autoconf 2.53, + libtool 1.4. + +2002-10-03 Mr. Sam <mrsam@courier-mta.com> + + * maildir/*.c: various cleanups. + +2002-10-02 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (dirsync): new configure switch --with-dirsync. + +2002-09-30 Ragnar Kurm <ragnar@uninet.ee> + + * Added DEBUG_LOGIN_ENV to imap and pop3 config files that produces + additional login diagnostics + +2002-09-28 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (noop): Return updated message flags if called within IDLE, + even with workaround enabled. + + * courier-imap.spec.in: add fam-devel to build-prereq. + + * IMAP_USELOCKS: Switch from flocks to dotlocks. Time out stale + network dotlocks after 120 seconds. Use FAM, if available, while + waiting on a dotlock file to go away. Update config file comments. + + * maildir/maildirwatch.[ch]: Abstraction layer wrapping SGI's + File Alteration Monitor, if it's available. + + * imapd.c (do_imap_command): Handle IDLE in authenticated state as, + essentially, a NOOP. Enable IDLE in the default CAPABILITIES + string (but keep the same version string, so sysconftool will not + touch an existing CAPABILITIES setting on an upgrade). + +2002-09-21 Mr. Sam <mrsam@courier-mta.com> + + * autobloat: check for gmake, check for gcc in top level makefile. + +2002-09-20 Mr. Sam <mrsam@courier-mta.com> + + * msgbodystructure.c (msgbodystructure): Update for the RFC 2231- + related changed in librfc2045.a + +2002-09-14 Mr. Sam <mrsam@courier-mta.com> + + * pop3login.c (main): Allow spaces in POP3 login id and password. + +2002-09-10 Alessandro Vesely <vesely@tana.it> + + * authlib/authdaemond.c: Fix error logging. + +2002-09-09 Mr. Sam <mrsam@courier-mta.com> + + * search.c (fill_search_veryquick): Fix search for \Draft + +2002-08-28 Mr. Sam <mrsam@courier-mta.com> + + * pop3login.c (starttls): For completeness sake, implement POP3 STLS. + +2002-08-26 Mr. Sam <mrsam@courier-mta.com> + + * msgenvelope.c (msgenvelope): Do not default reply-to and sender + headers in ENVELOPE reply to the from header, and do not default + the name to the address if the name is not specified for an address. + +2002-08-19 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authdaemond.c (start): After 5 mins of inactivity call + the newly-defined module 'idle' function. + +2002-08-13 Brian Candler <B.Candler@pobox.com> + + * Makefile.am: cleanup + +2002-08-12 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriertls.c: fix error reporting (esp at shutdown) + + * maildir/maildirsearch.h: added a C++ wrapper. + + * imap/utf8.c: cleanup, exposed internal unicode-to-utf8 conversion + function. + +2002-08-11 Mr. Sam <mrsam@courier-mta.com> + + * imap: cleanup: extracted text search macros into a separate module + in the maildir directory (for potential code reuse). + +2002-08-11 Brian Candler <B.Candler@pobox.com> + + * initscripts/*.in: fix paths. + +2002-08-08 Mr. Sam <mrsam@courier-mta.com> + + * rfc2047.c (rfc2047_encode_callback): Fix MIME encoding of words + with = and ? characters. + +1.5.3 + +2002-08-07 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/libcouriertls.c (tls_destroy): it looks like SSL_CTX_free + might invoke the ssl/tls cache call. Free tls_info after + SSL_CTX_free comes back. Explicitly call SSL_CTX_flush_sessions to + work around a bug in OpenSSL which blows away application data prior + to calling SSL_CTX_flush_sessions in SSL_CTX_free(). + +2002-08-05 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in: Fix %is_not_mandrake. + +1.5.2 + +2002-08-01 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tcpd.c (sighup): Reestablish handler for SIGHUP, after catching + it. + +2002-07-26 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Fix invocation of pg_config. + + * authlib/authsyschangepwd.c (dochangepwd): Call setsid() and + setlogin() on OpenBSD (make webmail passwd change work). + +2002-07-15 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tlscache.c (doadd): Fix tlscache file corruption on platforms + where sizeof(off_t) != sizeof(size_t). + +2002-07-14 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirquota.c (maildir_quota_delundel_end): Typo fix. + + * libcouriertls: By George, I think I've got it. Implement + orderly SSL/TLS session shut down. The libcouriertls library is now + fully event-driven; SSL/TLS errors are properly reported; an error + is reported if the SSL/TLS connection was closed without an orderly + shut down. + +2002-07-13 Mr. Sam <mrsam@courier-mta.com> + + * libcouriertls: The saga continues. Suppress whining on + SSL_ERROR_ZERO_RETURN + +2002-07-12 Mr. Sam <mrsam@courier-mta.com> + + * couriertls: more cleanup, better error reporting. + + * couriertls: Fix tlscache make check + +2002-07-09 Mr. Sam <mrsam@courier-mta.com> + + * Renamed logger to courierlogger + +2002-07-07 Yu Kobayasi <mail@yukoba.jp> + + * shift-JIS encoding. Search in iso-2022-jp and shift-JIS. + +2002-07-02 "Chris Rodgers" <courier-imap@bulk.rodgers.org.uk> + + * Patch to let the server run on Cygwin. Modified, untested, + patch uses AC_CANONICAL_HOST to replace the ':' character with '!' + for -cygwin targets. + +2002-06-30 Mr. Sam <mrsam@courier-mta.com> + + * fetch.c (dofetchmsgbody): Fix BODY[n] for message/rfc822 MIME + sections. + +2002-06-27 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in: some tweaks for mandrake. + +2002-06-26 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldap: fix a parsing bug that causes a crash if + LDAP_MAILROOT is left undefined. + +2002-06-23 Mr. Sam <mrsam@courier-mta.com> + + * couriertls: code cleanup of the SSL/TLS wrapper. Code cleaned up + and modularized as libcouriertls.a, with a mini-API library. + Implemented SSL/TLS session caching. + + * imapd.dist, pop3d.dist: new configuration settings for the + SSL/TLS session cache file. + +2002-06-27 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaplib.c: Fix null ptr deref in new LDAP_MAILROOT + code. + +1.5 + +2002-06-13 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirquota.h: Renamed maildirsize struct member to + maildirsizefile - some compiler is having a cow about a struct member + having the same name as the struct. + +2002-06-12 Mr. Sam <mrsam@courier-mta.com> + + * outbox.c (check_outbox): Bug fix. + + * maildir/maildirquota.c (do_maildir_openquotafile): Fix quotas + on FreeBSD (fcntl("/dev/null", F_SETFL) doesn't work on FreeBSD) + + * authlib/cryptpassword.c: Added missing include of stdlib.h + +2002-06-11 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaprc: Added LDAP_MAILROOT + +2002-06-10 Mr. Sam <mrsam@courier-mta.com> + + * outbox.c (check_outbox): More detailed error reporting. + +2002-06-09 Mr. Sam <mrsam@courier-mta.com> + + * numlib/strofft.c: off_t may be negative. + + * +++ maildirquota API update +++ + + + allows documented way to change the set quota on a maildir + + + major internal cleanup, established a sane API library + + External changes: + + + quota no longer set by deliverquota or MAILDIRQUOTA, new -q + option to maildirmake. Both deliverquota, maildrop, and + Courier now read the maildirsize no matter what, and observe + the quota + + + maildirmake and deliverquota now installed by the Courier-IMAP, + maildrop, and sqwebmail standalone builds. + + + updated README.maildirquota, and man pages to reflect all these + changes. + +2002-06-06 Mr. Sam <mrsam@courier-mta.com> + + * thread.c (printos): Sort threads by starting thread date. + +2002-06-05 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (main): Check the sticky bit, just like IMAP. + +2002-06-05 James F.Hranicky <jfh@cise.ufl.edu> + + * imapd.c (main): Log to stderr if failed to chdir to the maildir. + +2002-06-02 Mr. Sam <mrsam@courier-mta.com> + + * outbox.c: Added #include <signal.h> + +2002-05-29 Mr. Sam <mrsam@courier-mta.com> + + * outbox.c: Added the OUTBOX feature. + +2002-05-26 Ron van den Dungen <ron@dse.nl> + + * authlib/authmysqllib.c (do_connect): Fix server connect via + filesystem socket. + +1.4.6 + +2002-05-22 Mr. Sam <mrsam@courier-mta.com> + + * fetch.c (dofetchmsgbody): Fix BODY[x.MIME] replies for + message/rfc822 sections. + +2002-05-20 Mr. Sam <mrsam@courier-mta.com> + + * rfc822_parsedt.c (rfc822_parsedt): Ignore obviously invalid years + (someone else can worry about Y10K). + +2002-05-09 Mr. Sam <mrsam@courier-mta.com> + + * authlib/Makefile.am (libauth-modules): Get rid of some cruft in + the Makefile. + +2002-05-08 Norihisa Washitake <nori@washitake.com> + + * iso-2022-jp charset update + +2002-05-07 Keith T. Garner <kgarner@kgarner.com> + + * authlib: Additional LDAP authentication filter. + +2002-05-07 John Morrissey <jwm@horde.net> + + * authlib: Solaris LDAP fix. + +1.4.5 + +2002-04-30 Mr. Sam <mrsam@courier-mta.com> + + * unicode/big5.c (c2u): Fixed a crash caused by invalid big5 chars. + +2002-04-17 Norihisa Washitake <nori@washitake.com> + + * iso-2022-jp unicode map. + +2002-04-07 Mr. Sam <mrsam@courier-mta.com> + + * rfc822/rfc822_mkdate.c (rfc822_mkdate_buf): Explicit (int) cast gets + the file compiled under Cygwin. + +2002-04-06 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): If SELECT fails, close the old folder + anyway. + +1.4.4 + +2002-03-26 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaplib: Added an option to implement two-stage + LDAP lookups, for authentication purposes. + +2002-03-22 Mr. Sam <mrsam@courier-mta.com> + + * courier/doc/FAQ.html: Added Tru64 gmake check failure. + +2002-03-21 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Fix broken vpopmail_home test. + +2002-03-14 Brian Candler <B.Candler@pobox.com> + + * maildir/maildirmkdir.c (maildir_mkdir): Create tmp subdir last, + when creating a maildir. + +2002-03-13 James Collier <james.collier@fifthweb.net> + + * tcpd/starttls.c (ssl_verify_callback): Fix client peer certificate + check. + +2002-03-06 Mr. Sam <mrsam@courier-mta.com> + + * waitlib/configure.in: Fix check for sighold() function. + +2002-03-01 "Peter C. Norton" <spacey-courier@lenin.nu> + + * Added authlib/README.authpostgres.html + +2002-03-01 Mr. Sam <mrsam@courier-mta.com> + + * fix configure and makefile scripts for autoconf 2.52 and automake 1.5 + +2002-02-27 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Replace test ~vpopmail (home directory) with + a Perl script (~username not supported by Solaris's sh). + +1.4.3 + +2002-02-25 Mr. Sam <mrsam@courier-mta.com> + + * imap/imapd.c: fix GETQUOTAROOT response + + * imap/testsuite: dump imapd output to a file instead of /dev/null, + BSD's fcntl(O_NONBLOCK) chokes on /dev/null. + +2002-02-24 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c: fix UIDNEXT in the STATUS response to take into + account new mail. + +2002-02-20 Mr. Sam <mrsam@courier-mta.com> + + * couriertls, imapd: set socket into non-blocking mode, to correctly + implement POSIX select() semantics. + +2002-02-17 Mr. Sam <mrsam@courier-mta.com> + + * searchinfo.c: allow numerical SEARCH strings without quoting. + +2002-02-15 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: Link against $CRYPTLIBS when probing for + open_smtp_relay() + +2002-02-02 Mr. Sam <mrsam@courier-mta.com> + + * rfc1035: Fix rfc1035search functions if the original rfc1035 + library call did not use a FAQDN. + +2002-02-02 Ken Jones <kbo@inter7.com> + + * authvchkpw update: vpopmail 5.2 + +2002-01-28 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c (do_imapscan_maildir2): Fix uninitialized nextuid + variable when folder without courierimapuiddb is opened read-only. + +2002-01-25 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045.c (doline): Fix incorrect calculation of the + end of a multipart MIME section that's inside another multipart + MIME section. + +1.4.2 + +2002-01-17 Bob Pepin <bob@gms.lu> + + * thread.c (printthread): Fix THREAD REFERENCES. + +2002-01-12 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaplib.c (auth_ldap_do): Escape punctuation in + userid string. + +2002-01-08 Tomas Fasth <tomas@euronetics.se> + + * imapd.c (imapidle): Call noop() before entering IDLE. + +2002-01-08 Oliver Hitz <oliver@net-track.ch> + + * authlib/Makefile.am (libauth-modules): Fix Makefile build + with no authentication modules selected. + +2002-01-07 Pawel Wilk <siefca@kernel.pl> + + * authlib/authmysqllib.c: Major update to the authmysql driver + that adds the option to create hand-crafted SQL queries. + +1.4.1 + +2001-12-28 Iustin Pop <iusty@intensit.de> + + * authldap: if LDAP_TLS and LDAP_AUTHBIND were enabled, use TLS for + the authenticated bind also. + +2001-12-23 Mr. Sam <mrsam@courier-mta.com> + + * Converted couriertls and couriertcpd man pages to Docbook. + +2001-12-22 Mr. Sam <mrsam@courier-mta.com> + + * authpam: Fix failover to the next auth module if userid not found. + +2001-12-08 Mr. Sam <mrsam@courier-mta.com> + + * Converted maildir documentation to Docbook SGML + +2001-12-07 Mr. Sam <mrsam@courier-mta.com> + + * search.c (fill_search_header): Fix THREAD REFERENCES. + +1.4.0 + +2001-12-04 Mr. Sam <mrsam@courier-mta.com> + + * mailboxlist.c (do_mailbox_list): Get rid of an illegal free() + triggered by LIST #allfolders. + +2001-12-01 Mr. Sam <mrsam@courier-mta.com> + + * imap: convert imapd documentation to Docbook SGML + +2001-11-28 Bill Shupp <hostmaster@shupp.org> + + * imaplogin.c (do_imap_command): Added AUTHSERVICE configuration + setting. + +2001-11-27 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmod.c (authmod_success): Adjust error reporting after + a failed exec(). + +2001-11-25 John Morrissey <jwm@horde.net> + + * authlib/authldaplib.c: make user uid/gid optional, default to global + uid/gid. + +2001-11-24 Mr. Sam <mrsam@courier-mta.com> + + * Begin conversion of man/html documentation to Docbook SGML. + Created a docbook directory in CVS, and added it to all modules. + This directory won't get packaged into tarballs, the tarballs will + have just the compiled man and html documentation, and the docbook + directory (module name 'docbook-scripts') will contain only the + scripts to convert SGML to HTML and MAN. + + * Converted authlib and userdb man/html pages to sgml. + +2001-11-23 Mr. Sam <mrsam@courier-mta.com> + + * msgenvelope.c (read_header): Suppress \r-s from ENVELOPE + reply, because of Outlook. + +2001-11-23 Brian Candler <B.Candler@pobox.com> + + * pop3dserver.c: Reply with -ERR if maildir does not exist. + +2001-11-18 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaplib.c (auth_ldap_do): Fix incorrect soft/hard + error indication for a failure in ldap_search_st(). + +2001-11-17 Mr. Sam <mrsam@courier-mta.com> + + * configure.in (all): use a different test for -lnsl and -lsocket + that works on BSD/I. + +2001-11-13 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (append_flags): Use maildir flag D for Drafts + +2001-11-13 "Oliver Blasnik" <oliver.blasnik@nextra.de> + + * Partial support of the IMAP QUOTA extension. + +1.3.12 + +2001-11-03 Jeff King <peff@peff.net> + + * STATUS won't clear the \Recent flag. + +2001-11-03 Abhijit Menon-Sen <ams@wiw.org> + + * rfc2045/rfc2045.c (rfc2045_free): Plug a memory leak. + +2001-11-01 Mr. Sam <mrsam@courier-mta.com> + + * clean up rfc1035, md5, sha1, libhmac, configure scripts. + +2001-10-14 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in: sqwebmail.spec: stub out call to libtoolize in + configure.in - messes up RH 7.1 builds + +2001-10-07 Mr. Sam <mrsam@courier-mta.com> + + * msgenvelope.c (msgappends): If we find illegal 8-bit header content, + re-encode it using MIME encoding that specifies x-unknown charset. + +2001-10-06 Vittorio Ballestra <vittorio.ballestra@infogestnet.it> + + * Added experimental PostgreSQL authentication module. + +2001-10-01 Mr. Sam <mrsam@courier-mta.com> + + * Parser: allow [ and ] characters in search strings. + +2001-09-27 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap: fix INSTALL_SCRIPT in the top level Makefile + +2001-09-22 Mr. Sam <mrsam@courier-mta.com> + + * storeinfo,c: return UID in FETCH response to a UID STORE. Problem + noted by Nic Ferrier <nferrier@tf1.tapsellferrier.co.uk> + +2001-09-18 Mr. Sam <mrsam@courier-mta.com> + + * Enigma update - rebuild autoconf/automake. + +1.3.11 + +2001-09-10 HIROSHI OOTA <oota@LSi.nec.co.jp> + + * imapscanclient.c (fnamcmp): Explicitly sort maildir filenames + by timestamp value first. + +2001-09-05 Mr. Sam <mrsam@courier-mta.com> + + * testsuitefix.pl: Sort expected LSUB output, in addition to LIST. + +2001-08-30 Mr. Sam <mrsam@courier-mta.com> + + * fetch.c (dofetchmsgbody): Correctly find message/rfc822 inside + another message/rfc822. + +2001-08-29 James Knight <jknight@fuhm.net> + + * search.c (search_evaluate): Speed up searches by correcting an + inefficient evaluation search order. + +2001-08-26 Mr. Sam <mrsam@courier-mta.com> + + * Added unicode mappings for windows-874/tis-620. Refresh to + Unicode 3.1.1 + +2001-08-25 Lars Uffmann <lu@mediaways.net> + + * imapd.c (do_imap_command): Rename entire folder hierarchy even if + no trailing "." + +1.3.10 + +2001-08-15 Mr. Sam <mrsam@courier-mta.com> + + * authldap: Added experimental LDAP_TLS option. + + * logger: use LOG_WARNING and LOG_ALERT for WARN: and ALERT: messages. + + * couriertcpd: added -warn option - warn message logged when number + of connections exceeded. Alert message logged when number of + connections is at its maximum. + +2001-08-12 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldaplib.c (ldapopen): LDAP_OPT_DEREF is not available + in openldap 1.0 + +2001-08-10 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tcpd.c (run): Add perror() after a failed exec. + +2001-08-07 Mr. Sam <mrsam@courier-mta.com> + + * Fix --with-random configure.in option. Problem noted by William + Hue <williamhue@telus.net> + +1.3.9 + +2001-08-06 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirshared.c (maildir_shared_subscribe): Fix a bug in + shared folder subscribe logic. + Problem noted by Vojtech Karny <karny@datalite.cz> + + * maildir/maildirmake.c (add): Explicitly fseek() to start of file, + for system where fopen("a+") initially positions to EOF. + Problem noted by Vojtech Karny <karny@datalite.cz> + +2001-08-05 Mr. Sam <mrsam@courier-mta.com> + + * Re-sync with the Courier tree (some gcc3 compilation fixes, + authdaemon now always built by default). + +2001-08-01 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysql.h: Drop mysql/ prefix from paths to mysql header + files (should be included in mysql_config). + +2001-07-29 Mr. Sam <mrsam@courier-mta.com> + + * imapd.rc.in, pop3d.rc.in: - source the non-ssl config file after the + ssl config file. + +2001-07-25 inter7.com + + * Update authvchkpw module. + +2001-07-24 Mr. Sam <mrsam@courier-mta.com> + + * imaplogin.c (do_imap_command): Generate an error message after + a SASL authentication failure. + +2001-07-24 Christophe Sollet <csollet@coleebris.com> + + * authlib/authldaplib.c (authldap_read_config): Add LDAP_DEREF option + to authldaprc that sets the LDAP_OPT_DEREF option. + +2001-07-24 Mr. Sam <mrsam@courier-mta.com> + + * maildir/maildirquota.c (qcalc): Prevent a division by 0 if + someone specified a quota of 0. + +2001-07-13 Mr. Sam <mrsam@courier-mta.com> + + * imapd.dist.in (AUTHMODULES_ORIG): Fixed typo noted by + kherron@newsguy.com + +2001-07-07 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authdaemond.c (start): close stdin/stdout/stderr after + becoming a background process. + +2001-07-03 Mr. Sam <mrsam@courier-mta.com> + + * imap, pop3, webmail, ldap, mysql: minor changes to the default + settings in associated configuration files, to accomodate webadmin. + Be sure to verify your system configuration after doing make + install-configure + +2001-07-01 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldap.schema: Added - a sample LDAP schema. + +2001-06-25 Mr. Sam <mrsam@courier-mta.com> + + * Disable MSIE 6.0 smart tags in all html files + +2001-06-23 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/tcpd.c (doit): Fix initialization of socklen_t. + +2001-06-22 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): Add TRYCREATE response to APPEND + (modified version of Olivier Girondel <ogirondel@isdnet.net>'s + patch). + +2001-06-22 Matthias Andree <ma@dt.e-technik.uni-dortmund.de> + + * pop3login.c (main): Do not abort after a SASL failure. + +2001-06-22 Mr. Sam <mrsam@courier-mta.com> + + * configure.in: SCO needs -lsocket for inet_addr(). + +1.3.8.2 + +2001-06-13 "Sergei V. Rozinov" <rvs@monster.icc.ru> + + * tcpd/tcpd.c (doit): Fix uninitialized arg to accept(). + +2001-06-06 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (emptytrash): The IMAP_EMPTYTRASH setting can now + be set to automatically purge multiple folders. Loosely based + on a suggestion by John Morrissey <jwm@horde.net>. + + * liblock/lockdaemon.c: fix several improper tests for failed fopen(). + +2001-05-20 "John A. Barbuto" <jbarbuto@bizland-inc.com> + + * Add quota support to authmysql. + +2001-05-12 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authlib.html.in: authldap and authmysql are now battle- + tested, and are no longer marked "experimental". + + *.dist.in: Document ability to listen on multiple ports. Bump + the version tag of the PORT/SSLPORT setting, since it's not + backwards/forwards compatible (and let sysconftool do its job). + +2001-05-11 Mr. Sam <mrsam@courier-mta.com> + + * couriertcpd: reworked code to create and accept connection on + multiple sockets. If succeed in creating a wildcard IPv6 socket, + try to create a wildcard IPv4 socket on the same port (xBSD's + stack does not accept IPv4 connections on wildcard IPv6 sockets). + +1.3.8.1 + +2001-05-10 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (is_reserved): Remove INBOX.Drafts from list of reserved + folders. + +1.3.8 + +2001-04-24 "Roland Hänel" <rh@ginko.net> + + * authlib/authmysql: applied patch to replace the remaining + hardcoded mysql table field names with configurable values from + authmysqlrc. + +2001-04-19 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (get_flagname): Fix spelling of the experimental \Draft + flag. + + * msgenvelope.c (doenva): Fix garbage ENVELOPE reply for certain + corrupted mail headers. + +2001-04-18 Mr. Sam <mrsam@courier-mta.com> + + * authlib: added sha1 cipher, configured CRAM-SHA1 authentication. + + * imapd-ssl.dist.in (TLS_STARTTLS_PROTOCOL): Created this setting + that'll be used instead of TLS_PROTOCOL for the IMAP STARTTLS + command. Ditto for POP3 + +2001-04-17 Mr. Sam <mrsam@courier-mta.com> + + * tcpd/starttls.c (create_tls): Log an error if + PEM_read_bio_DHparams() call fails. + + * rfc822.c (rfc822t_alloc): Explicitly cast arg to (void *). + + * authlib/configure.in (AUTHLDAP): Test for -lresolv before -lber. + +2001-04-14 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authldap: minor fixes for OpenLDAP 2.0.7. Suppress a + spurious ldap_get_values msg. Fail authentication completely if LDAP + server is unreachable (installing of falling over to the next + authentication mode). IMPORTANT: some people might be relying on + this behavior to fail over to another authentication module. Make + sure to note this in release notes. + +2001-04-13 Mr. Sam <mrsam@courier-mta.com> + + * couriertls: reformat information returned by -printx509. Break + down X.509 subjects by field, also provide information on the + negotiated cipher. + +2001-04-12 Mr. Sam <mrsam@courier-mta.com> + + * SSL simplification project: Replace TLS_PEERCERTDIR and TLS_OURCACERT + with a single TLS_PEERCERTS setting. + + * Cosmetic fixes. Replace // with /* */ comments in some .c files + and replace return of void datatype with an explicit return. + Other misc stuff too. + +2001-04-11 Mr. Sam <mrsam@courier-mta.com> + + * userdb/makeuserdb.html.in: Fix some documentation typos. + + * authlib/authldaplib.c: refuse to authenticate if we end up running + as uid 0 or gid 0, this indicates a config file problem. + + * courier-imap.spec.in: update for RPM 4.0.2. Fix broken ldap and + mysql subpackaging. + +2001-04-10 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (imapidle): Renamed idle() to imapidle(), to avoid clashing + with libc5. + +2001-04-08 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysqllib.c (auth_mysql_getuserinfo): Better error + recovery when the mysql server goes down (from + oliver.blasnik@nextra.de). + +2001-04-07 Mr. Sam <mrsam@courier-mta.com> + + * rfc2045/rfc2045.c (rfc2045_mimepos): Fix a long-time glitch where + a garbled message with no body will have its headers logically placed + in the body section, and the supposed headers will be NULL -- this was + a benign artifact of the parsing logic. + + * Dropped TLS_ALLOWSELFSIGNEDCERTS option from config files - not + used by Courier-IMAP. + + * Added /usr/local/bin to AC_PATH macros in all configure.in scripts. + +2001-04-06 Alexei Batyr' <lehel@pcmag.ru> + + * imapd.c (get_message_flags): Fix reporting of \Draft flag. + +1.3.7 + +2001-04-01 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (get_message_flags): Fix potential NULL ptr deref. + +2001-03-26 Mr. Sam <mrsam@courier-mta.com> + + * authlib/preauthvchkpw.c: add configure script probe for the + existence of vlogauth() + +1.3.6 + +2001-03-23 Mr. Sam <mrsam@courier-mta.com> + + * Makefile.am: Fix RANDFILE in imapd.cnf and pop3d.cnf + + * search.c (is_in_set): Bugfix: make search 1:* work correctly. + +2001-03-16 Mr. Sam <mrsam@courier-mta.com> + + * rfc1035.h: drop include of netinet6/in6.h + +2001-03-13 Mr. Sam <mrsam@courier-mta.com> + + * index.html: add a link to the article in SecurityFocus + +2001-03-11 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c (acctout): Fix POP3 LOGOUT going out to syslog as + an error. + +2001-02-28 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authmysqllib.c (doconnect): Applied a modified version + of a patch by John Callaghan <jpc@msu.edu> that periodically + checks for a dead MySQL connection, and resets it. + +2001-02-28 Mark Anthony Lisher <markal@iname.com> + + * tcpd/starttls.c (dossl): read CERTFILE.ipaddress if it exists, to + allow multiple certs for multihomed SSL servers. + +2001-02-28 Tommi Virtanen <tv-nospam-42b34d@hq.yok.utu.fi> + + * imaptoken.c (do_readtoken): abort if client imap token was truncated + due to excessive size. + +1.3.5 + +2001-02-26 Tomas Fasth <tomas@euronetics.se> + * mainloop.c (mainloop): cut off clients that keep sending + junk, non-stop. + +2001-02-21 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (idle): Applied IDLE patch (Tomas Fasth), with some + modifications. + +2001-02-20 Mr. Sam <mrsam@courier-mta.com> + + * fetch.c (rfc822): Additional FETCH attributes need to be included + in transfer totals + +1.3.4 + +2001-02-18 Mr. Sam <mrsam@courier-mta.com> + + * maildir/deliverquota.c: replace snprintf with sprintf, for better + compatibility. + +2001-02-15 Mr. Sam <mrsam@courier-mta.com> + + * unicode/utf8.c (unicode_utf8_tou): Fixed memory corruption in + UTF8 module. + +2001-02-11 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): log number of bytes transferred from + headers and body (in LOGOUT and DISCONNECTED syslog messages). + + * pop3dserver.c (main): log number of bytes transferred by TOP and + RETR commands (in LOGOUT, DISCONNECTED, and TIMEOUT messages). + +2001-02-09 Mr. Sam <mrsam@courier-mta.com> + + * Patch: tobi@tobi.nu - replace --with-dyn-mysql with --with-mysql-libs + and --with-mysql-includes + +2001-02-08 Chris Seawood <cls@radiate.com> + + * authlib: Added check for open_smtp_relay in -lvpopmail + +2001-02-06 Mr. Sam <mrsam@courier-mta.com> + + * Makefile.am: remove -MAKEFLAGS + +2001-02-02 Mr. Sam <mrsam@courier-mta.com> + + * authlib/authpam.c: update for Linux-PAM 0.74 (get rid of + pam_set_item PAM_AUTHTOK). + + * imap/configure.in: probe for existence of /etc/pam.d/system-auth, + and use that instead of pam_pwdb.so + +2001-01-25 Mr. Sam <mrsam@courier-mta.com> + + * Added a hook for passing some additional flags to the + RPM spec script, using --define 'xflags [flags]' option. + +1.3.2 + +2001-01-25 Mr. Sam <mrsam@courier-mta.com> + + * Fix big5/gb2312 conversion logic. + +2001-01-19 Mr. Sam <mrsam@courier-mta.com> + + * Fix authldap connection failure recovery (Brian Candler) + +2001-01-16 Mr. Sam <mrsam@courier-mta.com> + + * Update INSTALL to reflect new upgrade instructions. + +1.3.1 + +2001-01-14 Mr. Sam <mrsam@courier-mta.com> + + * imaprefs.c: updates for thread-06.txt + +2001-01-13 Mr. Sam <mrsam@courier-mta.com> + + * authlib/configure.in: added --with-dyn-mysql option to specify + MySQL installation directory to dynamically link with vpopmail. + + * imaptoken.h (IT_MAX_ATOM_SIZE): Set to 16384. Should be enough + (UW-IMAP limits entire commands to 8192). + +2001-01-01 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c (do_imap_command): Do not expunge upon CLOSE of a read-only + mailbox. + + * Makefile: tweak testsuite to sort LIST output (meant to do that + a while ago). + +2000-12-27 Mr. Sam <mrsam@courier-mta.com> + + * tcpd.c: Fixed a compilation error in --without-tcpddns option. + +2000-12-25 Mr. Sam <mrsam@courier-mta.com> + + * deliverquota: optionally deliver a warning message to the maildir + when quota is about to be exceeded. + +1.3.0 + +2000-12-22 Mr. Sam <mrsam@courier-mta.com> + + * reftest.c: fix dependency on qsort order. + +2000-12-19 Mr. Sam <mrsam@courier-mta.com> + + * courier-imap.spec.in: add -q flag to %setup to suppress lots of + stuff we don't care for. + + * unicode/README tells you how to go about adding code for additional + character sets. + +2000-12-16 Mr. Sam <mrsam@gwl.email-scan.com> + + * rfc2045: boundary= in a non-multipart MIME section would screw + things up. + +2000-12-15 Mr. Sam <mrsam@courier-mta.com> + + * unicode: add gb2312 encoding. + +2000-12-12 Mr. Sam <mrsam@courier-mta.com> + + * unicode: add big5 encoding. + +2000-12-11 Mr. Sam <mrsam@courier-mta.com> + + * imapd.c: Call setlocale() to make sure we use ASCII ctype + conversions. + + * thread/sort: some logic adjustments. + +2000-12-10 Mr. Sam <mrsam@gwl.email-scan.com> + + * Added sysconftool support. + +2000-12-04 Mr. Sam <mrsam@courier-mta.com> + + * authldaplib.c - fix check for openldap V2. + +2000-11-30 Mr. Sam <mrsam@courier-mta.com> + + * Make LDAP_DOMAIN optional. + +2000-11-29 Mr. Sam <mrsam@courier-mta.com> + + * pop3login.c (main): remove " " from terminating token for PASS. + + * pop3dserver.c: Fix error handling of bad message numbers+others. + +2000-11-27 Mr. Sam <mrsam@courier-mta.com> + + * pop3dserver.c: Fix a bug in TOP command. + + * imapd.c: Implement hierarchical folder rename. + +2000-11-26 Mr. Sam <mrsam@courier-mta.com> + + * Migrated rfc822t_alloc() with rfc822t_alloc_new(). + +2000-11-23 Mr. Sam <mrsam@courier-mta.com> + + * authenticate_auth.c: missing read_eol() breaks multiresponse SASL + methods. + +2000-11-20 Mr. Sam <mrsam@courier-mta.com> + + * preauthvchkpw.c update. + +1.2.3 + +2000-11-15 Mr. Sam <mrsam@courier-mta.com> + + * Plug a file descriptor leak in quota update code. + +2000-11-14 Mr. Sam <mrsam@courier-mta.com> + + * imapscanclient.c: patch to ignore write failures to the IMAP UID + file. + + * thread.c/testsuite*: updated to draft-ietf-imapext-thread-04 + (case-sensitive message-id comparison). + + * ChangeLog: converted to emacs format. + + Added support for SASL PLAIN authentication. + +1.2.2 + Minor fixes -- bad stat in maildir_try_create, move chdir to homedir + after setting uid/gid. + + Fixed OpenLDAP memory leak. + +1.2.1 + + Fixed typo in label of authvchkpw module. + + Fixed ENVELOPE to properly handle legacy RFC-822 group list + notation. + +1.2 + Authentication overhaul. authdaemond split into alternate versions, + one for each database back end. The original "authdaemond" replaced + by a shell script that checks for the installed authdaemond + alternates, and run whatever's installed. The idea is to allow + LDAP and MySQL support to be separately packaged, and for LDAP and + MySQL support to be added simply by installing the extra package + (the base packaged for the garden variety and the LDAP/MySQL back + end remains the same). See NEWS for more information, as well + as "Alternative authdaemond modules" section in INSTALL. + + "custom" authentication module - a stub for site-specific + authentication code. + + Bundled POP3 server from the main Courier package. The Courier-IMAP + now includes a compatible POP3 server (by popular request). + + POP3 over SSL support is included. + + Added character sets windows-1250 through windows-1258. + + Added character sets IBM437, IBM775, IBM850, IBM852, IBM855, IBM857, + IBM860 through IBM866, and IBM869. + + Optimized downloading of large attachments for Netscape Messenger. + +1.1 + + Use mysql_real_connect(), if available. + + Strip () from name of mail envelope sender that uses old-style header + format, by calling rfc822_getname instead of rfc822_gettok. + + Reengineer couriertcpd locking mechanism, that permits SO_REUSEADDR. + New locking mechanism uses liblock's daemon functions. Removed + --forcebind option, obsolete. Added --stop and --restart to couriertcpd. + + standalone: new startup scripts. Must manually stop the server if + upgrading. + + Fix a minor interoperability issue with Linux, IMAP_MOVE_EXPUNGE_TO_TRASH, + and sqwebmail (sqwebmail moves mail to trash by creating a hard link, + when we expunge we attempt to rename the first link to the second one, + in Linux rename() returns 0, but NOTHING happens). + + Bug fix -- bad BODYSTRUCTURE response when a multipart content-type + does not have any sections. + + THREAD semantics updated to ietf-imapext-thread-03. + + THREAD REFERENCES bug fixes. + + Add unicode-based support for non-English character sets. + +1.0 Performance tweaks. + + Spec file tweaked to build under RH 7.x + + IMAP_CHECK_ALL_FOLDERS option added. + + Implement IMAP SUBSCRIBE/UNSUBSCRIBE for private folders. + + More questions for the FAQ + + Reorder authentication modules. + + Subject stripping for THREAD and SORT updated to definition in + draft-ietf-imapext-thread-02.txt ... Except for the broken part. Should + be interesting to see if someone spots the obvious glaring error, there. + + Experimental THREAD REFERENCES implementation. + + Fixed a bug in THREAD ORDEREDSUBJECT which skipped over messages without + a subject header. + + Fixed a typo in IMAP_CAPABILITY -- should be THREAD= not THREAD-, oops. + +0.99 SSL related bug fixes. + + Option to use IMAP STARTTLS instead of IMAP over SSL. + + Tweaks to the configuration for better detection of MySQL support. + +0.36 + Berkeley DB 3 support. + + Complete rewrite of authmysql. New authmysql module supports CRAM-MD5 + authentication. + + CHILDREN extension. A compatibility switch for legacy behavior in order + to keep Pine happy. + + Creation of debug file is now controlled entirely by IMAPDEBUGFILE. + + Replaced dependency on stunnel with couriertls. + + +0.35 + Remove spurious space in LIST response. + + IPv6 fixes. + + Fix bug that can be used to crash authdaemon with malformed CRAM-MD5 + authentication requests. + + +0.34 + Implemented THREAD and SORT extensions. + + Set ulimit for the spawned processes. + +0.33a + Various minor bug fixes in authentication code. Many memory leaks + plugged. CRAM-MD5 with LDAP now works correctly. + +0.33 Added IMAP_MOVE_EXPUNGE_TO_TRASH. + + Added IPv6 support. + + Bug fixes. + + IA-64 patches. + + Slightly changed semantics of maildir file creation. Will abort on + errors other than ENOENT. DJB is wrong - if you keep looping + you'll get stuck in an infinite loop if, say, directory permissions + are wrong. Don't loop on non-ENOENT errors. Abort instead. + +0.32 Added the authdaemon module. Minor fixes for gnus and mutt related + issues. Global shared folders. + +0.31 Added SSL support, via stunnel (tested with stunnel 3.8). + Added a FAQ. + Found and fixed a potential crash caused by messages with corrupted + MIME headers. + +0.29-30 mysql patch applied. Fixed irrelevant typo in maildir_folderdir(). + authldap failed init bug fix. + + Fixed a bug that sent an incorrect terminating character if a partial + fetch ended precisely in the middle of a CRLF boundary. + + Miscellaneous bug fixes in miscellaneous authentication modules. + + Added workaround for Netscape crash if it receives an attachment + with a filename that contains backslashes. + + Added workaround for Pine's failure to quote userids and passwords + that contain brackets. + +0.28 Explicit check to block compilation as root. Compile Courier-IMAP as + non-root, then su to root before running make install. + +0.27 A new configure option: --enable-workarounds-for-imap-client-bugs + This enables a couple of workarounds for a bunch of stupid bugs in + several IMAP clients. + + Nicholas Lee's vchkpw2userdb patch. + + Modified RPM build script. authldap is not going to be explicitly + suppressed. If you have OpenLDAP client libraries installed, authldap + authentication is going to be built and installed. Remove authldap from + imapd.config if you don't want it. + + Shared folder support (weeeeeeeeeee...). Courier-IMAP will install a + slightly modified maildirmake command, for that purpose, and a manual + page. Read the manual page. + + Added beta MySQL module. + + Kill all current sessions when the listener process stops. + + If the home directory has the sticky bit set, don't log in - maintenance + lock. + +0.26 Enhancements to authldap and authvchkpw - now links with the vpopmail + library, so MySQL-based authentication is now supported! + +0.25a More tweaks to authlib - Makefile + authvchkpw. Fix bogus failure in + make check in certain timezones. + +0.25 A minor bug fix release. Fixed a core dump in authldap. Fixed some + spurious make check failures in gdbmobj/bdbobj. Other minor makefile + and configure changes. Major editing of INSTALL and README that will + hopefully make them easier to understand. + +0.24 Fixed several userdb bugs introduced in 0.23. Oops. + +0.23 Added experimental LDAP authentication support. Don't set \Seen on + fetch of RFC822.HEADER + +0.22 authvchkpw fix. Miscellaneous fixes for Outlook Express. Other minor + bug fixes. + +0.21 Trying to fix problems that I think are caused by some cranky glibc/libc + stdio implementations (seek errors, and such). Refreshed userdb/authlib + from RCS (changes fix bugs in code that Courier-IMAP never uses, but I + like to keep things synced up). Twiddled some docs. + + Added NAMESPACE capability. This removed several configuration steps. + +0.20 Additional enhancements to the authentication library -- implementation + of framework to support additional SASL forms of authentication. + Completed implementation of FETCH parameters that were left in a TO DO + state. Gave up, and made LIST=LSUB, for all intents and purposes. + +0.19 Added CRAM-MD5 support. Changed location of some installed files, in + order to better comply with common GNU standards. Take care when + upgrading: + + prefix/lib is pretty much now renamed as prefix/libexec + most stuff in prefix/bin is moved to prefix/sbin + imapd.config is now installed in prefix/etc, instead of prefix/lib + authentication modules are installed in prefix/libexec/authlib + + All the initialization and other scripts have been modified to look + for files in new locations. + + Several bug fixes. + +0.18 Many bug fixes for problems experienced on some systems due to type + mismatches. + Do not expunge on LOGOUT. + Changed Red Hat RPMS to default to automatically start imapd on + system bootup (previously, you had to manually edit imapd.config and flip + the switch) + +0.17 Potential tiny array overflow in redhat-crypt-md5.c, causing a failure + in md5test.c. Many configuration and portability changes. + Intentionally omitted code that complained about some bad commands + received from an IMAP client. Certain IMAP clients sometimes send + malformed commands, which we reject with an error, causing the client + to complain. Apparently some IMAP servers silently ignore this error. + I may put this code back later. + +0.16 Oops. Typo fixed in the userdb script. + +0.15 Removed AC_LANGCPLUSPLUS from some configure.in's. They were giving + some gccs some grief. Fixed authvchkpw so that it actually works as + advertised. + +0.14 Initial release diff --git a/imap/Makefile.am b/imap/Makefile.am new file mode 100644 index 0000000..46a1e41 --- /dev/null +++ b/imap/Makefile.am @@ -0,0 +1,175 @@ +# +# Copyright 1998 - 2008 Double Precision, Inc. See COPYING for +# distribution information. + +AM_CPPFLAGS=@CPPAUTH@ + +BUILT_SOURCES=README.proxy + +DISTCLEANFILES=imapd.pam pop3d.pam imapd.cnf pop3d.cnf +CLEANFILES=imapd.8 imapd.html mkimapdcert.html mkimapdcert.8 \ + courierpop3d.html courierpop3d.8 mkpop3dcert.html mkpop3dcert.8 + +EXTRA_DIST=testsuite testsuite.txt smaptestsuite smaptestsuite.txt \ + BUGS BUGS.html README README.html imapd.authpam \ + pop3d.authpam system-auth.authpam system-auth2.authpam\ + imapd.html.in imapd.8.in \ + mkimapdcert.html.in mkimapdcert.8.in \ + mkpop3dcert.html.in mkpop3dcert.8.in \ + courierpop3d.html.in courierpop3d.8.in \ + README.proxy README.proxy.html \ + imapd.cnf.gnutls pop3d.cnf.gnutls + +noinst_SCRIPTS=mkimapdcert mkpop3dcert +noinst_PROGRAMS=imaplogin imapd pop3login pop3d + +noinst_DATA=imapd.8 imapd.html imapd.cnf pop3d.cnf \ + mkimapdcert.html mkimapdcert.8 \ + mkpop3dcert.html mkpop3dcert.8 \ + courierpop3d.html courierpop3d.8 + +imapd.cnf: imapd.cnf.@ssllib@ + cp imapd.cnf.@ssllib@ imapd.cnf + touch imapd.cnf + +imapd.cnf: $(top_builddir)/config.status + +pop3d.cnf: pop3d.cnf.@ssllib@ + cp pop3d.cnf.@ssllib@ pop3d.cnf + touch pop3d.cnf + +pop3d.cnf: $(top_builddir)/config.status + +noinst_LTLIBRARIES=libimaplogin.la libimapd.la libpop3d.la +libimaplogin_la_SOURCES= +libimaplogin_la_LIBADD=../tcpd/libspipe.la ../tcpd/libtlsclient.la \ + ../numlib/libnumlib.la +libimaplogin_la_LDFLAGS=-static + +libimapd_la_SOURCES=mainloop.c imaptoken.c imaptoken.h imapwrite.c \ + imapwrite.h capability.c externalauth.c smap.c smapsnapshot.c +libimapd_la_LIBADD= ../rfc2045/librfc2045.la ../maildir/libmaildir.la \ + ../rfc822/librfc822.la ../liblock/liblock.la ../numlib/libnumlib.la \ + ../unicode/libunicode.la +libimapd_la_LDFLAGS=-static + +imaplogin_SOURCES=imaplogin.c authenticate_auth.c proxy.c proxy.h +imaplogin_DEPENDENCIES=libimapd.la libimaplogin.la +imaplogin_LDADD=libimapd.la libimaplogin.la \ + @LDAUTH@ -lcourierauth -lcourierauthsasl + +imapd_SOURCES=fetch.c fetchinfo.c fetchinfo.h imapd.c imapd.h \ + imapscanclient.c imapscanclient.h \ + mailboxlist.c mailboxlist.h \ + msgbodystructure.c msgenvelope.c \ + mysignal.c mysignal.h \ + outbox.c outbox.h \ + thread.c thread.h \ + search.c searchinfo.c searchinfo.h \ + storeinfo.c storeinfo.h + +imapd_DEPENDENCIES=libimapd.la \ + ../maildir/maildir.libdeps @dblibrary@ + +imapd_LDADD=libimapd.la `cat ../maildir/maildir.libdeps` \ + @dblibrary@ @DEBUGLIB@ @LDAUTH@ -lcourierauth + +pop3login_SOURCES=pop3login.c pop3dcapa.c proxy.c proxy.h +pop3login_DEPENDENCIES=../tcpd/libspipe.la ../tcpd/libspipe.la libpop3d.la +pop3login_LDADD=../tcpd/libtlsclient.la ../tcpd/libspipe.la libpop3d.la ../tcpd/libspipe.la @LDAUTH@ -lcourierauth -lcourierauthsasl @NETLIBS@ + +libpop3d_la_SOURCES=externalauth.c +libpop3d_la_LIBADD=../maildir/libmaildir.la ../rfc822/librfc822.la \ + ../numlib/libnumlib.la +libpop3d_la_DEPENDENCIES=$(libpop3d_la_LIBADD) +libpop3d_la_LDFLAGS=-static + +pop3d_SOURCES=pop3dserver.c pop3dcapa.c +pop3d_DEPENDENCIES=libpop3d.la +pop3d_LDADD=libpop3d.la @LDAUTH@ -lcourierauth + +HTML2TXT=links -dump -no-numbering + +README: README.html + $(HTML2TXT) README.html >README + +BUGS: BUGS.html + $(HTML2TXT) BUGS.html >BUGS + +imapd.html: imapd.html.in + ./config.status --file=imapd.html + +imapd.8: imapd.8.in + ./config.status --file=imapd.8 + +mkimapdcert.html: mkimapdcert.html.in + ./config.status --file=mkimapdcert.html + +mkimapdcert.8: mkimapdcert.8.in + ./config.status --file=mkimapdcert.8 + +mkpop3dcert.html: mkpop3dcert.html.in + ./config.status --file=mkpop3dcert.html + +mkpop3dcert.8: mkpop3dcert.8.in + ./config.status --file=mkpop3dcert.8 + +courierpop3d.html: courierpop3d.html.in + ./config.status --file=courierpop3d.html + +courierpop3d.8: courierpop3d.8.in + ./config.status --file=courierpop3d.8 + +if HAVE_SGML +imapd.html.in: imapd.sgml ../docbook/sgml2html + ../docbook/sgml2html imapd.sgml imapd.html.in + +imapd.8.in: imapd.sgml ../docbook/sgml2man + ../docbook/sgml2man imapd.sgml imapd.8.in + mv imapd.8 imapd.8.in + +mkimapdcert.html.in: mkimapdcert.sgml ../docbook/sgml2html + ../docbook/sgml2html mkimapdcert.sgml mkimapdcert.html.in + +mkimapdcert.8.in: mkimapdcert.sgml ../docbook/sgml2man + ../docbook/sgml2man mkimapdcert.sgml mkimapdcert.8.in + mv mkimapdcert.8 mkimapdcert.8.in + +mkpop3dcert.html.in: mkpop3dcert.sgml ../docbook/sgml2html + ../docbook/sgml2html mkpop3dcert.sgml mkpop3dcert.html.in + +mkpop3dcert.8.in: mkpop3dcert.sgml ../docbook/sgml2man + ../docbook/sgml2man mkpop3dcert.sgml mkpop3dcert.8.in + mv mkpop3dcert.8 mkpop3dcert.8.in + +courierpop3d.html.in: courierpop3d.sgml ../docbook/sgml2html + ../docbook/sgml2html courierpop3d.sgml courierpop3d.html.in + +courierpop3d.8.in: courierpop3d.sgml ../docbook/sgml2man + ../docbook/sgml2man courierpop3d.sgml courierpop3d.8.in + mv courierpop3d.8 courierpop3d.8.in + +README.proxy.html: README.proxy.sgml ../docbook/sgml2html + ../docbook/sgml2html README.proxy.sgml README.proxy.html + +README.proxy: README.proxy.html + $(HTML2TXT) README.proxy.html >README.proxy +endif + +check-am: + @test "@MAKECHECKBROKEN@" = "Y" || exit 0; echo "" ; echo "Error: --with-trashquota or the --enable-workarounds-for-imap-client-bugs" ; echo "option was specified to the configure script."; echo ""; echo "As INSTALL told you, make check fails if these options are used, and I wasn't"; echo "kidding when I wrote it. Reconfigure and rebuild without these options, then"; echo "rerun make and make check. If make check passes, reconfigure again with your"; echo "original options, and proceed with installing this server. Have fun!"; exit 1 + @cp /dev/null conftest1 ; chmod 000 conftest1 ; test -w conftest1 || \ + exit 0; echo "=============================" ; \ + echo "Do not run make check as root" ; \ + echo "=============================" ; exit 1 + @rm -f conftest1 + @chmod +x testsuitefix.pl + LC_ALL=C; export LC_ALL; $(srcdir)/testsuite | ./testsuitefix.pl | sort | cmp -s - $(srcdir)/testsuite.txt + LC_ALL=C; export LC_ALL; test "@smap@" = "yes" || exit 0; @SHELL@ $(srcdir)/smaptestsuite | ./testsuitefix.pl | sort | cmp -s - $(srcdir)/smaptestsuite.txt + rm -rf confmdtest + +testsuite-imap: + @LC_ALL=C; export LC_ALL; $(srcdir)/testsuite | ./testsuitefix.pl | sort + +testsuite-smap: + @LC_ALL=C; export LC_ALL; test "@smap@" = "yes" || exit 0; @SHELL@ $(srcdir)/smaptestsuite | ./testsuitefix.pl | sort diff --git a/imap/README.html b/imap/README.html new file mode 100644 index 0000000..3f9ab77 --- /dev/null +++ b/imap/README.html @@ -0,0 +1,256 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> + <title>IMAP Client configuration</title> + <meta name="MSSmartTagsPreventParsing" content="TRUE" /> +</head> + +<body text="#000000" bgcolor="#FFFFFF" link="#0000EE" vlink="#551A8B" +alink="#FF0000"> +<!-- Copyright 1998 - 2003 Double Precision, Inc. See COPYING for --> +<!-- distribution information. --> + +<h1>IMAP client configuration</h1> + +<p>Configuring IMAP clients can be tricky for any server. You have to know a +little bit about how the IMAP server works. Recommended Courier-IMAP +configuration for popular IMAP client software follows.</p> + +<h2>Pine (version tested: 4.10)</h2> + +<p>Older versions of Pine may require the <code>IMAP_OBSOLETE_CLIENT</code> +flag in the imapd configuration file. This setting disables the CHILDREN IMAP +extension, which breaks older versions of Pine. Current version of Pine do +not appear to have this problem.</p> + +<p>To configure Pine to use Courier-IMAP for the main INBOX, go to Pine's +main menu, select S)etup, then C)onfigure. Go down to "inbox-path" setting, +set it as follows:</p> + +<p><code>{<em>hostname</em>}INBOX</code></p> + +<p>"<em>hostname</em>" is the name of the server that's running Courier-IMAP. +Exit and restart Pine.</p> + +<h3>Folder configuration</h3> + +<p>Pine can also create and use folders with Courier-IMAP, instead of +creating mailbox files in <code>$HOME/mail</code>. Go to Pine's main menu, +select S)etup, then choose collectionL)ist.</p> + +<p>Choose A)ddcollection. Pick a name for this folder collection. Enter the +hostname of the Courier-IMAP server. For "Path", enter INBOX. Do not enter +anything for "View". Press ^X to save and exit.</p> + +<p>Now, when you select a folder listing from Pine's main menu, instead of a +folder list, you will see two "folder collections" in a menu. The first one +will be "Mail - Local folders in mail/". Open that folder collection, and you +will see your familiar Pine folders (note, you may also see INBOX there, +that's a bit misleading because your INBOX really comes from Courier-IMAP, +it's just that Pine always sticks INBOX into the first folder collection). +The second menu entry will be your Courier-IMAP folders. If you open it, +you'll see just one folder - "Trash". This folder is not really used by Pine, +so it can be ignored. You can now create your Courier-IMAP folders by +pressing A.</p> + +<h3>Shared folder access</h3> + +<p>If your Maildir INBOX has been previously linked, by +<code>maildirmake</code>, with one or more sharable maildirs, you can access +shared folders by repeating the previous procedure, but entering "shared" +into the "Path" field, instead of "INBOX" (of course, you'll have to pick +another name for this shared folder collection).</p> + +<p>Unfortunately, Pine lists *ALL* of your sharable folders by default. There +is, apparently, no individual subscribe/unsubscribe mechanism, by which you +get to choose which sharable folders you want to be listed.</p> + +<p>For the FAQ: <i>In a shared folder, "expunge" does not remove some deleted +messages, why?</i>. ANSWER: You can only delete your own messages in a shared +folder (owners of sharable maildirs can delete anyone's messages, though). +You may be able to mark the message as deleted, but you won't be able to +actually remove it.</p> + +<p>Also, when you delete your own messages, others will still see them until +you expunge.</p> + +<h3>Creating folders</h3> + +<p>The names of Courier-IMAP folders may not contain periods or slashes. +Periods are used as hierarchy delimiters - Courier-IMAP and Pine can create +folders within other folders.</p> + +<p>Choose A)dd folder, then enter "Work.Important". Instead of a new folder +being created, Pine will display a new entry "Work.". This is a subdirectory +of folders. Move the cursor and press Enter, and Pine will open the contents +of "Work", which will contain an empty folder "Important". Choose A)dd +folder, and create a folder named "Not Important". You now have two folders +shown, "Important" and "Not Important". You can go back to the root folder +collection by pressing '<'.</p> + +<p>When saving messages to folders in Pine, you can manually type in the +complete folder name, "Work.Not Important", or press Ctrl-T, then navigate +your folder hierarchy.</p> + +<p>You can only delete folders after you've deleted all messages from the +folder. If you display the folder collection listing, go to the "Work." +subdirectory and then delete both Important and Not Important folders, when +you return to the root folder collection, the Work. subdirectory will +automatically disappear. Subdirectories disappear automatically when all +folders in the subdirectory are deleted.</p> + +<h3>Folders containing both messages and subfolders.</h3> + +<p>It's possible to have both a folder named "Work", and a subdirectory named +"Work." that contains other folders, however Pine doesn't implement it very +well. You can go to the folder collections list, then add a folder named +"Work", then add a folder named "Work.Slacking Off".</p> + +<p>Pine will display one entry: "Work[.]". Pressing the '>' key will +display all subfolders in the Work subdirectory, and pressing Enter will open +the Work folder.</p> + +<p>There will be a minor problem navigating the folder collection when you're +saving messages in Pine. When Pine prompts you for a folder to save a +message, you can bring up the folder collection list by pressing Ctrl-T, +however there is no way to select the Work folder from the menu. The '>' +key will not work, and pressing Enter will always display all subfolders in +the Work subdirectory. To save the message in the Work folder you will have +to type in its name manually.</p> + +<h3>Removing the local mail folder collection</h3> + +<p>You may find it cumbersome to have two folder collections in Pine - the +default folder collection of mailbox files in <code>$HOME/mail</code> and the +Courier-IMAP folder collection.</p> + +<p>From Pine's main menu, choose S)etup then collectionL)ist. You can simply +delete your local mail folder collections. Pine will now create its usual +"postponed-msgs" and "sent-mail" folders in Courier-IMAP, instead of creating +mailbox files in <code>$HOME/mail</code>.</p> + +<p>Note - you will lose all your messages saved in local mail folders when +you do that, so make sure to back up any messages you want by copying them +into a Courier-IMAP folder. Also, even if you remove the local mail folder +collection, Pine will not remove <code>$HOME/mail</code>, and it will take up +space until you manually delete it.</p> + +<p>NOTE - if you use SqWebmail, resist the urge to go into Pine setup and +rename "postponed-msgs" to "Drafts". SqWebmail will have a problem with Pine +creating and deleting the Drafts folder at will.</p> + +<h2>Netscape Messenger (version tested: 4.7)</h2> + +<p>Go to Edit|Preferences. Click on Mail & Newsgroups, choose "Mail +Servers".</p> + +<p>Enter the name of the Courier-IMAP server, for server type choose "IMAP", +and make sure to enter your login name.</p> + +<p>Click on the "Advanced" tab, and UNCHECK the option "Show only subscribed +folders". Make sure that "server supports folders that contain subfolders and +messages" is checked.</p> + +<p>Under "Personal Namespace", enter "<code>INBOX.</code>", and don't forget +the trailing period. Under "Public (shared)", enter "<code>shared.</code>". +You may not actually need to do this, if you use a recent version of +Messenger (I believe that Messenger 4.5 and later support the +<code>NAMESPACE</code> server extension which automatically configures these +values).</p> + +<p>There are several known bugs in Netscape Messenger's IMAP client. See BUGS +for additional information.</p> + +<p>For the FAQ: <em>Clicking on "Get new messages" results in an error +message "Error in IMAP command received by server", or nothing happens at all +when I know there are new messages in a shared folder. ANSWER: this is a +known bug in Messenger's IMAP client. Complain to Netscape. You can try +reinstalling Courier-IMAP with the +<code>--enable-workarounds-for-imap-client-bugs</code> option, to see if it +helps.</em></p> + +<h2>Microsoft Outlook Express (version tested: 4.72)</h2> + +<p>Open the "Tools" menu, choose "Accounts". This is done for you +automatically the first time you run Outlook Express. If you already use +Outlook Express, select this option manually.</p> + +<p>Press the "Add" button, select "Mail".</p> + +<p>Enter your personal information - name, E-mail address - when prompted.</p> + +<p>Choose 'IMAP' server type, of course.</p> + +<p>Specify the name of the server that's running Courier-IMAP, in the +"Incoming Mail Server" field.</p> + +<p>Specify your usual SMTP server for outgoing mail.</p> + +<p>On the next screen, enter your Courier-IMAP logon name and password.</p> + +<p>Finish the rest of the set up by giving a name to this mail account. +Specify your appropriate connection settings.</p> + +<p>You will receive the following prompt:</p> + +<blockquote> + <p><strong>Would you like to download folder list for the IMAP account + you've just created?</strong></p> +</blockquote> + +<p>Click on "NO".</p> + +<p>You will be returned to the "Internet Accounts" dialog. If you are not +returned to this screen, choose "Accounts" from the "Tools" menu account.</p> + +<p>There will be an entry there for the new IMAP server account that you just +entered. Click on it, then choose "Properties". Click on "Advanced".</p> + +<p>If you don't need to access any shared folders, enter "INBOX" (without the +quotes) in the "Root folder path" field, and make sure that "Only show +subscribed folders" is NOT checked.</p> + +<p>If you need to access shared folders, leave the "Root folder path" field +blank. If you don't have a lot of shared folders, then leaving "Only show +subscribed folders" also unchecked would probably be easier. Otherwise, after +you download your folder list "see below", shared folders will not +immediately show up. You will have to right-click on the folder list and +select "Subscribe to all folders". Then, after all shared folders are +downloaded and shown, you can right-click and select "unsubscribe" in order +to remove any folders that you are not interested in.</p> + +<p>Save your changes, and go back to the main Outlook Express window. You +will see a new entry in the left navigation tab for the new IMAP server +you've just entered.</p> + +<p>Click on the new entry. You will be asked again whether or not you would +like to download the folder list. This time, answer "Yes".</p> + +<p>If you did not configure access to shared folders, all your folders on the +Courier-IMAP server will be shown as a single hierarchy. If you enabled +access to shared folders, there will be two separate folder hierarchies +shown: "INBOX" - your private folders, and "shared" - all your shared +folders.</p> + +<p>When shared folder access is enabled, you can only create or delete +folders by making them subfolders of INBOX. That's your private personal +folder space. You cannot create any "shared" subfolders, because that has to +be configured on the server side, either by you logging into your shell +account, or by the system administrator.</p> + +<p></p> +<hr /> + +<p>Pine is a trademark of the University Of Washington</p> + +<p>Netscape Communicator is a trademark of the Netscape Communications +Corporation</p> + +<p>Outlook Express is a trademark of the Microsoft Corporation</p> +<hr /> + +<p></p> +</body> +</html> diff --git a/imap/README.proxy.sgml b/imap/README.proxy.sgml new file mode 100644 index 0000000..655b4e0 --- /dev/null +++ b/imap/README.proxy.sgml @@ -0,0 +1,233 @@ +<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" + "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> + + <!-- Copyright 2004-2009 Double Precision, Inc. See COPYING for --> + <!-- distribution information. --> + +<chapter id="proxy"> + + <title>The <application>Courier</application> + IMAP/POP3 proxy</title> + + <para> +The <application>Courier</application> +IMAP and POP3 servers now include a built-in proxy aggregator. +With a proxy aggregator, the mail accounts are split between multiple, +independent servers, with an IMAP/POP3 server running on each individual +server. +A separate, proxy server sits in front and accepts ordinary IMAP +and POP3 connections. It reads the login ID, determines which server the +account is located on, connects to the server, and logs in. +Then, for the lifetime on the login session the front-end server takes itself +out of the loop, and forwards all session traffic between the IMAP/POP3 +client, and the back-end server.</para> + + <section id="requirements"> + <title>Requirements</title> + + <para> +The <application>Courier</application> +mail server can operate in IMAP/POP3 proxy mode only +when the <application>Courier</application> Authentication Library uses the +<literal>userdb</literal>, +<literal>LDAP</literal>, +<literal>MySQL</literal>, or the +<literal>PostgreSQL</literal> authentication module. +<emphasis>Challenge-Response (CRAM) authentication +will also work with the +<literal>LDAP</literal>, +<literal>MySQL</literal>, or the +<literal>PostgreSQL</literal> authentication module</emphasis>. +Yes, CRAM authentication will work (except for <literal>userdb</literal>).</para> + </section> + + <section id="configuration"> + <title>Configuration</title> + + <para> +Follow the regular installation instructions to set up +The <application>Courier</application> +mail server with the actual mail accounts. +The proxy feature uses the <quote>account options</quote> feature of the +<application>Courier</application> Authentication Library, +specifically an option called <quote>mailhost</quote>. +Account option configuration process depends on the authentication module. +With <literal>userdb</literal>, account options are specified by the +<quote>options</quote> <literal>userdb</literal> attribute:</para> + + <informalexample> + <programlisting> +userdb user@example.com set options=mailhost=servera.example.com +</programlisting> + </informalexample> + + <para> +Instructions for setting up account options with +<literal>LDAP</literal>, +<literal>MySQL</literal>, or +<literal>PostgreSQL</literal>, may be found in the appropriate configuration +file. Briefly:</para> + + <itemizedlist> + <listitem> + <para> +In <filename>authldaprc</filename>, put +<quote>LDAP_AUXOPTIONS<TAB>mailhost=mailhost</quote>, +then populate the <quote>mailhost</quote> LDAP attribute +(this may entail modifications of the LDAP schema).</para> + </listitem> + + <listitem> + <para> +In <filename>authmysqlrc</filename>, put +<quote>MYSQL_AUXOPTIONS<TAB>CONCAT("mailhost=",mailhost)</quote> +(or modify the existing <literal>MYSQL_AUXOPTIONS</literal> setting +accordingly), then create a <quote>mailhost</quote> column in the account +table.</para> + </listitem> + + <listitem> + <para> +In <filename>authpgsqlrc</filename>, put +<quote>PGSQL_AUXOPTIONS<TAB>'mailhost=' || mailhost</quote> +(or append <literal>",mailhost=" || mailhost</literal> +to an existing setting), +then create a <quote>mailhost</quote> column in the account +table.</para> + </listitem> + </itemizedlist> + + <para> +The <quote>mailhost</quote> option for each account should be the name of +the server where that account is located. +If possible, this should match, <emphasis>exactly</emphasis>, +the <envar>PROXY_HOSTNAME</envar> environment variable or the value +returned by the <quote>gethostname</quote> on the server.</para> + + <para> +The final step is to set <quote>IMAP_PROXY</quote> and/or +<quote>POP3_PROXY</quote> to <quote>1</quote> in the +<filename>imapd</filename> and/or the <filename>pop3d</filename> +configuration file, in the Courier configuration file directory on +the proxy server.</para> + + <section> + <title>Using the same configuration files on all servers</title> + + <para> +It is possible to have both the proxy server, and the back-end servers with +the actual accounts, read the same configuration file that enables proxying. +Ordinarily, if the back-end server also has the proxy setting turned on, it +will also attempt to establish a proxy connection (to itself; +lather, rinse, repeat until the server runs out of sockets).</para> + + <para> +However, if the <quote>mailhost</quote> option matches the server's hostname, +as returned by <quote>gethostname</quote>, no proxying takes place. +Therefore, if specific attention and care is made, when setting up the +server names and account options, all servers can boot off the +same configuration file.</para> + </section> + + <section> + <title>Alternative configurations</title> + + <para> +If the server names are set up properly, it's possible to set things up +without a dedicated front-end proxy aggregator server. +All mail accounts are divided between a pool of servers, who are just one, +big, happy family. +IMAP and POP3 clients can connect to any server, at random. +If they try to log into an account that happens to reside on the +same box, then everything will be ready to go. +If not, the server automatically opens a proxy connection to the right +box, and everything will be ready to go as well.</para> + </section> + + <section> + <title>Homogenous environments</title> + + <para> +Both servers involved in a proxy connections should be running the +same version of the +<application>Courier</application> IMAP/POP3 server. +The proxy code included in the +Courier-IMAP package tarball will talk to +the server from the +Courier-MTA +package tarball that includes the same build of the IMAP daemon, and +vice-versa. +Run <quote>imapd --version</quote> to determine the build of the IMAP +daemon.</para> + + <para> +All servers +<emphasis>MUST</emphasis> +use the same identical <filename>imapd</filename> +and <filename>pop3d</filename> configuration files (with the possible +exception of the proxy flag). +The next section explains why.</para> + + </section> + + <section> + <title>Heterogenous environments</title> + + <para> +It should generally be possible to have the +The <application>Courier</application> +IMAP/POP3 server establish a proxy connection to some other third party, +non-<application>Courier</application>, IMAP or POP3 server. +Of course, the <application>Courier</application> Authentication Library +running on the proxy server must have the same understanding of the +account names and passwords as the other IMAP/POP3 server. +The main issue is the different levels of protocol implementations.</para> + + <para> +Both the IMAP and POP3 protocols have optional features that different +servers may or may not implement. +Some servers will implement certain optional features of the IMAP or POP3 +protocol; other servers will implement different features parts.</para> + + <para> +When the IMAP/POP3 client connects to the server, the client typically +obtains the list of available optional features. +After logging in, the client will have no reason to expect that it's now +talking to a different server with a different set of protocol features. +Therefore, it may not be possible to use a Courier proxy with some +other IMAP/POP3 server that implements a widely different set of +features. +This may work with some clients, that don't make use of optional features; +while other clients will report strange, or unpredictable errors.</para> + + <para> +In some cases, setting the <literal>IMAP_PROXY_FOREIGN</literal> flag, +in +the <filename>imapd</filename> configuration file, may help. +This command will send a message to the IMAP client explicitly informing +the client that the list of available protocol features has changed; +however some clients may ignore or not implement this particular message. +There is no equivalent POP3 command.</para> + + <note> + <para> +As previously mentioned the IMAP/POP3 clients may use any supported +authentication method, including CRAM authentication (in most cases), +with or without encryption, to log in. +However, Courier will always use plain userid/password authentication, +without encryption, to establish proxy connections. +When using a different server, that server must be configured to allow +plain userid/password authentication.</para> + + <para> +Note that the default configuration of the <literal>UW-IMAP</literal> +server requires encryption, and refuses non-encrypted connections. +Proxy connections are presumably carried over a private network, and +there is no reason to use encryption. +Therefore, the <literal>UW-IMAP</literal> server will have to be +re-configured to allow non-encrypted connections, if it's to be used +with Courier in proxy mode.</para> + </note> + </section> + </section> +</chapter> diff --git a/imap/authenticate_auth.c b/imap/authenticate_auth.c new file mode 100644 index 0000000..04b03ac --- /dev/null +++ b/imap/authenticate_auth.c @@ -0,0 +1,143 @@ +/* +** Copyright 1998 - 2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <string.h> +#include <stdlib.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include "imaptoken.h" +#include "imapwrite.h" +#include "courierauth.h" +#include "courierauthsasl.h" +#include "courierauthdebug.h" + + +extern int main_argc; +extern char **main_argv; + +extern int login_callback(struct authinfo *ainfo, void *dummy); + +extern const char *imap_externalauth(); + +static char *send_auth_reply(const char *q, void *dummy) +{ + struct imaptoken *tok; + char *p; + +#if SMAP + const char *cp=getenv("PROTOCOL"); + + if (cp && strcmp(cp, "SMAP1") == 0) + writes("> "); + else +#endif + + { + writes("+ "); + } + writes(q); + writes("\r\n"); + writeflush(); + read_timeout(SOCKET_TIMEOUT); + tok=nexttoken_nouc(); + + switch (tok->tokentype) { + case IT_ATOM: + case IT_NUMBER: + p=my_strdup(tok->tokenbuf); + break; + case IT_EOL: + p=my_strdup(""); + break; + default: + return (0); + } + if (!p) + { + perror("malloc"); + return (0); + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(p); + fprintf(stderr, "Invalid SASL response\n"); + return (0); + } + read_eol(); + return (p); +} + +int authenticate(const char *tag, char *methodbuf, int methodbuflen) +{ +struct imaptoken *tok=nexttoken(); +char *authmethod; +char *initreply=0; +char *authtype, *authdata; +char authservice[40]; +char *p ; +int rc; + + switch (tok->tokentype) { + case IT_ATOM: + case IT_QUOTED_STRING: + break; + default: + return (0); + } + + authmethod=my_strdup(tok->tokenbuf); + if (methodbuf) + snprintf(methodbuf, methodbuflen, "%s", authmethod); + + tok=nexttoken_nouc(); + if (tok->tokentype != IT_EOL) + { + switch (tok->tokentype) { + case IT_ATOM: + case IT_NUMBER: + break; + default: + return (0); + } + initreply=my_strdup(tok->tokenbuf); + if (strcmp(initreply, "=") == 0) + *initreply=0; + tok=nexttoken_nouc(); + } + + if (tok->tokentype != IT_EOL) return (0); + + read_eol(); + if ((rc = auth_sasl_ex(authmethod, initreply, imap_externalauth(), + &send_auth_reply, NULL, + &authtype, &authdata)) != 0) + { + free(authmethod); + if (initreply) + free(initreply); + return (rc); + } + + free(authmethod); + if (initreply) + free(initreply); + + strcat(strcpy(authservice, "AUTHSERVICE"), + getenv("TCPLOCALPORT")); + p=getenv(authservice); + + if (!p || !*p) + p="imap"; + + rc=auth_generic(p, authtype, authdata, login_callback, (void *)tag); + free(authtype); + free(authdata); + return (rc); +} diff --git a/imap/capability.c b/imap/capability.c new file mode 100644 index 0000000..25e77a2 --- /dev/null +++ b/imap/capability.c @@ -0,0 +1,137 @@ +/* +** Copyright 1998 - 2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include "imapwrite.h" + + +static int capa_keywords=0; + +extern const char *externalauth(); + +void initcapability() +{ + const char *p=getenv("IMAP_KEYWORDS"); + + if (p) capa_keywords=atoi(p); +} + +int have_starttls() +{ +const char *p; + + if ((p=getenv("IMAP_STARTTLS")) == 0) return (0); + if (*p != 'y' && *p != 'Y') return (0); + + p=getenv("COURIERTLS"); + if (!p || !*p) return (0); + if (access(p, X_OK)) return (0); + return (1); +} + + +int tlsrequired() +{ +const char *p=getenv("IMAP_TLS_REQUIRED"); + + if (p && atoi(p)) return (1); + return (0); +} + +int keywords() +{ + return capa_keywords != 0; +} + +int fastkeywords() +{ + return capa_keywords == 1; +} + +int magictrash() +{ + const char *p; + + p=getenv("IMAP_MOVE_EXPUNGE_TO_TRASH"); + + if (p && atoi(p)) + return 1; + return 0; +} + +const char *imap_externalauth() +{ + const char *p; + + if ((p=getenv("IMAP_TLS")) && atoi(p)) + return externalauth(); + + return NULL; +} + + +void imapcapability() +{ + const char *p; + + if ((p=getenv("IMAP_TLS")) && atoi(p) && + (p=getenv("IMAP_CAPABILITY_TLS")) && *p) + writes(p); + else if ((p=getenv("IMAP_CAPABILITY")) != 0 && *p) + writes(p); + else + writes("IMAP4rev1"); + +#if SMAP + p=getenv("SMAP_CAPABILITY"); + + if (p && *p) + { + writes(" "); + writes(p); + + if (keywords()) + writes(" KEYWORDS"); + } +#endif + + if ((p=getenv("IMAP_ACL")) && atoi(p)) + writes(" ACL ACL2=UNION"); + + if (getenv("IMAP_ID_FIELDS")) + writes(" ID"); + + if (have_starttls()) + { + writes(" STARTTLS"); + if (tlsrequired()) + writes(" LOGINDISABLED"); + } + else + { + if (imap_externalauth()) + writes(" AUTH=EXTERNAL"); + } + + + p=getenv("OUTBOX"); + + if (p && *p) + { + writes(" XCOURIEROUTBOX=INBOX"); + writes(p); + } + + if (magictrash()) + writes(" XMAGICTRASH"); +} diff --git a/imap/configure.in b/imap/configure.in new file mode 100644 index 0000000..6b1404f --- /dev/null +++ b/imap/configure.in @@ -0,0 +1,371 @@ +dnl Process this file with autoconf to produce a configure script. +dnl +dnl +dnl Copyright 1998 - 2012 Double Precision, Inc. See COPYING for +dnl distribution information. + +AC_INIT(courier-imap, 4.12.0, [courier-users@lists.sourceforge.net]) + +>confdefs.h # Kill PACKAGE_ macros + +AC_CONFIG_SRCDIR(imapd.c) +AC_CONFIG_AUX_DIR(../..) +AC_CANONICAL_TARGET +AM_INIT_AUTOMAKE([foreign no-define]) +LPATH="$PATH:/usr/local/bin" + +AM_CONFIG_HEADER(config.h) +dnl Checks for programs. +AC_USE_SYSTEM_EXTENSIONS +AC_PROG_CC +AC_PROG_AWK +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_CXX +AC_LIBTOOL_DLOPEN +AM_PROG_LIBTOOL +AC_PATH_PROGS(PERL, perl5 perl, perl, $LPATH) + +if test "$PERL" = "perl" +then + AC_MSG_ERROR(Perl is required) +fi + +AC_PATH_PROGS(COURIERAUTHCONFIG, courierauthconfig) + +if test "$COURIERAUTHCONFIG" = "" +then + AC_MSG_ERROR(courierauthconfig not found) +fi + +CPPAUTH="`$COURIERAUTHCONFIG --cppflags`" +LDAUTH="`$COURIERAUTHCONFIG --ldflags`" +AC_SUBST(CPPAUTH) +AC_SUBST(LDAUTH) + +MAKECHECKBROKEN=N + +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +eval "exec_prefix=$exec_prefix" +eval "bindir=$bindir" + +AC_ARG_WITH(mailer, +[ --with-mailer=prog Your mail submission program], + SENDMAIL="$withval", + +[ + if test -d ${srcdir}/../../courier + then + SENDMAIL="$bindir/sendmail" + else + SENDMAIL_PATH=$PATH:/etc:/sbin:/usr/sbin:/usr/local/bin:/var/qmail/bin + AC_PATH_PROG(sendmail, sendmail, /usr/bin/sendmail, $SENDMAIL_PATH) + SENDMAIL="$sendmail" + fi +] +) +AC_SUBST(SENDMAIL) + +OPENSSL_PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin:/usr/local/bin + +AC_PATH_PROGS(OPENSSL, openssl, , $OPENSSL_PATH) + +if test "$OPENSSL" = "" +then + OPENSSL=/usr/local/bin/openssl +fi +AC_SUBST(OPENSSL) + +AC_PATH_PROGS(CERTTOOL, certtool, , $OPENSSL_PATH) + +if test "$CERTTOOL" = "" +then + CERTTOOL=/usr/local/bin/certtool +fi + +AC_ARG_WITH(random, [ --with-random=/dev/urandom - location of the system random file generator +--without-random - there is no system random file generator ], + RANDOMV="$withval", +RANDOMV="/dev/random" +if test -r /dev/urandom +then + RANDOMV="/dev/urandom" +fi + ) + +AC_SUBST(RANDOMV) + +AC_ARG_WITH(mailuser, [], mailuser="$withval", + AC_MSG_ERROR(--with-mailuser missing)) +AC_SUBST(mailuser) + +dnl Checks for libraries. + +AC_ARG_WITH(db, [], db="$withval", db="") + + +AC_ARG_WITH(piddir, [ --with-piddir Directory where imapd.pid is created ], piddir="$withval", piddir=/var/run) + +AC_SUBST(piddir) + +dnl Checks for header files. +AC_HEADER_STDC +AC_CHECK_HEADERS(locale.h unistd.h sys/stat.h sys/wait.h time.h sys/time.h sys/utsname.h utime.h) +AC_HEADER_TIME +AC_HEADER_DIRENT +AC_HEADER_SYS_WAIT + +dnl Checks for typedefs, structures, and compiler characteristics. +AC_C_CONST +AC_C_INLINE +AC_TYPE_SIZE_T +AC_TYPE_SIGNAL +AC_SYS_LARGEFILE + +AC_CACHE_CHECK([for socklen_t], + sox_cv_hassocklen_t, + +AC_COMPILE_IFELSE([ +AC_LANG_SOURCE( [ +#include <sys/types.h> +#include <sys/socket.h> + +socklen_t sl_t; +],[ + accept(0, 0, &sl_t); +])], + sox_cv_hassocklen_t=yes, + sox_cv_hassocklen_t=no) +) + +socklen_t="int" + +if test $sox_cv_hassocklen_t = yes +then + : +else + AC_DEFINE_UNQUOTED(socklen_t, int, [ Default definition for socklen_t ]) +fi + +dnl Checks for library functions. + +AC_CHECK_FUNCS(strerror utime utimes strcasecmp strncasecmp setlocale poll getaddrinfo) + +AC_DEFINE_UNQUOTED(SOCKET_TIMEOUT,60, + [ Read/write timeout ]) + +AC_ARG_WITH(db, [ ], db="$withval") + +AC_DEFINE_UNQUOTED(IMAPDB, "courierimapuiddb", + [ Filename of the UID cache file ]) +AC_DEFINE_UNQUOTED(IMAPDBVERSION, 1, + [ UID cache file format version ]) + +AC_DEFINE_UNQUOTED(SNAPSHOTDIR, "courierimapsnapshots", + [ Directory where folder state is saved ]) +AC_DEFINE_UNQUOTED(SNAPSHOTVERSION, 1, + [ snapshot file format version ]) + +AC_DEFINE_UNQUOTED(TRASH,"Trash", [ Name of the trash folder ]) +AC_DEFINE_UNQUOTED(DRAFTS,"Drafts", [ Name of the drafts folder ]) + +# Unless you're the maintainer, clear DEBUGLIB just in case. + +#case `hostname` in +#*.email-scan.com) +# ;; +#*) + DEBUGLIB="" +# ;; +#esac +AC_SUBST(DEBUGLIB) + +if test x$GXX = xyes +then + CFLAGS="-Wall $CFLAGS" +fi + +if test x$GXX = xyes +then + CXXFLAGS="-Wall $CXXFLAGS" +fi + +CFLAGS="-I.. -I$srcdir/.. $CFLAGS" +CXXFLAGS="-I.. -I$srcdir/.. $CXXFLAGS" + +AC_ARG_ENABLE(workarounds-for-imap-client-bugs, [ --enable-workarounds-for-imap-client-bugs + - compile fixes for various bugs in several IMAP clients ], + IMAP_CLIENT_BUGS="$enableval", + IMAP_CLIENT_BUGS="no") + +case "$IMAP_CLIENT_BUGS" in +y*|Y*) + MAKECHECKBROKEN=Y + AC_DEFINE_UNQUOTED(IMAP_CLIENT_BUGS, 1, + [ Whether to suppress untagged replies that confuse some clients ]) + ;; +esac + +. ../../dbobj.config +dblibrary="../../$dblibrary" +AC_SUBST(dblibrary) + +USENSL=no +saveLIBS="$LIBS" +AC_CHECK_LIB(socket,socket,result=yes,result=no) +if test $result = yes; then + NETLIBS="-lsocket" +else + AC_CHECK_LIB(socket,socket,result=yes,result=no,-lnsl) + if test $result = yes; then + NETLIBS = "-lsocket -lnsl" + USENSL=yes + else + AC_CHECK_LIB(socket,connect,result=yes,result=no) + if test $result = yes; then + NETLIBS="-lsocket" + else + AC_CHECK_LIB(socket,connect,result=yes,result=no,-lnsl) + if test $result = yes; then + NETLIBS="-lsocket -lnsl" + USENSL=yes + fi + fi + fi +fi + +if test $USENSL != yes; then + LIBS="$LIBS $NETLIBS" + AC_TRY_LINK_FUNC(inet_addr, [ : ], + [ + AC_CHECK_LIB(nsl,inet_addr,result=yes,result=no) + if test $result = yes; then + NETLIBS="$NETLIBS -lnsl" + fi + ]) +fi + +LIBS="$saveLIBS $NETLIBS" +AC_SUBST(NETLIBS) + +AC_ARG_WITH(dirsync, [ --with-dirsync Manually sync queue file directory], + dirsync="$withval", dirsync="N") + +case "$dirsync" in +y*|Y*|1*) + AC_DEFINE_UNQUOTED(EXPLICITDIRSYNC,1, + [ Whether to sync the parent directory after delivering to a maildir ]) + ;; +esac + +AC_ARG_WITH(smap, [ --without-smap Do not compile SMAP support], + smap="$withval", smap=yes) + +case "$smap" in +y*|Y*) + AC_DEFINE_UNQUOTED(SMAP, 1, [ Whether SMAP support is compiled in ]) + smap="yes" + ;; +esac +AC_SUBST(smap) + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' +eval "prefix=$prefix" +eval "exec_prefix=$exec_prefix" +eval "libexecdir=$libexecdir" +eval "bindir=$bindir" +eval "sbindir=$sbindir" +eval "datarootdir=$datarootdir" +eval "datadir=$datadir" +eval "sysconfdir=$sysconfdir" +eval "localstatedir=$localstatedir" + +AC_ARG_WITH(certsdir, [ --with-certsdir Directory where certs are created ], +certsdir="$withval", certsdir="$datadir") + +AC_SUBST(certsdir) + + +mydatadir="$datadir" +AC_SUBST(mydatadir) # Avoid useless autoconf warning + +# +# Check for PAM configuration flavor + +rm -f imapd.pam pop3d.pam + +cp -f $srcdir/imapd.authpam imapd.pam +cp -f $srcdir/pop3d.authpam pop3d.pam + +if test -f /etc/pam.d/system-auth +then + if ls /lib*/security/pam_stack.so 2>/dev/null >/dev/null + then + cp -f $srcdir/system-auth.authpam imapd.pam + cp -f $srcdir/system-auth.authpam pop3d.pam + else + cp -f $srcdir/system-auth2.authpam imapd.pam + cp -f $srcdir/system-auth2.authpam pop3d.pam + fi +fi + +AM_CONDITIONAL(HAVE_SGML, test -d ${srcdir}/../docbook) + +AC_SUBST(target_cpu) +AC_SUBST(target_vendor) +AC_SUBST(target_os) + +AC_ARG_WITH(package, [], package="$withval", [package='courier-imap']) +AC_ARG_WITH(version, [], version="$withval", [version=$VERSION]) + +case "$package" in +courier) + package="Courier $version (Courier-IMAP $VERSION)" + ;; +*) + package="Courier-IMAP $version" + ;; +esac + +date=`date` +AC_DEFINE_UNQUOTED(PROGRAMVERSION, "$package/${target_cpu}-${target_vendor}-${target_os}/$date", + [ Source code version ]) + +AC_ARG_WITH(trashquota, [ --with-trashquota Count deleted messages as part of the quota], + trashquota="$withval", + trashquota="no") + +if test "$trashquota" = "yes" +then + MAKECHECKBROKEN=Y +fi + +AC_SUBST(MAKECHECKBROKEN) + +. ../tcpd/couriertls.config +if test "$ssllib" = "" +then + ssllib="gnutls" +fi +AC_SUBST(ssllib) + +AC_COMPILE_IFELSE([AC_LANG_SOURCE([ + +#include <stdio.h> + +int main() +{ + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); +} +])], + AC_DEFINE_UNQUOTED(HAVE_SETVBUF_IOLBF,1,[Whether setvbuf(..._IOLBF) works]) +) + +. ../rootcerts/rootcertsdir.cnf +AC_SUBST(cacerts) + +AC_OUTPUT(Makefile imapd.dist imapd-ssl.dist pop3d.dist pop3d-ssl.dist + testsuitefix.pl mkimapdcert mkpop3dcert + imapd.cnf.openssl pop3d.cnf.openssl) diff --git a/imap/courierpop3d.sgml b/imap/courierpop3d.sgml new file mode 100644 index 0000000..a25319c --- /dev/null +++ b/imap/courierpop3d.sgml @@ -0,0 +1,119 @@ +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<!-- Copyright 1998 - 2009 Double Precision, Inc. See COPYING for --> +<!-- distribution information. --> +<refentry> + <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info> + + <refmeta> + <refentrytitle>courierpop3d</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo>Double Precision, Inc.</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>courierpop3d</refname> + <refpurpose>The <application moreinfo="none">Courier</application> + POP3 server</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis sepchar=" "> + <command moreinfo="none">@sbindir@/couriertcpd</command> + <arg choice="req" rep="norepeat">-nodnslookup</arg> + <arg choice="req" rep="norepeat">-stderr=syslog</arg> + <arg choice="req" rep="norepeat">110</arg> + <arg choice="req" rep="norepeat">@libexecdir@/courier/courierpop3login</arg> + <arg choice="opt" rep="repeat"><replaceable>modules</replaceable></arg> + <arg choice="req" rep="norepeat">@libexecdir@/courier/courierpop3d</arg> + <arg choice="req" rep="norepeat">./Maildir</arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>DESCRIPTION</title> + + <para> +This is a simple POP3 server for Maildirs.</para> + + <note> + <para> +The <command moreinfo="none">couriertcpd</command>, <command moreinfo="none">courierpop3login</command>, +and <command moreinfo="none">courierpop3d</command> modules may be installed elsewhere than +indicated here.</para> + </note> + + <para> +<command moreinfo="none">courierpop3login</command> is usually started by +<command moreinfo="none">couriertcpd</command>. It +already expects that a POP3 client is connected to standard input and output, +presumably via a network socket. +<command moreinfo="none">courierpop3login</command> reads the POP3 +userid and password, then runs the +authentication <literal moreinfo="none">modules</literal>. The remaining +arguments are passed along as arguments to <literal moreinfo="none">modules</literal>.</para> + + <para> +<literal moreinfo="none">modules</literal> +is one or more authentication modules (see the +<ulink url="authlib.html"><citerefentry> + <refentrytitle>authlib</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> +</ulink> +manual page).</para> + + <para> +Each authentication modules runs the program specified by its first +argument, allowing the authentication modules to be chained. The last program +in the chain is +<command moreinfo="none">courierpop3d</command> +, which provides the actual POP3 service. In +accordance with the authentication protocol, as described in +<ulink url="authlib.html"><citerefentry> + <refentrytitle>authlib</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> +</ulink> +<command moreinfo="none">courierpop3d</command> reads +file descriptor 3 to see if the userid/password has been succesfully +validated. If not, <command moreinfo="none">courierpop3d</command> terminates.</para> + + <para> +Otherwise, <command moreinfo="none">courierpop3d</command> expects to be already running +under the +appropriate user and group id, with its current directory set to the account's +home directory.</para> + + <para> +The first order of business is to find the account's Maildir. If the +environment variable <envar>MAILDIR</envar> is set, that's where we go. +That should be the +pathname to the account's Maildir. The environment variable +<envar>MAILDIR</envar> may be set by the +authentication module. +If <envar>MAILDIR</envar> is not set, +<command moreinfo="none">courierpop3d</command> uses its first argument. +Usually, the default maildir is +<filename moreinfo="none">$HOME/Maildir</filename>, therefore the first argument to +<command moreinfo="none">courierpop3d</command> is +"<literal moreinfo="none">./Maildir</literal>".</para> + </refsect1> + + <refsect1> + <title>SEE ALSO</title> + + <para> +<ulink url="authlib.html"><citerefentry> + <refentrytitle>authlib</refentrytitle> + <manvolnum>7</manvolnum> + </citerefentry> +</ulink>, +<ulink url="userdb.html"><citerefentry> + <refentrytitle>userdb</refentrytitle> + <manvolnum>8</manvolnum> + </citerefentry> +</ulink>.</para> + + </refsect1> + +</refentry> diff --git a/imap/externalauth.c b/imap/externalauth.c new file mode 100644 index 0000000..154e876 --- /dev/null +++ b/imap/externalauth.c @@ -0,0 +1,39 @@ +/* +** Copyright 1998 - 2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +const char *externalauth() +{ + const char *p=getenv("TLS_EXTERNAL"); + char *q, *r; + + if (!p || !*p) + return NULL; + + if ((q=malloc(strlen(p)+20)) == NULL) + return NULL; + + strcat(strcpy(q, "TLS_SUBJECT_"), p); + + for (r=q; *r; r++) + if (*r >= 'a' && *r <= 'z') + *r -= 'a' - 'A'; + + p=getenv(q); + free(q); + + if (p && *p) + return p; + return 0; +} diff --git a/imap/fetch.c b/imap/fetch.c new file mode 100644 index 0000000..54e7139 --- /dev/null +++ b/imap/fetch.c @@ -0,0 +1,1626 @@ +/* +** Copyright 1998 - 2010 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> + +#include "imaptoken.h" +#include "imapwrite.h" +#include "imapscanclient.h" +#include "fetchinfo.h" +#include "rfc822/rfc822.h" +#include "rfc2045/rfc2045.h" +#include "maildir/config.h" +#include "maildir/maildirgetquota.h" +#include "maildir/maildirquota.h" +#include "maildir/maildiraclt.h" + +#if SMAP +extern int smapflag; +#endif + +static const char unavailable[]= + "\ +From: System Administrator <root@localhost>\n\ +Subject: message unavailable\n\n\ +This message is no longer available on the server.\n"; + +unsigned long header_count=0, body_count=0; /* Total transferred */ + +extern int current_mailbox_ro; +extern char *current_mailbox_acl; +extern struct imapscaninfo current_maildir_info; +extern char *current_mailbox; +extern char *rfc2045id(struct rfc2045 *); + +extern void snapshot_needed(); + +extern void msgenvelope(void (*)(const char *, size_t), + FILE *, struct rfc2045 *); +extern void msgbodystructure( void (*)(const char *, size_t), int, + FILE *, struct rfc2045 *); + +extern int is_trash(const char *); +extern void get_message_flags(struct imapscanmessageinfo *, + char *, struct imapflags *); +extern void append_flags(char *, struct imapflags *); + +static int fetchitem(FILE **, int *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 **); + +static void bodystructure(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +static void body(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +static void fetchmsgbody(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +static void dofetchmsgbody(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +static void envelope(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +void doflags(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +static void internaldate(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +static void uid(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +static void all(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +static void fast(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +static void full(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +static void rfc822size(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, struct rfc2045 *); + +#if 0 +static void do_envelope(FILE *, struct fetchinfo *, + struct imapscanmessageinfo *, struct rfc2045 *); +#endif + +static void dofetchheadersbuf(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *, + int (*)(struct fetchinfo *fi, const char *)); +static void dofetchheadersfile(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *, + int (*)(struct fetchinfo *fi, const char *)); + +static void print_bodysection_partial(struct fetchinfo *, + void (*)(const char *)); +static void print_bodysection_output(const char *); + +static int dofetchheaderfields(struct fetchinfo *, const char *); +static int dofetchheadernotfields(struct fetchinfo *, const char *); +static int dofetchheadermime(struct fetchinfo *, const char *); + +static void rfc822(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +static void rfc822header(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +static void rfc822text(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + +struct rfc2045 *fetch_alloc_rfc2045(unsigned long, FILE *); +FILE *open_cached_fp(unsigned long); + +void fetchflags(unsigned long); + +static void fetcherrorprt(const char *p) +{ + fprintf(stderr, "%s", p); +} + +static void fetcherror(const char *errmsg, + struct fetchinfo *fi, + struct imapscaninfo *info, unsigned long j) +{ +struct imapscanmessageinfo *mi=info->msgs+j; + + fprintf(stderr, "IMAP FETCH ERROR: %s, uid=%u, filename=%s: %s", + errmsg, (unsigned)getuid(), mi->filename, fi->name); + if (fi->bodysection) + print_bodysection_partial(fi, &fetcherrorprt); + fprintf(stderr, "\n"); +} + +char *get_reflagged_filename(const char *fn, struct imapflags *newflags) +{ + char *p=malloc(strlen(fn)+20); + char *q; + + if (!p) write_error_exit(0); + strcpy(p, fn); + if ((q=strrchr(p, MDIRSEP[0])) != 0) *q=0; + strcat(p, MDIRSEP "2,"); + append_flags(p, newflags); + return p; +} + +int reflag_filename(struct imapscanmessageinfo *mi, struct imapflags *flags, + int fd) +{ +char *p, *q, *r; +int rc=0; +struct imapflags old_flags; +struct stat stat_buf; + + get_message_flags(mi, 0, &old_flags); + + p=get_reflagged_filename(mi->filename, flags); + + q=malloc(strlen(current_mailbox)+strlen(mi->filename)+sizeof("/cur/")); + r=malloc(strlen(current_mailbox)+strlen(p)+sizeof("/cur/")); + if (!q || !r) write_error_exit(0); + strcat(strcat(strcpy(q, current_mailbox), "/cur/"), mi->filename); + strcat(strcat(strcpy(r, current_mailbox), "/cur/"), p); + if (strcmp(q, r)) + { + if (maildirquota_countfolder(current_mailbox) + && old_flags.deleted != flags->deleted + && fstat(fd, &stat_buf) == 0) + { + struct maildirsize quotainfo; + int64_t nbytes; + unsigned long unbytes; + int nmsgs=1; + + if (maildir_parsequota(mi->filename, &unbytes) == 0) + nbytes=unbytes; + else + nbytes=stat_buf.st_size; + if ( flags->deleted ) + { + nbytes= -nbytes; + nmsgs= -nmsgs; + } + + if ( maildir_quota_delundel_start(current_mailbox, + "ainfo, + nbytes, nmsgs)) + rc= -1; + else + maildir_quota_delundel_end("ainfo, + nbytes, nmsgs); + } + + if (rc == 0) + rename(q, r); + +#if SMAP + snapshot_needed(); +#endif + } + free(q); + free(r); + free(mi->filename); + mi->filename=p; + +#if 0 + if (is_sharedsubdir(current_mailbox)) + maildir_shared_updateflags(current_mailbox, p); +#endif + + return (rc); +} + +int do_fetch(unsigned long n, int byuid, void *p) +{ + struct fetchinfo *fi=(struct fetchinfo *)p; + FILE *fp; + struct rfc2045 *rfc2045p; + int seen; + int open_err; + + fp=NULL; + open_err=0; + + writes("* "); + writen(n); + writes(" FETCH ("); + + if (byuid) + { + struct fetchinfo *fip; + + for (fip=fi; fip; fip=fip->next) + if (strcmp(fip->name, "UID") == 0) + break; + + if (fip == 0) + { + writes("UID "); + writen(current_maildir_info.msgs[n-1].uid); + writes(" "); + } + } + seen=0; + rfc2045p=0; + while (fi) + { + if (fetchitem(&fp, &open_err, fi, ¤t_maildir_info, n-1, + &rfc2045p)) seen=1; + if ((fi=fi->next) != 0) writes(" "); + } + writes(")\r\n"); + + if (open_err) + { + writes("* NO Cannot open message "); + writen(n); + writes("\r\n"); + return (0); + } + + +#if SMAP + if (!smapflag) +#endif + if (current_mailbox_acl && + strchr(current_mailbox_acl, ACL_SEEN[0]) == NULL) + seen=0; /* No permissions */ + + if (seen && !current_mailbox_ro) + { + struct imapflags flags; + + get_message_flags(current_maildir_info.msgs+(n-1), + 0, &flags); + if (!flags.seen) + { + flags.seen=1; + reflag_filename(¤t_maildir_info.msgs[n-1],&flags, + fileno(fp)); + current_maildir_info.msgs[n-1].changedflags=1; + } + } + + if (current_maildir_info.msgs[n-1].changedflags) + fetchflags(n-1); + return (0); +} + +static int fetchitem(FILE **fp, int *open_err, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 **mimep) +{ + void (*fetchfunc)(FILE *, struct fetchinfo *, + struct imapscaninfo *, unsigned long, + struct rfc2045 *); + int parsemime=0; + int rc=0; + int do_open=1; + + if (strcmp(fi->name, "ALL") == 0) + { + parsemime=1; + fetchfunc= &all; + } + else if (strcmp(fi->name, "BODYSTRUCTURE") == 0) + { + parsemime=1; + fetchfunc= &bodystructure; + } + else if (strcmp(fi->name, "BODY") == 0) + { + parsemime=1; + fetchfunc= &body; + if (fi->bodysection) + { + fetchfunc= &fetchmsgbody; + rc=1; + } + } + else if (strcmp(fi->name, "BODY.PEEK") == 0) + { + parsemime=1; + fetchfunc= &body; + if (fi->bodysection) + fetchfunc= &fetchmsgbody; + } + else if (strcmp(fi->name, "ENVELOPE") == 0) + { + parsemime=1; + fetchfunc= &envelope; + } + else if (strcmp(fi->name, "FAST") == 0) + { + parsemime=1; + fetchfunc= &fast; + } + else if (strcmp(fi->name, "FULL") == 0) + { + parsemime=1; + fetchfunc= &full; + } + else if (strcmp(fi->name, "FLAGS") == 0) + { + fetchfunc= &doflags; + do_open=0; + } + else if (strcmp(fi->name, "INTERNALDATE") == 0) + { + fetchfunc= &internaldate; + } + else if (strcmp(fi->name, "RFC822") == 0) + { + fetchfunc= &rfc822; + rc=1; + } + else if (strcmp(fi->name, "RFC822.HEADER") == 0) + { + fetchfunc= &rfc822header; + } + else if (strcmp(fi->name, "RFC822.SIZE") == 0) + { + parsemime=1; + fetchfunc= &rfc822size; + } + else if (strcmp(fi->name, "RFC822.TEXT") == 0) + { + parsemime=1; + fetchfunc= &rfc822text; + } + else if (strcmp(fi->name, "UID") == 0) + { + fetchfunc= &uid; + do_open=0; + } + else return (0); + + if (do_open && *fp == NULL) + { + *fp=open_cached_fp(msgnum); + if (!*fp) + { + *open_err=1; + return rc; + } + } + + if (parsemime && !*mimep) + { + *mimep=fetch_alloc_rfc2045(msgnum, *fp); + } + + (*fetchfunc)(*fp, fi, i, msgnum, *mimep); + return (rc); +} + +static void bodystructure(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + writes("BODYSTRUCTURE "); + msgbodystructure(writemem, 1, fp, mimep); +} + +static void body(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + writes("BODY "); + msgbodystructure(writemem, 0, fp, mimep); +} + +static void envelope(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + writes("ENVELOPE "); + msgenvelope( &writemem, fp, mimep); +} + +void fetchflags(unsigned long n) +{ +#if SMAP + if (smapflag) + { + writes("* FETCH "); + writen(n+1); + } + else +#endif + { + writes("* "); + writen(n+1); + writes(" FETCH ("); + } + + doflags(0, 0, ¤t_maildir_info, n, 0); + +#if SMAP + if (smapflag) + { + writes("\n"); + } + else +#endif + writes(")\r\n"); +} + +void fetchflags_byuid(unsigned long n) +{ + writes("* "); + writen(n+1); + writes(" FETCH ("); + uid(0, 0, ¤t_maildir_info, n, 0); + writes(" "); + doflags(0, 0, ¤t_maildir_info, n, 0); + writes(")\r\n"); +} + +void doflags(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + struct libmail_kwMessageEntry *kme; + + char buf[256]; + +#if SMAP + if (smapflag) + { + writes(" FLAGS="); + get_message_flags(i->msgs+msgnum, buf, 0); + writes(buf); + } + else +#endif + { + struct libmail_kwMessage *km; + + writes("FLAGS "); + + get_message_flags(i->msgs+msgnum, buf, 0); + + writes("("); + writes(buf); + + if (buf[0]) + strcpy(buf, " "); + + if ((km=i->msgs[msgnum].keywordMsg) != NULL) + for (kme=km->firstEntry; kme; kme=kme->next) + { + writes(buf); + strcpy(buf, " "); + writes(keywordName(kme->libmail_keywordEntryPtr)); + } + writes(")"); + } + + i->msgs[msgnum].changedflags=0; +} + +static void internaldate(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ +struct stat stat_buf; +char buf[256]; +char *p, *q; + + writes("INTERNALDATE "); + if (fstat(fileno(fp), &stat_buf) == 0) + { + rfc822_mkdate_buf(stat_buf.st_mtime, buf); + + /* Convert RFC822 date to imap date */ + + p=strchr(buf, ','); + if (p) ++p; + else p=buf; + while (*p == ' ') ++p; + if ((q=strchr(p, ' ')) != 0) *q++='-'; + if ((q=strchr(p, ' ')) != 0) *q++='-'; + writes("\""); + writes(p); + writes("\""); + } + else + writes("NIL"); +} + +static void uid(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + writes("UID "); + writen(i->msgs[msgnum].uid); +} + +static void rfc822size(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ +off_t start_pos, end_pos, start_body; +off_t nlines, nbodylines; + + writes("RFC822.SIZE "); + + rfc2045_mimepos(mimep, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + writen(end_pos - start_pos + nlines); +} + +static void all(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + doflags(fp, fi, i, msgnum, mimep); + writes(" "); + internaldate(fp, fi, i, msgnum, mimep); + writes(" "); + rfc822size(fp, fi, i, msgnum, mimep); + writes(" "); + envelope(fp, fi, i, msgnum, mimep); +} + +static void fast(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + doflags(fp, fi, i, msgnum, mimep); + writes(" "); + internaldate(fp, fi, i, msgnum, mimep); + writes(" "); + rfc822size(fp, fi, i, msgnum, mimep); +} + +static void full(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + doflags(fp, fi, i, msgnum, mimep); + writes(" "); + internaldate(fp, fi, i, msgnum, mimep); + writes(" "); + rfc822size(fp, fi, i, msgnum, mimep); + writes(" "); + envelope(fp, fi, i, msgnum, mimep); + writes(" "); + body(fp, fi, i, msgnum, mimep); +} + +static void fetchmsgbody(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ + writes("BODY"); + print_bodysection_partial(fi, &print_bodysection_output); + writes(" "); + dofetchmsgbody(fp, fi, i, msgnum, mimep); +} + +static void print_bodysection_output(const char *p) +{ + writes(p); +} + +static void print_bodysection_partial(struct fetchinfo *fi, + void (*func)(const char *)) +{ + (*func)("["); + if (fi->bodysection) + { + struct fetchinfo *subl; + + (*func)(fi->bodysection); + if (fi->bodysublist) + { + char *p=" ("; + + for (subl=fi->bodysublist; subl; subl=subl->next) + { + (*func)(p); + p=" "; + (*func)("\""); + (*func)(subl->name); + (*func)("\""); + } + (*func)(")"); + } + } + (*func)("]"); + if (fi->ispartial) + { + char buf[80]; + + sprintf(buf, "<%lu>", (unsigned long)fi->partialstart); + (*func)(buf); + } +} + +static void dofetchmsgbody(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep) +{ +const char *p=fi->bodysection; +off_t start_pos, end_pos, start_body; +off_t nlines, nbodylines; +unsigned long cnt; +char buf[BUFSIZ]; +char rbuf[BUFSIZ]; +char *rbufptr; +int rbufleft; +unsigned long bufptr; +unsigned long skipping; +int ismsgrfc822=1; + +off_t start_seek_pos; + struct rfc2045 *headermimep; + +/* +** To optimize consecutive FETCHes, we cache our virtual and physical +** position. What we do is that on the first fetch we count off the +** characters we read, and keep track of both the physical and the CRLF-based +** offset into the message. Then, on subsequent FETCHes, we attempt to +** use that information. +*/ + +off_t cnt_virtual_chars; +off_t cnt_phys_chars; + +off_t cache_virtual_chars; +off_t cache_phys_chars; + + headermimep=mimep; + + while (p && isdigit((int)(unsigned char)*p)) + { + unsigned long n=0; + + headermimep=mimep; + + do + { + n=n*10 + (*p++ - '0'); + } while (isdigit((int)(unsigned char)*p)); + + if (mimep) + { + if (ismsgrfc822) + { + const char *ct, *dummy; + + if (mimep->firstpart == 0) + { + /* Not a multipart, n must be 1 */ + if (n != 1) + mimep=0; + if (*p == '.') + ++p; + continue; + } + ismsgrfc822=0; + + rfc2045_mimeinfo(mimep, &ct, + &dummy, + &dummy); + + if (ct && strcasecmp(ct, "message/rfc822" + ) == 0) + ismsgrfc822=1; + /* The content is another message/rfc822 */ + } + + mimep=mimep->firstpart; + while (mimep) + { + if (!mimep->isdummy && --n == 0) + break; + mimep=mimep->next; + } + headermimep=mimep; + + if (mimep && mimep->firstpart && + !mimep->firstpart->isdummy) + /* This is a message/rfc822 part */ + { + if (!*p) + break; + + mimep=mimep->firstpart; + ismsgrfc822=1; + } + } + if (*p == '.') + ++p; + } + + if (p && strcmp(p, "MIME") == 0) + mimep=headermimep; + + if (mimep == 0) + { + writes("{0}\r\n"); + return; + } + + rfc2045_mimepos(mimep, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + + if (p && strcmp(p, "TEXT") == 0) + { + start_seek_pos=start_body; + cnt=end_pos - start_body + nbodylines; + } + else if (p && strcmp(p, "HEADER") == 0) + { + start_seek_pos=start_pos; + cnt= start_body - start_pos + (nlines - nbodylines); + } + else if (p && strcmp(p, "HEADER.FIELDS") == 0) + { + if (start_body - start_pos <= BUFSIZ) + dofetchheadersbuf(fp, fi, i, msgnum, mimep, + &dofetchheaderfields); + else + dofetchheadersfile(fp, fi, i, msgnum, mimep, + &dofetchheaderfields); + return; + } + else if (p && strcmp(p, "HEADER.FIELDS.NOT") == 0) + { + if (start_body - start_pos <= BUFSIZ) + dofetchheadersbuf(fp, fi, i, msgnum, mimep, + &dofetchheadernotfields); + else + dofetchheadersfile(fp, fi, i, msgnum, mimep, + &dofetchheadernotfields); + return; + } + else if (p && strcmp(p, "MIME") == 0) + { + if (start_body - start_pos <= BUFSIZ) + dofetchheadersbuf(fp, fi, i, msgnum, mimep, + &dofetchheadermime); + else + dofetchheadersfile(fp, fi, i, msgnum, mimep, + &dofetchheadermime); + return; + } + else if (*fi->bodysection == 0) + { + start_seek_pos=start_pos; + + cnt= end_pos - start_pos + nlines; + } + else /* Last possibility: entire body */ + { + start_seek_pos=start_body; + + cnt= end_pos - start_body + nbodylines; + } + + skipping=0; + if (fi->ispartial) + { + skipping=fi->partialstart; + if (skipping > cnt) skipping=cnt; + cnt -= skipping; + if (fi->ispartial > 1 && cnt > fi->partialend) + cnt=fi->partialend; + } + + if (get_cached_offsets(start_seek_pos, &cnt_virtual_chars, + &cnt_phys_chars) == 0 && + cnt_virtual_chars <= skipping) /* Yeah - cache it, baby! */ + { + if (fseek(fp, start_seek_pos+cnt_phys_chars, SEEK_SET) == -1) + { + writes("{0}\r\n"); + fetcherror("fseek", fi, i, msgnum); + return; + } + skipping -= cnt_virtual_chars; + } + else + { + if (fseek(fp, start_seek_pos, SEEK_SET) == -1) + { + writes("{0}\r\n"); + fetcherror("fseek", fi, i, msgnum); + return; + } + + cnt_virtual_chars=0; + cnt_phys_chars=0; + } + + cache_virtual_chars=cnt_virtual_chars; + cache_phys_chars=cnt_phys_chars; + + writes("{"); + writen(cnt); + writes("}\r\n"); + bufptr=0; + writeflush(); + + rbufptr=0; + rbufleft=0; + + while (cnt) + { + int c; + + if (!rbufleft) + { + rbufleft=fread(rbuf, 1, sizeof(rbuf), fp); + if (rbufleft < 0) rbufleft=0; + rbufptr=rbuf; + } + + if (!rbufleft) + { + fetcherror("unexpected EOF", fi, i, msgnum); + _exit(1); + } + + --rbufleft; + c=(int)(unsigned char)*rbufptr++; + ++cnt_phys_chars; + + if (c == '\n') + { + ++cnt_virtual_chars; + + if (skipping) + --skipping; + else + { + if (bufptr >= sizeof(buf)) + { + writemem(buf, sizeof(buf)); + bufptr=0; + /*writeflush();*/ + } + buf[bufptr++]='\r'; + --cnt; + + if (cnt == 0) + break; + } + } + + ++cnt_virtual_chars; + if (skipping) + --skipping; + else + { + ++body_count; + + if (bufptr >= sizeof(buf)) + { + writemem(buf, sizeof(buf)); + bufptr=0; + /*writeflush();*/ + } + buf[bufptr++]=c; + --cnt; + } + cache_virtual_chars=cnt_virtual_chars; + cache_phys_chars=cnt_phys_chars; + } + writemem(buf, bufptr); + writeflush(); + save_cached_offsets(start_seek_pos, cache_virtual_chars, + cache_phys_chars); +} + +static int dofetchheaderfields(struct fetchinfo *fi, const char *name) +{ + for (fi=fi->bodysublist; fi; fi=fi->next) + { + int i, a, b; + + if (fi->name == 0) continue; + for (i=0; fi->name[i] && name[i]; i++) + { + a=(unsigned char)name[i]; + a=toupper(a); + b=fi->name[i]; + b=toupper(b); + if (a != b) break; + } + if (fi->name[i] == 0 && name[i] == 0) return (1); + } + + return (0); +} + +static int dofetchheadernotfields(struct fetchinfo *fi, const char *name) +{ + return (!dofetchheaderfields(fi, name)); +} + +static int dofetchheadermime(struct fetchinfo *fi, const char *name) +{ +int i, a; +static const char mv[]="MIME-VERSION"; + + for (i=0; i<sizeof(mv)-1; i++) + { + a= (unsigned char)name[i]; + a=toupper(a); + if (a != mv[i]) break; + } + if (mv[i] == 0 && name[i] == 0) return (1); + + for (i=0; i<8; i++) + { + a= (unsigned char)name[i]; + a=toupper(a); + if (a != "CONTENT-"[i]) return (0); + } + return (1); +} + +static void dofetchheadersbuf(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *info, unsigned long msgnum, + struct rfc2045 *mimep, + int (*headerfunc)(struct fetchinfo *fi, const char *)) +{ +off_t start_pos, end_pos, start_body; +off_t nlines, nbodylines; +size_t i,j,k,l; +char buf[BUFSIZ+2]; +int goodheader; +unsigned long skipping; +unsigned long cnt; +char *p; +int ii; + + rfc2045_mimepos(mimep, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + if (fseek(fp, start_pos, SEEK_SET) == -1) + { + writes("{0}\r\n"); + fetcherror("fseek", fi, info, msgnum); + return; + } + + ii=fread(buf, 1, start_body - start_pos, fp); + if (ii < 0 || (i=ii) != start_body - start_pos) + { + fetcherror("unexpected EOF", fi, info, msgnum); + exit(1); + } + goodheader= (*headerfunc)(fi, ""); + + l=0; + for (j=0; j<i; ) + { + if (buf[j] != '\n' && buf[j] != '\r' && + !isspace((int)(unsigned char)buf[j])) + { + goodheader= (*headerfunc)(fi, ""); + + for (k=j; k<i; k++) + { + if (buf[k] == '\n' || buf[k] == ':') + break; + } + + if (k < i && buf[k] == ':') + { + buf[k]=0; + goodheader=(*headerfunc)(fi, buf+j); + buf[k]=':'; + } + } + else if (buf[j] == '\n') + goodheader=0; + + for (k=j; k<i; k++) + if (buf[k] == '\n') + { + ++k; + break; + } + + if (goodheader) + { + while (j<k) + buf[l++]=buf[j++]; + } + j=k; + } + + buf[l++]='\n'; /* Always append a blank line */ + + cnt=l; + for (i=0; i<l; i++) + if (buf[i] == '\n') ++cnt; + + skipping=0; + if (fi->ispartial) + { + skipping=fi->partialstart; + if (skipping > cnt) skipping=cnt; + cnt -= skipping; + if (fi->ispartial > 1 && cnt > fi->partialend) + cnt=fi->partialend; + } + + writes("{"); + writen(cnt); + writes("}\r\n"); + p=buf; + while (skipping) + { + if (*p == '\n') + { + --skipping; + if (skipping == 0) + { + if (cnt) + { + writes("\n"); + --cnt; + } + break; + } + } + --skipping; + ++p; + } + + while (cnt) + { + if (*p == '\n') + { + writes("\r"); + if (--cnt == 0) break; + writes("\n"); + --cnt; + ++p; + continue; + } + for (i=0; i<cnt; i++) + if (p[i] == '\n') + break; + writemem(p, i); + p += i; + cnt -= i; + header_count += i; + } +} + +struct fetchheaderinfo { + unsigned long skipping; + unsigned long cnt; + } ; + +static void countheader(struct fetchheaderinfo *, const char *, size_t); + +static void printheader(struct fetchheaderinfo *, const char *, size_t); + +static void dofetchheadersfile(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *info, unsigned long msgnum, + struct rfc2045 *mimep, + int (*headerfunc)(struct fetchinfo *fi, const char *)) +{ +off_t start_pos, end_pos, start_body, left; +off_t nlines, nbodylines; +size_t i; +int c, pass; +char buf1[256]; +int goodheader; +struct fetchheaderinfo finfo; + + finfo.cnt=0; + for (pass=0; pass<2; pass++) + { + void (*func)(struct fetchheaderinfo *, const char *, size_t)= + pass ? printheader:countheader; + + rfc2045_mimepos(mimep, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + if (fseek(fp, start_pos, SEEK_SET) == -1) + { + writes("{0}\r\n"); + fetcherror("fseek", fi, info, msgnum); + return; + } + if (pass) + { + finfo.skipping=0; + if (fi->ispartial) + { + finfo.skipping=fi->partialstart; + if (finfo.skipping > finfo.cnt) + finfo.skipping=finfo.cnt; + finfo.cnt -= finfo.skipping; + if (fi->ispartial > 1 && + finfo.cnt > fi->partialend) + finfo.cnt=fi->partialend; + } + + writes("{"); + writen(finfo.cnt+2); /* BUG */ + writes("}\r\n"); + } + left=start_body - start_pos; + + goodheader= (*headerfunc)(fi, ""); + while (left) + { + for (i=0; i<sizeof(buf1)-1 && i<left; i++) + { + c=getc(fp); + if (c == EOF) + { + fetcherror("unexpected EOF", fi, info, msgnum); + _exit(1); + } + + if (c == '\n' || c == ':') + { + ungetc(c, fp); + break; + } + buf1[i]=c; + } + buf1[i]=0; + left -= i; + + if (buf1[0] != '\n' && buf1[0] != '\r' && + !isspace((int)(unsigned char)buf1[0])) + goodheader= (*headerfunc)(fi, buf1); + else if (buf1[0] == '\n') + goodheader=0; + + if (!goodheader) + { + while (left) + { + c=getc(fp); + --left; + if (c == EOF) + { + fetcherror("unexpected EOF", fi, info, msgnum); + _exit(1); + } + if (c == '\n') break; + } + continue; + } + + (*func)(&finfo, buf1, i); + + i=0; + while (left) + { + c=getc(fp); + if (c == EOF) + { + fetcherror("unexpected EOF", fi, info, msgnum); + _exit(1); + } + --left; + if (i >= sizeof(buf1)) + { + (*func)(&finfo, buf1, i); + i=0; + } + if (c == '\n') + { + (*func)(&finfo, buf1, i); + buf1[0]='\r'; + i=1; + } + buf1[i++]=c; + if (c == '\n') break; + } + (*func)(&finfo, buf1, i); + if (pass && finfo.cnt == 0) break; + } + } + writes("\r\n"); /* BUG */ +} + +static void countheader(struct fetchheaderinfo *fi, const char *p, size_t s) +{ + fi->cnt += s; +} + +static void printheader(struct fetchheaderinfo *fi, const char *p, size_t s) +{ + size_t i; + + if (fi->skipping) + { + if (fi->skipping > s) + { + fi->skipping -= s; + return; + } + p += fi->skipping; + s -= fi->skipping; + fi->skipping=0; + } + if (s > fi->cnt) s=fi->cnt; + for (i=0; i <= s; i++) + if (p[i] != '\r') + ++header_count; + writemem(p, s); + fi->cnt -= s; +} + +static void rfc822(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *info, unsigned long msgnum, + struct rfc2045 *rfcp) +{ +unsigned long n=0; +int c; +char buf[BUFSIZ]; +unsigned long i; + + writes("RFC822 "); + + if (fseek(fp, 0L, SEEK_SET) == -1) + { + fetcherror("fseek", fi, info, msgnum); + writes("{0}\r\n"); + return; + } + while ((c=getc(fp)) != EOF) + { + ++n; + if (c == '\n') ++n; + } + + if (fseek(fp, 0L, SEEK_SET) == -1) + { + fetcherror("fseek", fi, info, msgnum); + writes("{0}\r\n"); + return; + } + writes("{"); + writen(n); + writes("}\r\n"); + + i=0; + while (n) + { + c=getc(fp); + if (c == '\n') + { + if (i >= sizeof(buf)) + { + writemem(buf, i); + i=0; + } + buf[i++]='\r'; + if (--n == 0) break; + } + + if (i >= sizeof(buf)) + { + writemem(buf, i); + i=0; + } + buf[i++]=c; + --n; + ++body_count; + } + writemem(buf, i); +} + +static void rfc822header(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *info, unsigned long msgnum, + struct rfc2045 *rfcp) +{ +unsigned long n=0; +int c; +char buf[BUFSIZ]; +unsigned long i; +int eol; + + writes("RFC822.HEADER "); + + if (fseek(fp, 0L, SEEK_SET) == -1) + { + fetcherror("fseek", fi, info, msgnum); + writes("{0}\r\n"); + return; + } + + eol=0; + while ((c=getc(fp)) != EOF) + { + ++n; + if (c != '\n') + { + eol=0; + continue; + } + ++n; + if (eol) break; + eol=1; + } + + if (fseek(fp, 0L, SEEK_SET) == -1) + { + fetcherror("fseek", fi, info, msgnum); + writes("{0}\r\n"); + return; + } + writes("{"); + writen(n); + writes("}\r\n"); + + i=0; + while (n) + { + c=getc(fp); + if (c == '\n') + { + if (i >= sizeof(buf)) + { + writemem(buf, i); + i=0; + } + buf[i++]='\r'; + if (--n == 0) break; + } + + if (i >= sizeof(buf)) + { + writemem(buf, i); + i=0; + } + buf[i++]=c; + --n; + ++header_count; + } + writemem(buf, i); +} + +static void rfc822text(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *info, unsigned long msgnum, + struct rfc2045 *rfcp) +{ +off_t start_pos, end_pos, start_body; +off_t nlines, nbodylines; +unsigned long i; +int c; +char buf[BUFSIZ]; +unsigned long l; + + writes("RFC822.TEXT {"); + + rfc2045_mimepos(rfcp, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + if (fseek(fp, start_body, SEEK_SET) == -1) + { + fetcherror("fseek", fi, info, msgnum); + writes("0}\r\n"); + return; + } + + i=end_pos - start_body + nbodylines; + + writen(i); + writes("}\r\n"); + + l=0; + while (i) + { + c=getc(fp); + if (c == EOF) + { + fetcherror("unexpected EOF", fi, info, msgnum); + _exit(1); + } + --i; + if (l >= sizeof(BUFSIZ)) + { + writemem(buf, l); + l=0; + } + if (c == '\n' && i) + { + --i; + buf[l++]='\r'; + if (l >= sizeof(BUFSIZ)) + { + writemem(buf, l); + l=0; + } + } + buf[l++]=c; + ++body_count; + } + writemem(buf, l); +} + +/* +** Poorly written IMAP clients (read: Netscape Messenger) like to issue +** consecutive partial fetches for downloading large messages. +** +** To save the time of reparsing the MIME structure, we cache it. +*/ + +static struct rfc2045 *cached_rfc2045p; +static char *cached_filename; + +void fetch_free_cached() +{ + if (cached_rfc2045p) + { + rfc2045_free(cached_rfc2045p); + cached_rfc2045p=0; + free(cached_filename); + cached_filename=0; + } +} + +struct rfc2045 *fetch_alloc_rfc2045(unsigned long msgnum, FILE *fp) +{ + if (cached_rfc2045p && + strcmp(cached_filename, + current_maildir_info.msgs[msgnum].filename) == 0) + return (cached_rfc2045p); + + fetch_free_cached(); + + if ((cached_filename=strdup(current_maildir_info. + msgs[msgnum].filename)) + == 0) write_error_exit(0); + + if (fseek(fp, 0L, SEEK_SET) == -1) + { + write_error_exit(0); + return (0); + } + cached_rfc2045p=rfc2045_fromfp(fp); + if (!cached_rfc2045p) + { + free(cached_filename); + cached_filename=0; + write_error_exit(0); + } + return (cached_rfc2045p); +} + +static FILE *cached_fp=0; +static char *cached_fp_filename=0; +static off_t cached_base_offset; +static off_t cached_virtual_offset; +static off_t cached_phys_offset; + +FILE *open_cached_fp(unsigned long msgnum) +{ + int fd; + + if (cached_fp && strcmp(cached_fp_filename, + current_maildir_info.msgs[msgnum].filename) + == 0) + return (cached_fp); + + if (cached_fp) + { + fclose(cached_fp); + free(cached_fp_filename); + cached_fp_filename=0; + cached_fp=0; + } + + fd=imapscan_openfile(current_mailbox, ¤t_maildir_info, msgnum); + if (fd < 0 || (cached_fp=fdopen(fd, "r")) == 0) + { + if (fd >= 0) close(fd); + + if ((cached_fp=tmpfile()) != 0) + { + fprintf(cached_fp, unavailable); + if (fseek(cached_fp, 0L, SEEK_SET) < 0 || + ferror(cached_fp)) + { + fclose(cached_fp); + cached_fp=0; + } + } + + if (cached_fp == 0) + { + fprintf(stderr, "ERR: %s: %s\n", + getenv("AUTHENTICATED"), +#if HAVE_STRERROR + strerror(errno) +#else + "error" +#endif + + ); + fflush(stderr); + _exit(1); + } + } + + if ((cached_fp_filename=strdup(current_maildir_info. + msgs[msgnum].filename)) + == 0) + { + fclose(cached_fp); + cached_fp=0; + write_error_exit(0); + } + cached_base_offset=0; + cached_virtual_offset=0; + cached_phys_offset=0; + return (cached_fp); +} + +void fetch_free_cache() +{ + if (cached_fp) + { + fclose(cached_fp); + cached_fp=0; + free(cached_fp_filename); + cached_fp_filename=0; + } +} + +void save_cached_offsets(off_t base, off_t virt, off_t phys) +{ + cached_base_offset=base; + cached_virtual_offset=virt; + cached_phys_offset=phys; +} + +int get_cached_offsets(off_t base, off_t *virt, off_t *phys) +{ + if (!cached_fp) + return (-1); + if (base != cached_base_offset) + return (-1); + + *virt=cached_virtual_offset; + *phys=cached_phys_offset; + return (0); +} diff --git a/imap/fetchinfo.c b/imap/fetchinfo.c new file mode 100644 index 0000000..96bd1e4 --- /dev/null +++ b/imap/fetchinfo.c @@ -0,0 +1,197 @@ +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#ifndef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> + +#include "imaptoken.h" +#include "imapwrite.h" +#include "fetchinfo.h" + + +/* This file contains functions to parse a FETCH attribute list */ + +static struct fetchinfo *alloc_headerlist(int); +static char *good_section(char *); + +struct fetchinfo *fetchinfo_alloc(int oneonly) +{ +struct fetchinfo *list, **listtail, *p; +struct imaptoken *tok; + + list=0; + listtail= &list; + + while ((tok=currenttoken())->tokentype == IT_ATOM) + { + if (oneonly && list) break; + *listtail=p=(struct fetchinfo *)malloc(sizeof(*list)); + if (!p) write_error_exit(0); + p->next=0; + p->name=my_strdup(tok->tokenbuf); + p->bodysection=0; + p->bodysublist=0; + p->ispartial=0; + listtail= &p->next; + + if (strcmp(p->name, "ALL") == 0 || + strcmp(p->name, "BODYSTRUCTURE") == 0 || + strcmp(p->name, "ENVELOPE") == 0 || + strcmp(p->name, "FLAGS") == 0 || + strcmp(p->name, "FAST") == 0 || + strcmp(p->name, "FULL") == 0 || + strcmp(p->name, "INTERNALDATE") == 0 || + strcmp(p->name, "RFC822") == 0 || + strcmp(p->name, "RFC822.HEADER") == 0 || + strcmp(p->name, "RFC822.SIZE") == 0 || + strcmp(p->name, "RFC822.TEXT") == 0 || + strcmp(p->name, "UID") == 0) + { + nexttoken(); + continue; + } + if (strcmp(p->name, "BODY") && strcmp(p->name, "BODY.PEEK")) + break; + if (nexttoken()->tokentype != IT_LBRACKET) continue; + + /* Parse BODY[ ... ] */ + + if ((tok=nexttoken())->tokentype != IT_RBRACKET) + { + char *s; + + if ( (tok->tokentype != IT_ATOM && + tok->tokentype != IT_NUMBER) || + !(s=good_section(tok->tokenbuf))) + { + fetchinfo_free(list); + return (0); + } + p->bodysection=my_strdup(tok->tokenbuf); + + if (strcmp(s, "HEADER.FIELDS") == 0 || + strcmp(s, "HEADER.FIELDS.NOT") == 0) + { + /* Must be followed by header list */ + + if ((tok=nexttoken_nouc())->tokentype + != IT_LPAREN) + { + p->bodysublist=alloc_headerlist(1); + if (p->bodysublist == 0) + { + fetchinfo_free(list); + return (0); + } + } + else + { + nexttoken_nouc(); + p->bodysublist=alloc_headerlist(0); + if ( currenttoken()->tokentype + != IT_RPAREN) + { + fetchinfo_free(list); + return (0); + } + } + } + tok=nexttoken(); + + } + else p->bodysection=my_strdup(""); + + if (tok->tokentype != IT_RBRACKET) + { + fetchinfo_free(list); + return (0); + } + tok=nexttoken(); + if (tok->tokentype == IT_ATOM && tok->tokenbuf[0] == '<' && + tok->tokenbuf[strlen(tok->tokenbuf)-1] == '>' && + (p->ispartial=sscanf(tok->tokenbuf+1, "%lu.%lu", + &p->partialstart, &p->partialend)) > 0) + nexttoken(); + } + return (list); +} + +/* Just validate that the syntax of the attribute is correct */ + +static char *good_section(char *p) +{ +int has_mime=0; + + while (isdigit((int)(unsigned char)*p)) + { + if (*p == '0') return (0); + has_mime=1; + while (isdigit((int)(unsigned char)*p)) ++p; + if (*p == '\0') + return (p); + + if (*p != '.') return (0); + ++p; + } + + if (strcmp(p, "HEADER") == 0 || + strcmp(p, "HEADER.FIELDS") == 0 || + strcmp(p, "HEADER.FIELDS.NOT") == 0 || + strcmp(p, "TEXT") == 0) + return (p); + + if (strcmp(p, "MIME") == 0 && has_mime) return (p); + return (0); +} + +/* Header list looks like atoms to me */ + +static struct fetchinfo *alloc_headerlist(int oneonly) +{ +struct fetchinfo *list, **listtail, *p; +struct imaptoken *tok; + + list=0; + listtail= &list; + + while ((tok=currenttoken())->tokentype == IT_ATOM || + tok->tokentype == IT_QUOTED_STRING || + tok->tokentype == IT_NUMBER) + { + *listtail=p=(struct fetchinfo *)malloc(sizeof(*list)); + if (!p) write_error_exit(0); + p->next=0; + p->name=my_strdup(tok->tokenbuf); + p->bodysublist=0; + p->bodysection=0; + listtail= &p->next; + if (oneonly) + break; + nexttoken_nouc(); + } + return (list); +} + +void fetchinfo_free(struct fetchinfo *p) +{ +struct fetchinfo *q; + + while (p) + { + if (p->bodysublist) fetchinfo_free(p->bodysublist); + q=p->next; + if (p->name) free(p->name); + if (p->bodysection) free(p->bodysection); + free(p); + p=q; + } +} diff --git a/imap/fetchinfo.h b/imap/fetchinfo.h new file mode 100644 index 0000000..ab2a82d --- /dev/null +++ b/imap/fetchinfo.h @@ -0,0 +1,30 @@ +#ifndef fetchinfo_h +#define fetchinfo_h + +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +struct fetchinfo { + struct fetchinfo *next; /* Siblings */ + char *name; /* Name */ + char *bodysection; /* BODY section */ + int ispartial; + unsigned long partialstart; + unsigned long partialend; + struct fetchinfo *bodysublist; /* HEADER sublist */ + } ; + +struct fetchinfo *fetchinfo_alloc(int); + +void fetchinfo_free(struct fetchinfo *); + +void fetch_free_cache(); + +void save_cached_offsets(off_t, off_t, off_t); + +int get_cached_offsets(off_t, off_t *, off_t *); + +#endif diff --git a/imap/html2man.pl.in b/imap/html2man.pl.in new file mode 100644 index 0000000..0a8ca70 --- /dev/null +++ b/imap/html2man.pl.in @@ -0,0 +1,174 @@ +#! @PERL@ +# +# Copyright 1998 - 1999 Double Precision, Inc. See COPYING for +# distribution information. + +############################################################################ +# +# Preprocess HTML file: put all directives on a separate line. Remove +# blank lines. +# +# +############################################################################ + +$pid=open(FD, "-|"); + +die "Can't fork.\n" unless defined $pid; + +if ($pid == 0) +{ + while (<>) + { + if ( $_ =~ s/^ *<[lL][iI]>// ) + { + $line=$_; + $line=<> if $line eq "\n"; + chop $line; + $line =~ s/ - /\n/; + ($line0,$line1)=split(/\n/,$line); + $line0 =~ s/"/\\"/g; + $line0 =~ s/\\/\\\\/g; + print ".TP\n.B \"$line0\n$line1\n"; + next; + } + while ( /<[^>]*\n$/ ) + { + chop; + $foo=$_; + last unless defined ($_=<>); + $_="$foo$_"; + } + print; + } + exit 0; +} + +$pid2=open(FD2, "-|"); +die "Can't fork.\n" unless defined $pid2; + +sub dosubst { + s/<[^>]*>//g; + s/ / /g; + s/</</g; + s/>/>/g; + s/&/\&/g; +} + +$INH1=0; +$INBODY=0; + +$inpre=0; + +if ($pid2 == 0) +{ + while (<FD>) + { + s/\\/\\\\/g; + s/<[iI]>/\\fI/g; + s/<\/[iI]>/\\fP/g; + s/<BR>/\n.br/g; + s/<br>/\n.br/g; + s/<[pP]>/\n.PP\n/g; + s/^\n\././; + + s/^ *// unless $inpre; + if (s/^<[hH]1>/.SH NAME\n/) + { + $INH1=1; + } + s/-/\\-/ if $INH1; + $INH1=0 if ( /<\/[hH]1>/ ); + + if (s/^<[hH]2>//) + { + $_=<FD> if $_ eq "\n"; + &dosubst; + $_ =~ s/^/.SH "/; + print $_; + next; + } + + if (s/^<[hH][3456789]>//) + { + $_=<FD> if $_ eq "\n"; + &dosubst; + $_ =~ s/^/.SS "/; + print $_; + next; + } + if (/^ *<(TITLE|title)>/) + { + while ( ! /<\/(title|TITLE)>/) + { + chop; + $_ = $_ . <FD>; + } + } + + + if (/^ *<(TITLE|title)>(.*)<\/(title|TITLE)>/) + { + ($cmd, $desc)=split(/ - /,$2); + $cmd =~ s/ *$//; + $desc =~ s/^ *//; + + open (DATE, 'date "+%B %e, %Y" | ') + || die "Can't run date.\n"; + $date=<DATE>; + close(DATE); + chomp $date; + $TITLE=".TH \"$cmd\" [SECTION] \"$date\" \"Double Precision, Inc.\" \"\"\n"; + next; + } + + if (/^<!-- *SECTION/) + { + chop; + s/.*SECTION *//; + s/ .*//; + $SECTION=$_; + next; + } + if (/^<!-- \$Id/) + { + s/.*\$Id/\$Id/; + s/ *-->.*//; + $RCS=".\\\" $_"; + print $RCS if $INBODY; + next; + } + if (/<\/(HEAD|head)>/) + { + $TITLE =~ s/\[SECTION\]/$SECTION/; + print $TITLE; + print $RCS; + print ".\\\" Copyright 1998-1999 Double Precision, Inc. See COPYING for\n"; + print ".\\\" distribution information.\n"; + $INBODY=1; + } + + s/^\./\\\&./ unless /^\.(SH|PP|br|TP|B|I) / || /^\.(SH|PP|br|TP|B|I|)\n/; + + $inpre=1 if s/^<(PRE|pre)>/.nf\n\n/; + $inpre=0 if s/<\/(PRE|pre)>/\n.fi\n.PP/; + + &dosubst; + print "$_"; + } + exit 0; +} + +$first=1; +$innf=0; +while (<FD2>) +{ + $first=0 if /^.TH/; + next if $first; + next if (! $innf) && /^\n$/; + $innf=1 if /^\.nf/; + $innf=0 if /^\.fi/; + + s/^ ? ? ?// if $innf; + print; +} +exit 0; diff --git a/imap/imapd-ssl.dist.in b/imap/imapd-ssl.dist.in new file mode 100644 index 0000000..41df386 --- /dev/null +++ b/imap/imapd-ssl.dist.in @@ -0,0 +1,278 @@ +##VERSION: $Id:$ +# +# imapd-ssl created from imapd-ssl.dist by sysconftool +# +# Do not alter lines that begin with ##, they are used when upgrading +# this configuration. +# +# Copyright 2000 - 2008 Double Precision, Inc. See COPYING for +# distribution information. +# +# This configuration file sets various options for the Courier-IMAP server +# when used to handle SSL IMAP connections. +# +# SSL and non-SSL connections are handled by a dedicated instance of the +# couriertcpd daemon. If you are accepting both SSL and non-SSL IMAP +# connections, you will start two instances of couriertcpd, one on the +# IMAP port 143, and another one on the IMAP-SSL port 993. +# +# Download OpenSSL from http://www.openssl.org/ +# +##NAME: SSLPORT:1 +# +# Options in the imapd-ssl configuration file AUGMENT the options in the +# imapd configuration file. First the imapd configuration file is read, +# then the imapd-ssl configuration file, so we do not have to redefine +# anything. +# +# However, some things do have to be redefined. The port number is +# specified by SSLPORT, instead of PORT. The default port is port 993. +# +# Multiple port numbers can be separated by commas. When multiple port +# numbers are used it is possibly to select a specific IP address for a +# given port as "ip.port". For example, "127.0.0.1.900,192.168.0.1.900" +# accepts connections on port 900 on IP addresses 127.0.0.1 and 192.168.0.1 +# The SSLADDRESS setting is a default for ports that do not have +# a specified IP address. + +SSLPORT=993 + +##NAME: SSLADDRESS:0 +# +# Address to listen on, can be set to a single IP address. +# +# SSLADDRESS=127.0.0.1 + +SSLADDRESS=0 + +##NAME: SSLPIDFILE:0 +# +# That's the SSL IMAP port we'll listen on. +# Feel free to redefine MAXDAEMONS, TCPDOPTS, and MAXPERIP. + +SSLPIDFILE=@piddir@/imapd-ssl.pid + +##NAME: SSLLOGGEROPTS:0 +# +# courierlogger(1) options. +# + +SSLLOGGEROPTS="-name=imapd-ssl" + +##NAME: IMAPDSSLSTART:0 +# +# Different pid files, so that both instances of couriertcpd can coexist +# happily. +# +# You can also redefine IMAP_CAPABILITY, although I can't +# think of why you'd want to do that. +# +# +# Ok, the following settings are new to imapd-ssl: +# +# Whether or not to start IMAP over SSL on simap port: + +IMAPDSSLSTART=NO + +##NAME: IMAPDSTARTTLS:0 +# +# Whether or not to implement IMAP STARTTLS extension instead: + +IMAPDSTARTTLS=YES + +##NAME: IMAP_TLS_REQUIRED:1 +# +# Set IMAP_TLS_REQUIRED to 1 if you REQUIRE STARTTLS for everyone. +# (this option advertises the LOGINDISABLED IMAP capability, until STARTTLS +# is issued). + +IMAP_TLS_REQUIRED=0 + + +######################################################################### +# +# The following variables configure IMAP over SSL. If OpenSSL or GnuTLS +# is available during configuration, the couriertls helper gets compiled, and +# upon installation a dummy TLS_CERTFILE gets generated. +# +# WARNING: Peer certificate verification has NOT yet been tested. Proceed +# at your own risk. Only the basic SSL/TLS functionality is known to be +# working. Keep this in mind as you play with the following variables. +# +##NAME: COURIERTLS:0 +# + +COURIERTLS=@bindir@/couriertls + +##NAME: TLS_PRIORITY:0 +# +# Set TLS protocol priority settings (GnuTLS only) +# +# DEFAULT: NORMAL:-CTYPE-OPENPGP +# +# TLS_PRIORITY="NORMAL:-CTYPE-OPENPGP" + +##NAME: TLS_PROTOCOL:0 +# +# TLS_PROTOCOL sets the protocol version. The possible versions are: +# +# OpenSSL: +# +# SSL3 - SSLv3 +# SSL23 - either SSLv2 or SSLv3 (also TLS1, it seems) +# TLS1 - TLS1 +# +# Note that this setting, with OpenSSL, is modified by the TLS_CIPHER_LIST +# setting, below. +# +# DEFAULT VALUES: +# +# SSL23 + +##NAME: TLS_STARTTLS_PROTOCOL:0 +# +# TLS_STARTTLS_PROTOCOL is used instead of TLS_PROTOCOL for the IMAP STARTTLS +# extension, as opposed to IMAP over SSL on port 993. +# +# It takes the same values for OpenSSL as TLS_PROTOCOL + +##NAME: TLS_CIPHER_LIST:0 +# +# TLS_CIPHER_LIST optionally sets the list of ciphers to be used by the +# OpenSSL library. In most situations you can leave TLS_CIPHER_LIST +# undefined +# +# OpenSSL: +# +# TLS_CIPHER_LIST="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH" +# +# + +##NAME: TLS_MIN_DH_BITS:0 +# +# TLS_MIN_DH_BITS=n +# +# GnuTLS only: +# +# Set the minimum number of acceptable bits for a DH key exchange. +# +# GnuTLS's compiled-in default is 727 bits (as of GnuTLS 1.6.3). Some server +# have been encountered that offer 512 bit keys. You may have to set +# TLS_MIN_DH_BITS=512 here, if necessary. + +##NAME: TLS_TIMEOUT:0 +# TLS_TIMEOUT is currently not implemented, and reserved for future use. +# This is supposed to be an inactivity timeout, but its not yet implemented. +# + +##NAME: TLS_DHCERTFILE:0 +# +# TLS_DHCERTFILE - PEM file that stores a Diffie-Hellman -based certificate. +# When OpenSSL is compiled to use Diffie-Hellman ciphers instead of RSA +# you must generate a DH pair that will be used. In most situations the +# DH pair is to be treated as confidential, and the file specified by +# TLS_DHCERTFILE must not be world-readable. +# +# TLS_DHCERTFILE= + +##NAME: TLS_CERTFILE:0 +# +# TLS_CERTFILE - certificate to use. TLS_CERTFILE is required for SSL/TLS +# servers, and is optional for SSL/TLS clients. TLS_CERTFILE is usually +# treated as confidential, and must not be world-readable. Set TLS_CERTFILE +# instead of TLS_DHCERTFILE if this is a garden-variety certificate +# +# VIRTUAL HOSTS (servers only): +# +# Due to technical limitations in the original SSL/TLS protocol, a dedicated +# IP address is required for each virtual host certificate. If you have +# multiple certificates, install each certificate file as +# $TLS_CERTFILE.aaa.bbb.ccc.ddd, where "aaa.bbb.ccc.ddd" is the IP address +# for the certificate's domain name. So, if TLS_CERTFILE is set to +# /etc/certificate.pem, then you'll need to install the actual certificate +# files as /etc/certificate.pem.192.168.0.2, /etc/certificate.pem.192.168.0.3 +# and so on, for each IP address. +# +# GnuTLS only (servers only): +# +# GnuTLS implements a new TLS extension that eliminates the need to have a +# dedicated IP address for each SSL/TLS domain name. Install each certificate +# as $TLS_CERTFILE.domain, so if TLS_CERTFILE is set to /etc/certificate.pem, +# then you'll need to install the actual certificate files as +# /etc/certificate.pem.host1.example.com, /etc/certificate.pem.host2.example.com +# and so on. +# +# Note that this TLS extension also requires a corresponding support in the +# client. Older SSL/TLS clients may not support this feature. +# +# This is an experimental feature. + +TLS_CERTFILE=@certsdir@/imapd.pem + +##NAME: TLS_TRUSTCERTS:0 +# +# TLS_TRUSTCERTS=pathname - load trusted certificates from pathname. +# pathname can be a file or a directory. If a file, the file should +# contain a list of trusted certificates, in PEM format. If a +# directory, the directory should contain the trusted certificates, +# in PEM format, one per file and hashed using OpenSSL's c_rehash +# script. TLS_TRUSTCERTS is used by SSL/TLS clients (by specifying +# the -domain option) and by SSL/TLS servers (TLS_VERIFYPEER is set +# to PEER or REQUIREPEER). +# + +TLS_TRUSTCERTS=@cacerts@ + +##NAME: TLS_VERIFYPEER:0 +# +# TLS_VERIFYPEER - how to verify client certificates. The possible values of +# this setting are: +# +# NONE - do not verify anything +# +# PEER - verify the client certificate, if one's presented +# +# REQUIREPEER - require a client certificate, fail if one's not presented +# +# +TLS_VERIFYPEER=NONE + +##NAME: TLS_EXTERNAL:0 +# +# To enable SSL certificate-based authentication: +# +# 1) TLS_TRUSTCERTS must be set to a pathname that holds your certificate +# authority's SSL certificate +# +# 2) TLS_VERIFYPEER=PEER or TLS_VERIFYPEER=REQUIREPEER (the later settings +# requires all SSL clients to present a certificate, and rejects +# SSL/TLS connections without a valid cert). +# +# 3) Set TLS_EXTERNAL, below, to the subject field that holds the login ID. +# Example: +# +# TLS_EXTERNAL=emailaddress +# +# The above example retrieves the login ID from the "emailaddress" subject +# field. The certificate's emailaddress subject must match exactly the login +# ID in the courier-authlib database. + +##NAME: TLS_CACHE:0 +# +# A TLS/SSL session cache may slightly improve response for IMAP clients +# that open multiple SSL sessions to the server. TLS_CACHEFILE will be +# automatically created, TLS_CACHESIZE bytes long, and used as a cache +# buffer. +# +# This is an experimental feature and should be disabled if it causes +# problems with SSL clients. Disable SSL caching by commenting out the +# following settings: + +TLS_CACHEFILE=@localstatedir@/couriersslcache +TLS_CACHESIZE=524288 + +##NAME: MAILDIRPATH:0 +# +# MAILDIRPATH - directory name of the maildir directory. +# +MAILDIRPATH=Maildir diff --git a/imap/imapd.authpam b/imap/imapd.authpam new file mode 100644 index 0000000..54aa3ab --- /dev/null +++ b/imap/imapd.authpam @@ -0,0 +1,15 @@ +#%PAM-1.0 +# +# +# Copyright 1998-2001 Double Precision, Inc. See COPYING for +# distribution information. +# +# To use the authpam authentication module with imapd, you must +# configure your PAM library to authenticate the "imap" service. +# See your system documentation for information on how to configure your +# PAM services. In most cases, all you need to do is to install this file +# as /etc/pam.d/imapd, but check your system documentation to make sure. + +auth required /lib/security/pam_pwdb.so shadow nullok +account required /lib/security/pam_pwdb.so +session required /lib/security/pam_pwdb.so diff --git a/imap/imapd.c b/imap/imapd.c new file mode 100644 index 0000000..7a61e2b --- /dev/null +++ b/imap/imapd.c @@ -0,0 +1,6665 @@ +/* +** Copyright 1998 - 2011 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <fcntl.h> +#include <pwd.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif +#if HAVE_LOCALE_H +#include <locale.h> +#endif +#if HAVE_SYS_UTSNAME_H +#include <sys/utsname.h> +#endif + +#include <courierauth.h> +#include "maildir/maildiraclt.h" +#include "maildir/maildirnewshared.h" + +#include <sys/types.h> +#include <sys/stat.h> + +#include "imaptoken.h" +#include "imapwrite.h" +#include "imapscanclient.h" + +#include "mysignal.h" +#include "imapd.h" +#include "fetchinfo.h" +#include "searchinfo.h" +#include "storeinfo.h" +#include "mailboxlist.h" +#include "thread.h" +#include "outbox.h" + +#include "maildir/config.h" +#include "maildir/maildiraclt.h" +#include "maildir/maildircreate.h" +#include "maildir/maildirrequota.h" +#include "maildir/maildirgetquota.h" +#include "maildir/maildirquota.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirwatch.h" +#include "maildir/maildirkeywords.h" +#include "maildir/maildirinfo.h" +#include "maildir/loginexec.h" + +#include "unicode/unicode.h" +#include "maildir/maildirkeywords.h" +#include "courierauth.h" + +#define KEYWORD_IMAPVERBOTTEN " (){%*\"\\]" +#define KEYWORD_SMAPVERBOTTEN "," + +extern time_t rfc822_parsedt(const char *); +extern void fetchflags(unsigned long); +extern unsigned long header_count, body_count; +extern time_t start_time; +extern void smap(); +extern void smap_fetchflags(unsigned long); + +extern int do_fetch(unsigned long, int, void *); +extern void fetch_free_cached(); +extern int keywords(); +extern int fastkeywords(); +extern void imapscanfail(const char *); +extern void bye_msg(const char *); + +extern void mainloop(); +extern void initcapability(); +extern void imapcapability(); +extern int magictrash(); + +#if SMAP +int smapflag=0; + +extern void snapshot_save(); +extern void snapshot_needed(); + +#endif + +static const char *protocol; + +char *dot_trash = "." TRASH; +char *trash = TRASH; + +char *current_mailbox=0; /* .folder */ +FILE *debugfile=0; +#if 0 +char *imapscanpath; +#endif + +int current_temp_fd=-1; +const char *current_temp_fn=NULL; + +struct imapscaninfo current_maildir_info; +int current_mailbox_ro; +char *current_mailbox_acl; + +dev_t homedir_dev; +ino_t homedir_ino; + +void rfc2045_error(const char *p) +{ + if (write(2, p, strlen(p)) < 0) + _exit(1); + _exit(0); +} + + +extern int maildirsize_read(const char *,int *,off_t *,unsigned *,unsigned *,struct stat *); + +int maildir_info_suppress(const char *maildir) +{ + struct stat stat_buf; + + if (stat(maildir, &stat_buf) < 0 || + /* maildir inaccessible, perhaps another server? */ + + (stat_buf.st_dev == homedir_dev && + stat_buf.st_ino == homedir_ino) + /* Exclude ourselves from the shared list */ + + ) + { + return 1; + } + + return 0; +} + + +void quotainfo_out(const char* qroot) +{ + char quotabuf[QUOTABUFSIZE]; + char qresult[200]=""; + char qbuf[200]; + + if ((maildir_getquota(".", quotabuf) == 0) && (strcmp(qroot,"ROOT") == 0)) + { + struct maildirsize quotainfo; + + if (maildir_openquotafile("ainfo, ".") == 0) + maildir_closequotafile("ainfo); + else + quotainfo.quota.nbytes=quotainfo.size.nbytes= + quotainfo.quota.nmessages= + quotainfo.size.nmessages=0; + + if (quotainfo.quota.nbytes > 0) + { + sprintf(qbuf,"STORAGE %ld %ld", + (long)((quotainfo.size.nbytes+1023)/1024), + (long)((quotainfo.quota.nbytes+1023)/1024)); + strcat(qresult,qbuf); + } + if (quotainfo.quota.nmessages > 0) + { + sprintf(qbuf,"MESSAGE %d %d", + quotainfo.size.nmessages, + quotainfo.quota.nmessages); + if (strcmp(qresult,"")!=0) strcat(qresult," "); + strcat(qresult,qbuf); + } + } + + writes("* "); + writes("QUOTA \""); + writes(qroot); + writes("\""); + if (strcmp(qresult,"")!=0) + { + writes(" ("); + writes(qresult); + writes(")"); + }; + writes("\r\n"); +} + +int is_trash(const char *m) +{ + if (strcmp(m, dot_trash)) + { + /* + * not trying to delete .Trash but folder inside of .Trash + */ + return (0); + } + else + { + /* + * trying to delete .Trash - stop them + */ + return (1); + } +} + +void emptytrash() +{ + char *dir, *all_settings, *next_folder, *folder, *p; + unsigned l; + + all_settings=getenv("IMAP_EMPTYTRASH"); + + if (!all_settings) + return; + + all_settings=strdup(all_settings); + if (!all_settings) + return; + + if (strchr(all_settings, ':') == 0 && + strchr(all_settings, ',') == 0) + { + l=atoi(all_settings); + + if (l <= 0) + l=1; + + maildir_getnew(".", trash, NULL, NULL); + if ((dir=maildir_folderdir(".", trash))) + { + maildir_purge(dir, l * 24 * 60 * 60); + free(dir); + } + free(all_settings); + return; + } + + for (folder=all_settings; folder && *folder; ) + { + if (*folder == ',') + { + ++folder; + continue; + } + next_folder=strchr(folder, ','); + if (next_folder) + *next_folder++=0; + + p=strchr(folder, ':'); + if (!p) + { + folder=next_folder; + continue; + } + + *p++=0; + + l=atoi(p); + if (l <= 0) l=1; + + maildir_getnew(".", folder, NULL, NULL); + if ((dir=maildir_folderdir(".", folder))) + { + maildir_purge(dir, l * 24 * 60 * 60); + free(dir); + } + folder=next_folder; + } + free(all_settings); +} + +#if 0 +int is_draft(const char *m) +{ +#if 1 + /* Fix some PINE bugs first */ + + if (strcmp(m, "." DRAFTS)) return (0); + return (1); +#else + return (0); +#endif +} +#endif + +int is_reserved(const char *m) +{ + if (strncmp(m, "./", 2) == 0) m += 2; + + if (is_trash(m)) return (1); + return (0); +} + +int is_reserved_name(const char *name) +{ + if (strncmp(name, INBOX, strlen(INBOX)) == 0) + return is_trash(name+strlen(INBOX)); + return 0; +} + +char *decode_valid_mailbox(const char *p, int autosubscribe) +{ + struct maildir_info mi; + char *q, *r; + + if (maildir_info_imap_find(&mi, p, getenv("AUTHENTICATED")) < 0) + { + return NULL; + } + + if (mi.homedir && mi.maildir) + { + q=maildir_name2dir(mi.homedir, mi.maildir); + + if (q) + { + r=malloc(strlen(q)+sizeof("/.")); + if (!r) write_error_exit(0); + strcat(strcpy(r, q), "/."); + if (access(r, 0) == 0) + { + free(r); + maildir_info_destroy(&mi); + return q; + } + free(r); + free(q); + } + maildir_info_destroy(&mi); + return NULL; + } + + if (mi.mailbox_type == MAILBOXTYPE_OLDSHARED) + { + const char *q; + char *r; + + if ((q=strchr(p, '.')) == NULL) + { + maildir_info_destroy(&mi); + errno=EINVAL; + return NULL; + } + + r=maildir_shareddir(".", q+1); + if (!r) + { + maildir_info_destroy(&mi); + errno=EINVAL; + return NULL; + } + + if (access(r, 0) == 0) + { + maildir_info_destroy(&mi); + return r; + } + + maildir_shared_subscribe(".", q+1); + if (access(r, 0) == 0) + { + maildir_info_destroy(&mi); + return r; + } + + free(r); + maildir_info_destroy(&mi); + return NULL; + } + maildir_info_destroy(&mi); + return (NULL); +} + +static time_t decode_date_time(char *p) +{ +unsigned i; + + /* Convert to format rfc822_parsedt likes */ + + for (i=1; p[i] != ' '; i++) + { + if (!p[i]) return (0); + if (p[i] == '-') p[i]=' '; + } + return (rfc822_parsedt(p)); +} + +int get_flagname(const char *p, struct imapflags *flags) +{ + if (strcasecmp(p, "\\SEEN") == 0) + flags->seen=1; + else if (strcasecmp(p, "\\ANSWERED") == 0) + flags->answered=1; + else if (strcasecmp(p, "\\DRAFT") == 0) + flags->drafts=1; + else if (strcasecmp(p, "\\DELETED") == 0) + flags->deleted=1; + else if (strcasecmp(p, "\\FLAGGED") == 0) + flags->flagged=1; + else return (-1); + return (0); +} + +int valid_keyword(const char *kw) +{ + const char *p; + + if (!keywords()) + return 0; + + /* Check for valid keyword names */ + + for (p=kw; *p; p++) + { + if ((unsigned char)*p <= ' ' + || strchr(KEYWORD_IMAPVERBOTTEN, *p)) + return 0; + } + return 1; +} + +int get_keyword(struct libmail_kwMessage **kwPtr, const char *kw) +{ + if (libmail_kwmSetName(current_maildir_info.keywordList, *kwPtr, kw) < 0) + write_error_exit(0); + + return 0; +} + + +int get_flagsAndKeywords(struct imapflags *flags, + struct libmail_kwMessage **kwPtr) +{ +struct imaptoken *t; + + while ((t=nexttoken_nouc())->tokentype == IT_ATOM) + { + if (get_flagname(t->tokenbuf, flags)) + { + if (!valid_keyword(t->tokenbuf)) + return -1; + + if (get_keyword(kwPtr, t->tokenbuf)) + return -1; + } + } + return (t->tokentype == IT_RPAREN ? 0:-1); +} + +void get_message_flags( + struct imapscanmessageinfo *mi, + char *buf, struct imapflags *flags) +{ + const char *filename=mi->filename; + + const char *DRAFT="\\Draft"; + const char *FLAGGED="\\Flagged"; + const char *REPLIED="\\Answered"; + const char *SEEN="\\Seen"; + const char *DELETED="\\Deleted"; + const char *RECENT="\\Recent"; + + const char *SPC=" "; + + if (buf) + *buf=0; + + if (flags) + flags->seen=flags->answered=flags->deleted=flags->flagged + =flags->recent=flags->drafts=0; + + if ((filename=strrchr(filename, MDIRSEP[0])) == 0 + || strncmp(filename, MDIRSEP "2,", 3)) return; + +#if SMAP + if (smapflag) + { + SPC=","; + DRAFT="DRAFT"; + FLAGGED="MARKED"; + REPLIED="REPLIED"; + SEEN="SEEN"; + DELETED="DELETED"; + RECENT="RECENT"; + } +#endif + + if (strchr(filename, 'D')) + { + if (buf) strcat(buf, DRAFT); + if (flags) flags->drafts=1; + } + + if (strchr(filename, 'F')) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), FLAGGED); + if (flags) flags->flagged=1; + } + if (strchr(filename, 'R')) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), REPLIED); + if (flags) flags->answered=1; + } + + if (strchr(filename, 'S') != NULL) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), SEEN); + if (flags) flags->seen=1; + } + + if (strchr(filename, 'T')) + { + if (buf) strcat(strcat(buf, *buf ? SPC:""), DELETED); + if (flags) flags->deleted=1; + } + + if (mi->recentflag) + { + if (flags) flags->recent=1; + if (buf) strcat(strcat(buf, *buf ? SPC:""), RECENT); + } +} + +static char *parse_mailbox_error(const char *tag, + struct imaptoken *curtoken, + int ok_hierarchy, /* RFC 2060 errata - DELETE can take + ** a trailing hierarchy separator if the + ** IMAP server supports subfolders of + ** a real folder (such as this one). + */ + + int autosubscribe) /* Really DUMP clients that do a LIST, + ** and don't bother to check if a folder is + ** subscribed to, or not (Pine) + */ +{ +char *mailbox; + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + mailbox=0; + } + else + { + if (ok_hierarchy && (mailbox=strrchr(curtoken->tokenbuf, + HIERCH)) && mailbox[1] == 0) + *mailbox=0; + + mailbox=decode_valid_mailbox(curtoken->tokenbuf, + autosubscribe); + } + + if ( mailbox == 0) + { + writes(tag); + writes(" NO Mailbox does not exist, or must be subscribed to.\r\n"); + return (0); + } + return (mailbox); +} + +/* + STORE NEW MESSAGE INTO A MAILBOX +*/ + +void append_flags(char *buf, struct imapflags *flags) +{ + if (flags->drafts) strcat(buf, "D"); + if (flags->flagged) strcat(buf, "F"); + if (flags->answered) strcat(buf, "R"); + if (flags->seen) strcat(buf, "S"); + if (flags->deleted) strcat(buf, "T"); +} + + /* First, figure out the filenames used in tmp and new */ + +FILE *maildir_mkfilename(const char *mailbox, struct imapflags *flags, + unsigned long s, char **tmpname, char **newname) +{ + char *p; + char uniqbuf[80]; + static unsigned uniqcnt=0; + FILE *fp; + struct maildir_tmpcreate_info createInfo; + + sprintf(uniqbuf, "%u", uniqcnt++); + + maildir_tmpcreate_init(&createInfo); + createInfo.openmode=0666; + createInfo.maildir=mailbox; + createInfo.uniq=uniqbuf; + createInfo.msgsize=s; + createInfo.hostname=getenv("HOSTNAME"); + createInfo.doordie=1; + + if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) + return NULL; + + *tmpname=createInfo.tmpname; + *newname=createInfo.newname; + + createInfo.tmpname=NULL; + createInfo.newname=NULL; + maildir_tmpcreate_free(&createInfo); + + strcpy(uniqbuf, MDIRSEP "2,"); + append_flags(uniqbuf, flags); + + /* Ok, this message will really go to cur, not new */ + + p=malloc(strlen(*newname)+strlen(uniqbuf)+1); + if (!p) write_error_exit(0); + strcpy(p, *newname); + memcpy(strrchr(p, '/')-3, "cur", 3); /* HACK OF THE MILLENIA */ + strcat(p, uniqbuf); + free(*newname); + *newname=p; + return fp; +} + +void set_time(const char *tmpname, time_t timestamp) +{ +#if HAVE_UTIME + if (timestamp) + { + struct utimbuf ub; + + ub.actime=ub.modtime=timestamp; + utime(tmpname, &ub); + } +#else +#if HAVE_UTIMES + if (timestamp) + { + struct timeval tv; + + tv.tv_sec=timestamp; + tv.tv_usec=0; + utimes(tmpname, &tv); + } +#endif +#endif +} + +static int store_mailbox(const char *tag, const char *mailbox, + struct imapflags *flags, + struct libmail_kwMessage *keywords, + time_t timestamp, + unsigned long nbytes, + unsigned long *new_uidv, + unsigned long *new_uid) +{ +char *tmpname; +char *newname; +char *p; +char *e; +FILE *fp; +unsigned long n; +static const char nowrite[]=" NO [ALERT] Cannot create message - no write permission or out of disk space.\r\n"; +int lastnl; +int rb; + + fp=maildir_mkfilename(mailbox, flags, 0, &tmpname, &newname); + + if (!fp) + { + writes(tag); + writes(nowrite); + return -1; + } + + writes("+ OK\r\n"); + writeflush(); + lastnl=0; + + current_temp_fd=fileno(fp); + current_temp_fn=tmpname; + + while (nbytes) + { + read_string(&p, &n, nbytes); + nbytes -= n; + if (p[n-1] == '\n') lastnl = 1; + else lastnl = 0; + + while (n) + { + e = memchr(p, '\r', n); + if (e && p == e) + { + rb=1; + } + else if (e) + { + rb = fwrite(p, 1, e-p, fp); + } + else + { + rb = fwrite(p, 1, n, fp); + } + n -= rb; + p += rb; + } + } + if (!lastnl) putc('\n', fp); + + current_temp_fd=-1; + current_temp_fn=NULL; + + if (fflush(fp) || ferror(fp)) + { + fprintf(stderr, + "ERR: error storing a message, user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + + fclose(fp); + unlink(tmpname); + writes(tag); + writes(nowrite); + free(tmpname); + free(newname); + return (-1); + } + + nbytes=ftell(fp); + if (nbytes == (unsigned long)-1 || + (p=maildir_requota(newname, nbytes)) == 0) + + { + fclose(fp); + unlink(tmpname); + writes(tag); + writes(nowrite); + free(tmpname); + free(newname); + return (-1); + } + + free(newname); + + fclose(fp); + + if (maildirquota_countfolder(mailbox) && + maildirquota_countfile(p)) + { + struct maildirsize quotainfo; + + if (maildir_quota_add_start(mailbox, "ainfo, nbytes, 1, + getenv("MAILDIRQUOTA"))) + { + unlink(tmpname); + free(tmpname); + free(p); + writes(tag); + writes(" NO [ALERT] You exceeded your mail quota.\r\n"); + return (-1); + } + maildir_quota_add_end("ainfo, nbytes, 1); + } + + if (check_outbox(tmpname, mailbox)) + { + unlink(tmpname); + writes(tag); + writes(" NO [ALERT] Unable to send E-mail message.\r\n"); + free(tmpname); + free(p); + return (-1); + } + + { + struct imapscaninfo new_maildir_info; + struct uidplus_info new_uidplus_info; + int rc; + + imapscan_init(&new_maildir_info); + + memset(&new_uidplus_info, 0, sizeof(new_uidplus_info)); + + new_uidplus_info.tmpfilename=tmpname; + new_uidplus_info.curfilename=p; + new_uidplus_info.mtime=timestamp; + + if (keywords && keywords->firstEntry) + { + if (maildir_kwSave(mailbox, + strrchr(p, '/')+1, + keywords, + &new_uidplus_info.tmpkeywords, + &new_uidplus_info.newkeywords, + 0)) + { + unlink(tmpname); + writes(tag); + writes(" NO [ALERT] "); + writes(strerror(errno)); + free(tmpname); + free(p); + return (-1); + } + } + + rc=imapscan_maildir(&new_maildir_info, mailbox, 0, 0, + &new_uidplus_info); + + if (new_uidplus_info.tmpkeywords) + free(new_uidplus_info.tmpkeywords); + + if (new_uidplus_info.newkeywords) + free(new_uidplus_info.newkeywords); + + if (rc) + { + free(tmpname); + free(p); + writes(tag); + writes(nowrite); + return -1; + } + + *new_uidv=new_maildir_info.uidv; + *new_uid=new_uidplus_info.uid; + imapscan_free(&new_maildir_info); + } + + free(tmpname); + free(p); + return (0); +} + + +/************** Create and delete folders **************/ + +#if 0 +static int checksubdir(const char *s) +{ +DIR *dirp; +struct dirent *de; + + dirp=opendir(s); + while (dirp && (de=readdir(dirp)) != 0) + if (de->d_name[0] != '.') + { + closedir(dirp); + return (1); + } + if (dirp) closedir(dirp); + return (0); +} +#endif + +int mddelete(const char *s) +{ + int rc; + struct stat stat_buf; + + /* If the top level maildir is a sym link, don't delete it */ + + if (stat(s, &stat_buf) < 0 && + S_ISLNK(stat_buf.st_mode)) + return -1; + + trap_signals(); + rc=maildir_del(s); + if (release_signals()) _exit(0); + return (rc); +} + +int mdcreate(const char *mailbox) +{ + trap_signals(); + if (maildir_make(mailbox, 0700, 0700, 1) < 0) + { + if (release_signals()) _exit(0); + return (-1); + } + + if (release_signals()) _exit(0); + return (0); +} + +/****************************************************************************/ + +/* do_msgset parses a message set, and calls a processing func for each msg */ + +/****************************************************************************/ + +static int do_msgset(const char *msgset, + int (*msgfunc)(unsigned long, int, void *), + void *msgfunc_arg, int isuid) +{ +unsigned long i, j; +int rc; +unsigned long last=0; + + if (current_maildir_info.nmessages > 0) + { + last=current_maildir_info.nmessages; + if (isuid) + { + last=current_maildir_info.msgs[last-1].uid; + } + } + + while (isdigit((int)(unsigned char)*msgset) || *msgset == '*') + { + i=0; + if (*msgset == '*') + { + i=last; + ++msgset; + } + else while (isdigit((int)(unsigned char)*msgset)) + { + i=i*10 + (*msgset++-'0'); + } + if (*msgset != ':') + j=i; + else + { + j=0; + ++msgset; + if (*msgset == '*') + { + j=last; + ++msgset; + } + else while (isdigit((int)(unsigned char)*msgset)) + { + j=j*10 + (*msgset++-'0'); + } + } + if (j < i) + { +#if 0 + /* BUGS issue */ + writes("* NO Invalid message set: "); + writen(i); + writes(":"); + writen(j); + writes("\r\n"); +#endif + } + else if (isuid) + { + unsigned long k; + + for (k=0; k<current_maildir_info.nmessages; k++) + if (current_maildir_info.msgs[k].uid >= i) + break; + if (k >= current_maildir_info.nmessages || + current_maildir_info.msgs[k].uid > j) + { +#if 0 + /* BUGS issue */ + writes("* NO Invalid message: UID "); + writen(i); + if (j > i) + { + writes(":"); + writen(j); + } + writes("\r\n"); +#endif + } + else while (k < current_maildir_info.nmessages && + current_maildir_info.msgs[k].uid <= j) + { + if ((rc=(*msgfunc)(k+1, 1, msgfunc_arg)) != 0) + return (rc); + ++k; + } + } + else + { + do + { + if (i == 0 || + i > current_maildir_info.nmessages) + { + writes("* NO Invalid message sequence number: "); + writen(i); + writes("\r\n"); + break; + } + + if ((rc=(*msgfunc)(i, 0, msgfunc_arg)) != 0) + return (rc); + } while (i++ < j); + } + + if (*msgset++ != ',') break; + } + return (0); +} + +/** Show currently defined flags and keywords **/ + +static int write_keyword_name(struct libmail_keywordEntry *, void *); + +static void mailboxflags(int ro) +{ +#if SMAP + if (smapflag) + return; +#endif + + writes("* FLAGS ("); + + if (current_maildir_info.keywordList) + { + void (*writefunc)(const char *)=writes; + + libmail_kwEnumerate(current_maildir_info.keywordList, + &write_keyword_name, &writefunc); + } + + writes("\\Draft \\Answered \\Flagged" + " \\Deleted \\Seen \\Recent)\r\n"); + writes("* OK [PERMANENTFLAGS ("); + + + if (ro) + { + writes(")] No permanent flags permitted\r\n"); + } + else + { + if (current_maildir_info.keywordList) + { + void (*writefunc)(const char *)=writes; + + libmail_kwEnumerate(current_maildir_info + .keywordList, + &write_keyword_name, + &writefunc); + } + + if (keywords()) + writes("\\* "); + + writes("\\Draft \\Answered \\Flagged \\Deleted \\Seen)] Limited\r\n"); + } +} + +static int write_keyword_name(struct libmail_keywordEntry *kw, void *dummy) +{ + void (**writefunc)(const char *)=(void (**)(const char *))dummy; + + (**writefunc)(keywordName(kw)); + (**writefunc)(" "); + return 0; +} + +/****************************************************************************/ + +/* Show how many messages are in the mailbox */ + +/****************************************************************************/ + +static void mailboxmetrics() +{ +unsigned long i,j; + +#if SMAP + if (smapflag) + { + writes("* EXISTS "); + writen(current_maildir_info.nmessages); + writes("\n"); + return; + } +#endif + + + writes("* "); + writen(current_maildir_info.nmessages); + writes(" EXISTS\r\n"); + + writes("* "); + i=0; + + for (j=0; j<current_maildir_info.nmessages; j++) + if (current_maildir_info.msgs[j].recentflag) + ++i; + writen(i); + writes(" RECENT\r\n"); +} + +/****************************************************************************/ + +/* Do the NOOP stuff */ + +/****************************************************************************/ + +struct noopKeywordUpdateInfo { + struct libmail_kwHashtable *keywordList; + struct libmail_kwMessage *messagePtr; +}; + +static int noopAddKeyword(struct libmail_keywordEntry *ke, void *vp) +{ + struct noopKeywordUpdateInfo *kui= + (struct noopKeywordUpdateInfo *)vp; + + libmail_kwmSetName(kui->keywordList, kui->messagePtr, keywordName(ke)); + /* + ** ke originates from a different keyword namespace, so must use + ** its name. + */ + return 0; +} + +void doNoop(int real_noop) +{ + struct imapscaninfo new_maildir_info; + unsigned long i, j; + int needstats=0; + unsigned long expunged; +#if SMAP + unsigned long smap_expunge_count=0; + unsigned long smap_expunge_bias=0; + + unsigned long smap_last=0; + unsigned long smap_range=0; + + int takeSnapshot=1; +#endif + struct noopKeywordUpdateInfo kui; + + imapscan_init(&new_maildir_info); + + if (imapscan_maildir(&new_maildir_info, current_mailbox, 0, + current_mailbox_ro, NULL)) + return; + + j=0; + expunged=0; + for (i=0; i<current_maildir_info.nmessages; i++) + { + struct libmail_kwMessage *a, *b; + + while (j < new_maildir_info.nmessages && + new_maildir_info.msgs[j].uid < + current_maildir_info.msgs[i].uid) + { + /* How did this happen??? */ + + new_maildir_info.msgs[j].changedflags=1; + ++j; + needstats=1; +#if SMAP + takeSnapshot=0; +#endif + } + + if (j >= new_maildir_info.nmessages || + new_maildir_info.msgs[j].uid > + current_maildir_info.msgs[i].uid) + { +#if SMAP + if (smapflag) + { + takeSnapshot=0; + + if (smap_expunge_count > 100) + { + if (smap_range > 0) + { + writes("-"); + writen(smap_last + smap_range + - smap_expunge_bias + + 1); + } + writes("\n"); + smap_expunge_count=0; + } + + if (smap_expunge_count == 0) + { + smap_expunge_bias=expunged; + + writes("* EXPUNGE "); + writen(i+1-smap_expunge_bias); + smap_last=i; + smap_range=0; + } + else if (smap_last + smap_range + 1 == i) + { + ++smap_range; + } + else + { + if (smap_range > 0) + { + writes("-"); + writen(smap_last + smap_range + - smap_expunge_bias + + 1); + } + writes(" "); + writen(i+1-smap_expunge_bias); + smap_last=i; + smap_range=0; + } + ++smap_expunge_count; + } + else +#endif + { + writes("* "); + writen(i+1-expunged); + writes(" EXPUNGE\r\n"); + needstats=1; + } + + ++expunged; + continue; + } + + /* Must be the same */ + + a=current_maildir_info.msgs[i].keywordMsg; + b=new_maildir_info.msgs[j].keywordMsg; + + if (strcmp(current_maildir_info.msgs[i].filename, + new_maildir_info.msgs[j].filename) || + (a && !b) || (!a && b) || + (a && b && libmail_kwmCmp(a, b))) + { + new_maildir_info.msgs[j].changedflags=1; +#if SMAP + takeSnapshot=0; +#endif + } + if (current_maildir_info.msgs[i].recentflag) + new_maildir_info.msgs[j].recentflag=1; +#if SMAP + if (smapflag) + new_maildir_info.msgs[j].recentflag=0; +#endif + new_maildir_info.msgs[j].copiedflag= + current_maildir_info.msgs[i].copiedflag; + ++j; + } + +#if SMAP + if (smapflag && smap_expunge_count) + { + if (smap_range > 0) + { + writes("-"); + writen(smap_last + smap_range - smap_expunge_bias + 1); + } + writes("\n"); + } + +#endif + + while (j < new_maildir_info.nmessages) + { +#if SMAP + if (smapflag) + takeSnapshot=0; +#endif + ++j; + needstats=1; + } + + new_maildir_info.keywordList->keywordAddedRemoved=0; + + + /********************************************************** + ** + ** current_maildir_info: existing keywords + ** new_maildir_info: new keywords + ** + ** Process new/deleted keywords as follows: + */ + + /* + ** 1. Make sure that the old keyword list includes any new keywords. + */ + + kui.keywordList=current_maildir_info.keywordList; + kui.messagePtr=libmail_kwmCreate(); + + if (!kui.messagePtr) + write_error_exit(0); + + current_maildir_info.keywordList->keywordAddedRemoved=0; + libmail_kwEnumerate(new_maildir_info.keywordList, + noopAddKeyword, &kui); + + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + libmail_kwmDestroy(kui.messagePtr); + + + /* + ** 2. Temporarily add all existing keywords to the new keyword list. + */ + + kui.keywordList=new_maildir_info.keywordList; + kui.messagePtr=libmail_kwmCreate(); + + if (!kui.messagePtr) + write_error_exit(0); + libmail_kwEnumerate(current_maildir_info.keywordList, + noopAddKeyword, &kui); + + imapscan_copy(¤t_maildir_info, &new_maildir_info); + imapscan_free(&new_maildir_info); + +#if SMAP + if (takeSnapshot) + { + if (real_noop && smapflag) + snapshot_save(); + } + else + snapshot_needed(); +#endif + + if (needstats) + mailboxmetrics(); + + for (j=0; j < current_maildir_info.nmessages; j++) + if (current_maildir_info.msgs[j].changedflags) + { +#if SMAP + if (smapflag) + smap_fetchflags(j); + else +#endif + fetchflags(j); + } + + /* + ** After sending changed flags to the client, see if any keywords + ** have gone away. + */ + + current_maildir_info.keywordList->keywordAddedRemoved=0; + libmail_kwmDestroy(kui.messagePtr); + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); +} + +static char *alloc_filename(const char *mbox, const char *name) +{ +char *p=malloc(strlen(mbox)+strlen(name)+sizeof("/cur/")); + + if (!p) write_error_exit(0); + + strcat(strcat(strcpy(p, mbox), "/cur/"), name); + return (p); +} + +/****************************************************************************/ + +/* Do the ID stuff */ + +/****************************************************************************/ +static int doId() +{ + const char *ev = getenv("IMAP_ID_FIELDS"); + unsigned int flags=0; + struct imaptoken *curtoken; + + if (!ev) + return -1; + + flags = atoi(ev); + + /* The data sent by the client isn't used for anything, but make sure + * it is syntactically correct. */ + curtoken = nexttoken(); + switch (curtoken->tokentype) { + case IT_NIL: + break; + case IT_LPAREN: + { + unsigned int k = 0; + + curtoken = nexttoken(); + + fprintf(stderr, "INFO: ID, user=%s, ip=[%s]", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP")); + + while ((k < 30) && (curtoken->tokentype != IT_RPAREN)) { + k++; + if (curtoken->tokentype != IT_QUOTED_STRING) + { + fprintf(stderr, "\n"); + fflush(stderr); + return -1; + } + fprintf(stderr, ", %s=", curtoken->tokenbuf); + + curtoken = nexttoken(); + if ((curtoken->tokentype != IT_QUOTED_STRING) && + (curtoken->tokentype != IT_NIL)) + { + fprintf(stderr, "\n"); + fflush(stderr); + return -1; + } + fprintf(stderr, "%s", + curtoken->tokentype == IT_QUOTED_STRING + ? curtoken->tokenbuf:"(nil)"); + curtoken = nexttoken(); + } + fprintf(stderr, "\n"); + fflush(stderr); + + /* no strings sent */ + if (k == 0) + return -1; + + /* at most 30 pairs allowed */ + if ((k >= 30) && (curtoken->tokentype != IT_RPAREN)) + return -1; + + break; + } + default: + return -1; + } + + writes("* ID (\"name\" \"Courier-IMAP"); + + if (flags & 1) { + const char *sp = strchr(PROGRAMVERSION, ' ') + 1; + writes("\" \"version\" \""); + writemem(sp, strchr(sp, '/') - sp); + } + +#if HAVE_SYS_UTSNAME_H + if (flags & 6) { + struct utsname uts; + if (uname(&uts) == 0) + { + if (flags & 2) { + writes("\" \"os\" \""); + writeqs(uts.sysname); + } + if (flags & 4) { + writes("\" \"os-version\" \""); + writeqs(uts.release); + } + + } + } +#endif + + writes("\" \"vendor\" \"Double Precision, Inc.\")\r\n"); + + return 0; +} + +/****************************************************************************/ + +/* Do the IDLE stuff */ + +/****************************************************************************/ + +extern int doidle(time_t, int); + +int imapenhancedidle(void) +{ + struct maildirwatch *w; + struct maildirwatch_contents c; + int rc; + int started=0; + + if (!current_mailbox) + return (-1); + + if ((w=maildirwatch_alloc(current_mailbox)) == NULL) + { + perror(current_mailbox); + fprintf(stderr, "This may be a problem with FAM or Gamin\n"); + return (-1); + } + + rc=maildirwatch_start(w, &c); + + if (rc < 0) + { + perror("maildirwatch_start"); + maildirwatch_free(w); + return (-1); + } + + if (rc > 0) + { + maildirwatch_free(w); + return (-1); /* Fallback */ + } + +#if SMAP + if (smapflag) + { + writes("+OK Entering ENHANCED idle mode\n"); + } + else +#endif + writes("+ entering ENHANCED idle mode\r\n"); + writeflush(); + + for (;;) + { + if (!started) + { + int fd; + int rc; + + rc=maildirwatch_started(&c, &fd); + + if (rc > 0) + { + started=1; + doNoop(0); + writeflush(); + } + else + { + if (rc < 0) + perror("maildirwatch_started"); + if (doidle(60, fd)) + break; + } + continue; + } + + if (started < 0) /* Error fallback mode*/ + { + if (doidle(60, -1)) + break; + } + else + { + int changed; + int fd; + int timeout; + + if (maildirwatch_check(&c, &changed, &fd, &timeout) + == 0) + { + if (!changed) + { + if (doidle(timeout, fd)) + break; + continue; + } + + maildirwatch_end(&c); + doNoop(0); + + rc=maildirwatch_start(w, &c); + + if (rc < 0) + { + perror("maildirwatch_start"); + started= -1; + } + else + started=0; + } + else + { + started= -1; + } + } + + doNoop(0); + writeflush(); + } + + maildirwatch_end(&c); + maildirwatch_free(w); + return (0); +} + +void imapidle(void) +{ + const char * envp = getenv("IMAP_IDLE_TIMEOUT"); + const int idleTimeout = envp ? atoi(envp) : 60; + +#if SMAP + if (smapflag) + { + writes("+OK Entering idle mode...\n"); + } + else +#endif + writes("+ entering idle mode\r\n"); + if (current_mailbox) + doNoop(0); + writeflush(); + while (!doidle(idleTimeout, -1)) + { + if (current_mailbox) + doNoop(0); + writeflush(); + } +} + +/****************************************************************************/ + +/* Do the EXPUNGE stuff */ + +/****************************************************************************/ + +void do_expunge(unsigned long from, unsigned long to, int force); + +static int uid_expunge(unsigned long msgnum, int uidflag, void *void_arg) +{ + do_expunge(msgnum-1, msgnum, 0); + return 0; +} + +void expunge() +{ + do_expunge(0, current_maildir_info.nmessages, 0); +} + +void do_expunge(unsigned long expunge_start, + unsigned long expunge_end, + int force) +{ +unsigned long j; +struct imapflags flags; +char *old_name; +int move_to_trash=0; +struct stat stat_buf; +const char *cp=getenv("IMAP_LOG_DELETIONS"); +int log_deletions= cp && *cp != '0'; + + fetch_free_cache(); + + if (magictrash() && + !is_trash(strncmp(current_mailbox, "./", 2) == 0? + current_mailbox+2:current_mailbox)) + move_to_trash=1; + + for (j=expunge_start; j < expunge_end; j++) + { + int file_counted=0; + + get_message_flags(current_maildir_info.msgs+j, 0, &flags); + + if (!flags.deleted && !force) + continue; + + old_name=alloc_filename(current_mailbox, + current_maildir_info.msgs[j].filename); + + if (stat(old_name, &stat_buf) < 0) + { + free(old_name); + continue; + } + + if (maildirquota_countfolder(current_mailbox) && + maildirquota_countfile(old_name)) + file_counted=1; + + if (is_sharedsubdir(current_mailbox)) + { + maildir_unlinksharedmsg(old_name); + } + else if (move_to_trash && current_maildir_info + .msgs[j].copiedflag == 0) + { + char *new_name; + char *p; + int will_count=0; + + new_name=alloc_filename(dot_trash, + current_maildir_info.msgs[j].filename); + + if (maildirquota_countfolder(dot_trash)) + will_count=1; + + if (file_counted != will_count) + { + unsigned long filesize=0; + + if (maildir_parsequota(old_name, &filesize)) + { + if (stat(old_name, &stat_buf)) + stat_buf.st_size=0; + filesize=(unsigned long) + stat_buf.st_size; + } + + maildir_quota_deleted(".", + (long)filesize * + (will_count + -file_counted), + will_count + -file_counted); + } + + if ((p=strrchr(new_name, '/')) && + (p=strrchr(p, MDIRSEP[0])) && + strncmp(p, MDIRSEP "2,", 3) == 0) + { + char *q; + + /* Don't mark it as deleted in the Trash */ + + if ((q=strchr(p, 'T')) != NULL) + while ((*q=q[1]) != 0) + ++q; + + /* Don't mark it as a draft msg in the Trash */ + + if ((q=strchr(p, 'D')) != NULL) + while ((*q=q[1]) != 0) + ++q; + } + + if (log_deletions) + fprintf(stderr, "INFO: EXPUNGED, user=%s, ip=[%s], old_name=%s, new_name=%s: only new_name will be left\n", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP"), + old_name, new_name); + + if (rename(old_name, new_name)) + { + mdcreate(dot_trash); + rename(old_name, new_name); + } + + unlink(old_name); + /* triggers linux kernel bug if also moved to Trash by + sqwebmail */ + + free(new_name); + } + else + { + unlink(old_name); + + if (file_counted) + { + unsigned long filesize=0; + + if (maildir_parsequota(old_name, &filesize)) + { + if (stat(old_name, &stat_buf)) + stat_buf.st_size=0; + filesize=(unsigned long) + stat_buf.st_size; + } + + maildir_quota_deleted(".",-(long)filesize, -1); + } + } + + if (log_deletions) + fprintf(stderr, "INFO: EXPUNGED, user=%s, ip=[%s], old_name=%s\n", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP"), + old_name); + free(old_name); + } +} + + +static FILE *newsubscribefile(char **tmpname) +{ + char uniqbuf[80]; + static unsigned tmpuniqcnt=0; + FILE *fp; + struct maildir_tmpcreate_info createInfo; + + maildir_tmpcreate_init(&createInfo); + + sprintf(uniqbuf, "imapsubscribe%u", tmpuniqcnt++); + + createInfo.uniq=uniqbuf; + createInfo.hostname=getenv("HOSTNAME"); + createInfo.doordie=1; + + if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) + write_error_exit(0); + + *tmpname=createInfo.tmpname; + createInfo.tmpname=NULL; + maildir_tmpcreate_free(&createInfo); + + return (fp); +} + +static int sub_strcmp(const char *a, const char *b) +{ + if (strncasecmp(a, "inbox", 5) == 0 && + strncasecmp(b, "inbox", 5) == 0) + { + a += 5; + b += 5; + } + return (strcmp(a, b)); +} + +static void subscribe(const char *f) +{ + char *newf; + FILE *newfp=newsubscribefile(&newf); + FILE *oldfp; + + if ((oldfp=fopen(SUBSCRIBEFILE, "r")) != 0) + { + char buf[BUFSIZ]; + + while (fgets(buf, sizeof(buf), oldfp) != 0) + { + char *p=strchr(buf, '\n'); + + if (p) *p=0; + if (sub_strcmp(buf, f) == 0) + { + fclose(oldfp); + fclose(newfp); + unlink(newf); + free(newf); + return; /* Already subscribed */ + } + fprintf(newfp, "%s\n", buf); + } + fclose(oldfp); + } + fprintf(newfp, "%s\n", f); + if (fflush(newfp) || ferror(newfp)) + write_error_exit(0); + fclose(newfp); + rename(newf, SUBSCRIBEFILE); + free(newf); +} + +static void unsubscribe(const char *f) +{ + char *newf; + FILE *newfp=newsubscribefile(&newf); + FILE *oldfp; + + if ((oldfp=fopen(SUBSCRIBEFILE, "r")) != 0) + { + char buf[BUFSIZ]; + + while (fgets(buf, sizeof(buf), oldfp) != 0) + { + char *p=strchr(buf, '\n'); + + if (p) *p=0; + if (sub_strcmp(buf, f) == 0) + continue; + fprintf(newfp, "%s\n", buf); + } + fclose(oldfp); + } + if (fflush(newfp) || ferror(newfp)) + write_error_exit(0); + fclose(newfp); + rename(newf, SUBSCRIBEFILE); + free(newf); +} + +/* +** Count selected messages (if there's >1 copy to OUTBOX should fail). +*/ + +static int do_count(unsigned long n, int byuid, void *voidptr) +{ + const char *p=getenv("OUTBOX_MULTIPLE_SEND"); + + ++ *(int *)voidptr; + + if (p && atoi(p)) + *(int *)voidptr=1; /* Suppress the error, below */ + + return 0; +} + +static void dirsync(const char *folder) +{ +#if EXPLICITDIRSYNC + + char *p=malloc(strlen(folder)+sizeof("/new")); + int fd; + + if (!p) + write_error_exit(0); + + p=strcat(strcpy(p, folder), "/new"); + + fd=open(p, O_RDONLY); + + if (fd >= 0) + { + fsync(fd); + close(fd); + } + + p=strcat(strcpy(p, folder), "/cur"); + + fd=open(p, O_RDONLY); + + if (fd >= 0) + { + fsync(fd); + close(fd); + } + + free(p); +#endif +} + +/* +** Keyword updates for +FLAGS and -FLAGS +*/ + +static int addRemoveKeywords1(void *); + +static int addRemoveKeywords2(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s, + int *tryagain); + +struct addremove_info { + int (*callback_func)(void *, void *); + void *callback_func_arg; + struct storeinfo *storeinfo_s; + int *tryagain; +}; + + +int addRemoveKeywords(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s) +{ + int tryagain; + struct addremove_info ai; + + if (!keywords()) + return 0; + + if (current_mailbox_acl && + strchr(current_mailbox_acl, ACL_WRITE[0]) == NULL) + return 0; /* No permission */ + do + { + ai.callback_func=callback_func; + ai.callback_func_arg=callback_func_arg; + ai.storeinfo_s=storeinfo_s; + ai.tryagain= &tryagain; + + if (imapmaildirlock(¤t_maildir_info, + current_mailbox, + addRemoveKeywords1, + &ai)) + return -1; + } while (tryagain); + + return 0; +} + +static int addRemoveKeywords1(void *void_arg) +{ + struct addremove_info *ai=(struct addremove_info *)void_arg; + + return addRemoveKeywords2(ai->callback_func, + ai->callback_func_arg, + ai->storeinfo_s, + ai->tryagain); +} + +int doAddRemoveKeywords(unsigned long, int, void *); + +struct addRemoveKeywordInfo { + struct libmail_kwGeneric kwg; + struct storeinfo *storeinfo; +}; + +static int addRemoveKeywords2(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s, + int *tryagain) +{ + struct addRemoveKeywordInfo arki; + int rc; + + *tryagain=0; + + libmail_kwgInit(&arki.kwg); + + arki.storeinfo=storeinfo_s; + + rc=libmail_kwgReadMaildir(&arki.kwg, current_mailbox); + + if (rc == 0) + rc= (*callback_func)(callback_func_arg, &arki); + + if (rc < 0) + { + libmail_kwgDestroy(&arki.kwg); + return -1; + } + + if (rc > 0) /* Race */ + { + libmail_kwgDestroy(&arki.kwg); + *tryagain=1; + return 0; + } + + libmail_kwgDestroy(&arki.kwg); + return 0; +} + +int doAddRemoveKeywords(unsigned long n, int uid, void *vp) +{ + struct addRemoveKeywordInfo *arki= + (struct addRemoveKeywordInfo *)vp; + struct libmail_kwGenericEntry *ge= + libmail_kwgFindByName(&arki->kwg, + current_maildir_info.msgs[--n].filename); + char *tmpname=NULL, *newname=NULL; + struct stat stat_buf; + int rc; + + if (!ge || ge->keywords == NULL) + { + if (arki->storeinfo->plusminus == '+') + { + rc=maildir_kwSave(current_mailbox, + current_maildir_info.msgs[n]. + filename, + arki->storeinfo->keywords, + &tmpname, &newname, 1); + + if (rc < 0) + return -1; + } + } + else if (arki->storeinfo->plusminus == '+') + { + int flag=0; + struct libmail_kwMessageEntry *kme; + + for (kme=arki->storeinfo->keywords->firstEntry; + kme; kme=kme->next) + { + if ((rc=libmail_kwmSet(ge->keywords, + kme->libmail_keywordEntryPtr)) + < 0) + { + write_error_exit(0); + return 0; + } + + if (rc == 0) + flag=1; + } + + if (flag) + { + rc=maildir_kwSave(current_mailbox, + current_maildir_info.msgs[n]. + filename, + ge->keywords, + &tmpname, &newname, 1); + + if (rc < 0) + return -1; + } + } + else + { + int flag=0; + struct libmail_kwMessageEntry *kme; + + for (kme=arki->storeinfo->keywords->firstEntry; + kme; kme=kme->next) + { + struct libmail_keywordEntry *kwe; + + if ((kwe=libmail_kweFind(&arki->kwg.kwHashTable, + keywordName(kme-> + libmail_keywordEntryPtr), + 0)) != NULL && + + libmail_kwmClear(ge->keywords, kwe) == 0) + flag=1; + } + + if (flag) + { + rc=maildir_kwSave(current_mailbox, + current_maildir_info.msgs[n]. + filename, + ge->keywords, + &tmpname, &newname, 1); + + if (rc < 0) + return -1; + } + + + } + + if (tmpname) + { + current_maildir_info.msgs[n].changedflags=1; + + if (link(tmpname, newname) < 0) + { + unlink(tmpname); + free(tmpname); + free(newname); + + return (errno == EEXIST ? 1:-1); + } + + if (stat(tmpname, &stat_buf) == 0 && + stat_buf.st_nlink == 2) + { + unlink(tmpname); + free(tmpname); + free(newname); + return 0; + } + unlink(tmpname); + free(tmpname); + free(newname); + return 1; + } + + return 0; +} + +/* IMAP interface to add/remove keyword */ + +struct imap_addRemoveKeywordInfo { + char *msgset; + int uid; +}; + +static int markmessages(unsigned long n, int i, void *dummy) +{ + --n; + current_maildir_info.msgs[n].storeflag=1; + return 0; +} + +static int imap_addRemoveKeywords(void *myVoidArg, void *addRemoveVoidArg) +{ + struct imap_addRemoveKeywordInfo *i= + (struct imap_addRemoveKeywordInfo *)myVoidArg; + unsigned long j; + + for (j=0; j<current_maildir_info.nmessages; j++) + current_maildir_info.msgs[j].storeflag=0; + + do_msgset(i->msgset, markmessages, NULL, i->uid); + + for (j=0; j<current_maildir_info.nmessages; j++) + { + int rc; + + if (!current_maildir_info.msgs[j].storeflag) + continue; + + rc=doAddRemoveKeywords(j+1, i->uid, addRemoveVoidArg); + if (rc) + return rc; + } + return 0; +} + +/* +** After adding messages to a maildir, compute their new UIDs. +*/ + +static int uidplus_fill(const char *mailbox, + struct uidplus_info *uidplus_list, + unsigned long *uidv) +{ + struct imapscaninfo scan_info; + + imapscan_init(&scan_info); + + if (imapscan_maildir(&scan_info, mailbox, 0, 0, uidplus_list)) + return -1; + + *uidv=scan_info.uidv; + + imapscan_free(&scan_info); + return 0; +} + +static void uidplus_writemsgset(struct uidplus_info *uidplus_list, + int new_uids) +{ +#define UIDN(u) ( new_uids ? (u)->uid:(u)->old_uid ) + + unsigned long uid_start, uid_end; + const char *comma=""; + + writes(" "); + while (uidplus_list) + { + uid_start=UIDN(uidplus_list); + uid_end=uid_start; + + while (uidplus_list->next && + UIDN(uidplus_list->next) == uid_end + 1) + { + uidplus_list=uidplus_list->next; + ++uid_end; + } + + writes(comma); + writen(uid_start); + if (uid_end != uid_start) + { + writes(":"); + writen(uid_end); + } + comma=","; + uidplus_list=uidplus_list->next; + } + +#undef UIDN +} + +static void uidplus_free(struct uidplus_info *uidplus_list) +{ + struct uidplus_info *u; + + while ((u=uidplus_list) != NULL) + { + uidplus_list=u->next; + free(u->tmpfilename); + free(u->curfilename); + + if (u->tmpkeywords) + free(u->tmpkeywords); + if (u->newkeywords) + free(u->newkeywords); + free(u); + } +} + +/* Abort a partially-filled uidplus */ + +static void uidplus_abort(struct uidplus_info *uidplus_list) +{ + struct uidplus_info *u; + + while ((u=uidplus_list) != NULL) + { + uidplus_list=u->next; + unlink(u->tmpfilename); + unlink(u->curfilename); + + if (u->tmpkeywords) + { + unlink(u->tmpkeywords); + free(u->tmpkeywords); + } + + if (u->newkeywords) + { + unlink(u->newkeywords); + free(u->newkeywords); + } + + free(u->tmpfilename); + free(u->curfilename); + free(u); + } +} + +static void rename_callback(const char *old_path, const char *new_path) +{ +struct imapscaninfo minfo; + + char *p=malloc(strlen(new_path)+sizeof("/" IMAPDB)); + + if (!p) + write_error_exit(0); + + strcat(strcpy(p, new_path), "/" IMAPDB); + unlink(p); + free(p); + imapscan_init(&minfo); + imapscan_maildir(&minfo, new_path, 0,0, NULL); + imapscan_free(&minfo); +} + +static int broken_uidvs() +{ + const char *p=getenv("IMAP_BROKENUIDV"); + + return (p && atoi(p) != 0); +} + +static void logoutmsg() +{ + bye_msg("INFO: LOGOUT"); +} + +void bye() +{ + if (current_temp_fd >= 0) + close(current_temp_fd); + if (current_temp_fn) + unlink(current_temp_fn); + + if (current_mailbox) + free(current_mailbox); + imapscan_free(¤t_maildir_info); + maildirwatch_cleanup(); + fetch_free_cached(); + exit(0); +} + +char *get_myrightson(const char *mailbox); + +static void list_acl(const char *mailbox, + maildir_aclt_list *); +static int get_acllist(maildir_aclt_list *l, + const char *mailbox, + char **computed_mailbox_owner); + +static void list_myrights(const char *mailbox, + const char *myrights); +static void list_postaddress(const char *mailbox); + +static void accessdenied(const char *cmd, const char *folder, + const char *acl_required); + +static int list_callback(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg) +{ + const char *sep=""; + const char *cmd=(const char *)void_arg; + maildir_aclt_list l; + + char *myrights=NULL; + + /* + ** If we're going to list ACLs, grab the ACLs now, because + ** get_acllist2() may end up calling myrights(), which generates + ** its own output. + */ + + if (flags & (LIST_MYRIGHTS | LIST_ACL)) + { + myrights=get_myrightson(mailbox); + + if (flags & LIST_MYRIGHTS) + { + if (!maildir_acl_canlistrights(myrights)) + { + flags &= ~LIST_MYRIGHTS; +#if 0 + /* F.Y.I. only, should not be enabled + 'cause make check may fail on systems + which return directory entries in a + different order */ + + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\""); + accessdenied("LIST(MYRIGHTS)", + mailbox, + ACL_LOOKUP + ACL_READ + ACL_INSERT + ACL_CREATE + ACL_DELETEFOLDER + ACL_EXPUNGE + ACL_ADMINISTER); +#endif + } + } + + if (flags & LIST_ACL) + { + if (!strchr(myrights, ACL_ADMINISTER[0])) + { + flags &= ~LIST_ACL; +#if 0 + /* F.Y.I. only, should not be enabled + 'cause make check may fail on systems + which return directory entries in a + different order */ + + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\""); + accessdenied("LIST(ACL)", + mailbox, + ACL_ADMINISTER); +#endif + } + } + } + + if (flags & LIST_ACL) + { + if (get_acllist(&l, mailbox, NULL) < 0) + { + fprintf(stderr, + "ERR: Error reading ACLs for %s: %s\n", + mailbox, strerror(errno)); + flags &= ~LIST_ACL; + } + } + + writes("* "); + writes(cmd); + writes(" ("); + +#define DO_FLAG(flag, flagname) \ + if (flags & (flag)) { writes(sep); writes(flagname); \ + sep=" "; } + + DO_FLAG(MAILBOX_NOSELECT, "\\Noselect"); + DO_FLAG(MAILBOX_UNMARKED, "\\Unmarked"); + DO_FLAG(MAILBOX_MARKED, "\\Marked"); + DO_FLAG(MAILBOX_NOCHILDREN, "\\HasNoChildren"); + DO_FLAG(MAILBOX_NOINFERIORS, "\\Noinferiors"); + DO_FLAG(MAILBOX_CHILDREN, "\\HasChildren"); +#undef DO_FLAG + + writes(") "); + writes(hiersep); + writes(" \""); + writeqs(mailbox); + writes("\""); + + if (flags & (LIST_ACL|LIST_MYRIGHTS|LIST_POSTADDRESS)) + { + writes(" ("); + if (flags & LIST_ACL) + list_acl(mailbox, &l); + if (flags & LIST_MYRIGHTS) + list_myrights(mailbox, myrights); + if (flags & LIST_POSTADDRESS) + list_postaddress(mailbox); + writes(")"); + } + + writes("\r\n"); + if (myrights) + free(myrights); + if (flags & LIST_ACL) + maildir_aclt_list_destroy(&l); + + return 0; +} + +static void list_postaddress(const char *mailbox) +{ + writes("(POSTADDRESS NIL)"); +} + +/* +** Wrapper for maildir_aclt_read and maildir_aclt_write +*/ + +static int acl_read_folder(maildir_aclt_list *aclt_list, + const char *maildir, + const char *path) +{ + char *p, *q; + int rc; + + /* Handle legacy shared. namespace */ + + if (strcmp(path, SHARED) == 0) + { + maildir_aclt_list_init(aclt_list); + if (maildir_aclt_list_add(aclt_list, "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + return 0; + } + + if (strncmp(path, SHARED ".", sizeof(SHARED)) == 0) + { + maildir_aclt_list_init(aclt_list); + + if (strchr(path+sizeof(SHARED), '.') == 0) + { + if (maildir_aclt_list_add(aclt_list, + "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + } + + p=maildir_shareddir(maildir, path+sizeof(SHARED)); + if (!p) + { + if (maildir_aclt_list_add(aclt_list, + "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + return 0; + } + q=malloc(strlen(p)+sizeof("/new")); + if (!q) + { + free(p); + maildir_aclt_list_destroy(aclt_list); + return -1; + } + strcat(strcpy(q, p), "/new"); + free(p); + + if (maildir_aclt_list_add(aclt_list, + "anyone", + access(q, W_OK) == 0 ? + ACL_LOOKUP ACL_READ + ACL_SEEN ACL_WRITE ACL_INSERT + + ACL_DELETEFOLDER /* Wrong, but needed for ACL1 compatibility */ + + ACL_DELETEMSGS ACL_EXPUNGE: + ACL_LOOKUP ACL_READ ACL_SEEN + ACL_WRITE, NULL) < 0) + { + free(q); + maildir_aclt_list_destroy(aclt_list); + return -1; + } + free(q); + return 0; + } + + p=maildir_name2dir(".", path); + + if (!p) + return -1; + + rc=maildir_acl_read(aclt_list, maildir, p[0] == '.' && p[1] == '/' + ? p+2:p); + free(p); + return rc; +} + +static int acl_write_folder(maildir_aclt_list *aclt_list, + const char *maildir, + const char *path, + + const char *owner, + const char **err_failedrights) +{ + char *p; + int rc; + + if (strcmp(path, SHARED) == 0 || + strncmp(path, SHARED ".", sizeof(SHARED ".")-1) == 0) + { + /* Legacy */ + + return 1; + } + + p=maildir_name2dir(".", path); + + if (!p) + return -1; + + rc=maildir_acl_write(aclt_list, maildir, p[0] == '.' && p[1] == '/' + ? p+2:p, + owner, err_failedrights); + free(p); + return rc; +} + +static void writeacl(const char *); + +static void myrights() +{ + writes("* OK [MYRIGHTS \""); + writeacl(current_mailbox_acl); + writes("\"] ACL\r\n"); +} + +static int list_acl_cb(const char *ident, + const maildir_aclt *acl, + void *cb_arg); + +char *compute_myrights(maildir_aclt_list *l, + const char *l_owner); + +static int get_acllist(maildir_aclt_list *l, + const char *p, + char **computed_mailbox_owner) +{ + int rc; + char *v; + + struct maildir_info mi; + char *dummy_mailbox_owner=NULL; + + if (!computed_mailbox_owner) + computed_mailbox_owner= &dummy_mailbox_owner; + + if (maildir_info_imap_find(&mi, p, getenv("AUTHENTICATED")) < 0) + return -1; + + v=NULL; + + if (mi.homedir && mi.maildir) + { + rc=acl_read_folder(l, mi.homedir, mi.maildir); + v=maildir_name2dir(mi.homedir, mi.maildir); + } + else if (mi.mailbox_type == MAILBOXTYPE_OLDSHARED) + { + rc=acl_read_folder(l, ".", p); /* It handles it */ + } + else if (mi.mailbox_type == MAILBOXTYPE_NEWSHARED) + { + /* Intermediate #shared node. Punt */ + + maildir_aclt_list_init(l); + rc=0; + + if (maildir_aclt_list_add(l, "anyone", + ACL_LOOKUP, NULL) < 0) + { + maildir_aclt_list_destroy(l); + rc=-1; + } + } + else + { + /* Unknown mailbox type, no ACLs */ + + maildir_aclt_list_init(l); + rc=0; + } + + if (rc) + { + if (v) + free(v); + maildir_info_destroy(&mi); + return -1; + } + + *computed_mailbox_owner=my_strdup(mi.owner); + + maildir_info_destroy(&mi); + + /* Detect if ACLs on the currently-open folder have changed */ + + if (rc == 0 && current_mailbox && +#if SMAP + !smapflag && +#endif + + v && strcmp(v, current_mailbox) == 0) + { + char *new_acl=compute_myrights(l, *computed_mailbox_owner); + + if (new_acl && strcmp(new_acl, current_mailbox_acl)) + { + free(current_mailbox_acl); + current_mailbox_acl=new_acl; + new_acl=NULL; + myrights(); + } + + if (new_acl) + free(new_acl); + } + if (v) + free(v); + if (dummy_mailbox_owner) + free(dummy_mailbox_owner); + return rc; +} + +static void list_acl(const char *mailbox, + maildir_aclt_list *l) +{ + writes("(\"ACL\" ("); + maildir_aclt_list_enum(l, list_acl_cb, NULL); + writes("))"); +} + +static void writeacl(const char *aclstr) +{ + char *p, *q, *r; + const char *cp; + int n=0; + + for (cp=aclstr; *cp; cp++) + if (*cp == ACL_EXPUNGE[0]) + n |= 1; + else if (*cp == ACL_DELETEMSGS[0]) + n |= 2; + else if (*cp == ACL_DELETEFOLDER[0]) + n |= 4; + + if (n != 7) + { + writeqs(aclstr); + return; + } + + p=my_strdup(aclstr); + + *strchr(p, ACL_EXPUNGE[0])= ACL_DELETE_SPECIAL[0]; + + q=r=p; + while (*q) + { + if (*q != ACL_EXPUNGE[0] && *q != ACL_DELETEMSGS[0] && + *q != ACL_DELETEFOLDER[0]) + *r++= *q; + ++q; + } + *r=0; + writeqs(p); + free(p); +} + +static int list_acl_cb(const char *ident, + const maildir_aclt *acl, + void *cb_arg) +{ + writes("(\""); + writeqs(ident); + writes("\" \""); + writeacl(maildir_aclt_ascstr(acl)); + writes("\")"); + return 0; +} + +static void writeacl1(char *p) +{ + char *q, *r; + + if (strchr(p, ACL_EXPUNGE[0]) && + strchr(p, ACL_DELETEMSGS[0]) && + strchr(p, ACL_DELETEFOLDER[0])) + *strchr(p, ACL_EXPUNGE[0])=ACL_DELETE_SPECIAL[0]; + /* See no evil */ + + + for (q=r=p; *q; q++) + { + if (*q == ACL_EXPUNGE[0] || + *q == ACL_DELETEMSGS[0] || + *q == ACL_DELETEFOLDER[0]) + { + continue; + } + + *r++= *q; + } + *r=0; + writeqs(p); +} + +static int getacl_cb(const char *ident, + const maildir_aclt *acl, + void *cb_arg) +{ + char *p; + int isneg=0; + + if (*ident == '-') + { + isneg=1; + ++ident; + } + + if (strchr(ident, '=')) + { + if (strncmp(ident, "user=", 5)) + return 0; /* Hear no evil */ + ident += 5; + } + + writes(" \""); + if (isneg) + writes("-"); + writeqs(ident); + writes("\" \""); + + + p=my_strdup(maildir_aclt_ascstr(acl)); + + writeacl1(p); + + free(p); + writes("\""); + return 0; +} + +char *get_myrightson(const char *mailbox) +{ + maildir_aclt_list l; + char *mailbox_owner; + char *rights; + + if (get_acllist(&l, mailbox, &mailbox_owner) < 0) + return NULL; + + rights=compute_myrights(&l, mailbox_owner); + free(mailbox_owner); + maildir_aclt_list_destroy(&l); + return rights; +} + + +char *compute_myrights(maildir_aclt_list *l, const char *l_owner) +{ + maildir_aclt aa; + char *a; + + if (maildir_acl_computerights(&aa, l, getenv("AUTHENTICATED"), + l_owner) < 0) + return NULL; + + a=my_strdup(maildir_aclt_ascstr(&aa)); + maildir_aclt_destroy(&aa); + return a; +} + +void check_rights(const char *mailbox, char *rights_buf) +{ + char *r=get_myrightson(mailbox); + char *p, *q; + + if (!r) + { + fprintf(stderr, "ERR: Error reading ACLs for %s: %s\n", + mailbox, strerror(errno)); + *rights_buf=0; + return; + } + + for (p=q=rights_buf; *p; p++) + { + if (strchr(r, *p) == NULL) + continue; + + *q++ = *p; + } + *q=0; + free(r); +} + +static void list_myrights(const char *mailbox, + const char *r) +{ + + writes("(\"MYRIGHTS\" "); + + if (r == NULL) + { + fprintf(stderr, "ERR: Error reading ACLs for %s: %s\n", + mailbox, strerror(errno)); + writes(" NIL"); + } + else + { + writes("\""); + writeacl(r); + writes("\""); + } + writes(")"); +} + + +struct temp_acl_mailbox_list { + struct temp_acl_mailbox_list *next; + char *mailbox; + char *hier; + int flags; +}; + +/* +** Callback function from aclmailbox_scan that saves the listed mailboxes into +** a temporary link list. +*/ + +static int aclmailbox_scan(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg) +{ + struct temp_acl_mailbox_list **p + =(struct temp_acl_mailbox_list **)void_arg, + *q=malloc(sizeof(struct temp_acl_mailbox_list)); + + if (!q || !(q->mailbox=malloc(strlen(mailbox)+1))) + { + if (q) free(q); + return -1; + } + if (!(q->hier=strdup(hiersep))) + { + free(q->mailbox); + free(q); + return -1; + } + strcpy(q->mailbox, mailbox); + q->next= *p; + q->flags=flags; + *p=q; + return 0; +} + +static void free_temp_acl_mailbox(struct temp_acl_mailbox_list *p) +{ + free(p->mailbox); + free(p->hier); +} + +static int cmp_mb(const void *a, const void *b) +{ + return (strcmp ( ((struct temp_acl_mailbox_list *)a)->mailbox, + ((struct temp_acl_mailbox_list *)b)->mailbox)); +} + +/* +** Combine mailbox patterns; remove duplicate mailboxes. +** +** mailbox_merge takes the temp_acl_mailbox_list generated from a pattern, +** combines it with the current mailbox list (kept as an array), sorts the +** end result, then removes dupes. +*/ + +static int aclmailbox_merge(struct temp_acl_mailbox_list *l, + struct temp_acl_mailbox_list **mailbox_list) +{ + size_t n, o; + struct temp_acl_mailbox_list *p; + struct temp_acl_mailbox_list *newa; + + /* Count # of mailboxes so far */ + + for (n=0; *mailbox_list && (*mailbox_list)[n].mailbox; n++) + ; + + /* Count # of new mailboxes */ + + for (p=l, o=0; p; p=p->next) + ++o; + + if (n + o == 0) + return 0; /* The list is empty */ + + /* Expand the array */ + + if ((newa= *mailbox_list == NULL ? malloc( (n+o+1)*sizeof(*l)): + realloc(*mailbox_list, (n+o+1)*sizeof(*l))) == NULL) + return -1; + + *mailbox_list=newa; + while (l) + { + newa[n]= *l; + + if ((newa[n].mailbox=strdup(l->mailbox)) == NULL) + return -1; + + if ((newa[n].hier=strdup(l->hier)) == NULL) + { + free(newa[n].mailbox); + newa[n].mailbox=NULL; + return -1; + } + + ++n; + memset(&newa[n], 0, sizeof(newa[n])); + + l=l->next; + } + qsort(newa, n, sizeof(*l), cmp_mb); + + /* Remove dupes */ + + for (n=o=0; newa[n].mailbox; n++) + { + if (newa[n+1].mailbox && + strcmp(newa[n].mailbox, newa[n+1].mailbox) == 0) + { + free_temp_acl_mailbox(&newa[n]); + continue; + } + newa[o]=newa[n]; + ++o; + } + memset(&newa[o], 0, sizeof(newa[o])); + + return 0; +} + +static int aclstore(const char *, struct temp_acl_mailbox_list *); +static int aclset(const char *, struct temp_acl_mailbox_list *); +static int acldelete(const char *, struct temp_acl_mailbox_list *); + +static void free_mailboxlist(struct temp_acl_mailbox_list *mailboxlist) +{ + size_t i; + for (i=0; mailboxlist && mailboxlist[i].mailbox; i++) + { + free(mailboxlist[i].hier); + free(mailboxlist[i].mailbox); + } + if (mailboxlist) + free(mailboxlist); +} + +static void free_tempmailboxlist(struct temp_acl_mailbox_list *mailboxlist) +{ + struct temp_acl_mailbox_list *p; + + while ((p=mailboxlist) != NULL) + { + mailboxlist=p->next; + free_temp_acl_mailbox(p); + free(p); + } +} + +static int aclcmd(const char *tag) +{ + char aclcmd[11]; + struct temp_acl_mailbox_list *mailboxlist; + struct temp_acl_mailbox_list *mblist; + struct imaptoken *curtoken; + size_t i; + int rc; + int (*aclfunc)(const char *, struct temp_acl_mailbox_list *); + + /* Expect ACL followed only by: STORE/DELETE/SET */ + + if ((curtoken=nexttoken())->tokentype != IT_ATOM || + strlen(curtoken->tokenbuf) > sizeof(aclcmd)-1) + { + errno=EINVAL; + return -1; + } + + strcpy(aclcmd, curtoken->tokenbuf); + + mailboxlist=NULL; + + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_LPAREN: + while ((curtoken=nexttoken_nouc())->tokentype != IT_RPAREN) + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + { + errno=EINVAL; + return -1; + } + + mblist=NULL; + + if (mailbox_scan("", curtoken->tokenbuf, 0, + aclmailbox_scan, &mblist) || + aclmailbox_merge(mblist, &mailboxlist)) + { + free_tempmailboxlist(mblist); + free_mailboxlist(mailboxlist); + return -1; + + } + + free_tempmailboxlist(mblist); + } + break; + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + + mblist=NULL; + if (mailbox_scan("", curtoken->tokenbuf, LIST_CHECK1FOLDER, + aclmailbox_scan, &mblist) || + aclmailbox_merge(mblist, &mailboxlist)) + + { + free_tempmailboxlist(mblist); + free_mailboxlist(mailboxlist); + return -1; + } + free_tempmailboxlist(mblist); + break; + } + + rc=0; + + aclfunc=strcmp(aclcmd, "STORE") == 0 ? aclstore: + strcmp(aclcmd, "DELETE") == 0 ? acldelete: + strcmp(aclcmd, "SET") == 0 ? aclset:NULL; + + rc= aclfunc ? (*aclfunc)(tag, mailboxlist): -1; + + if (rc == 0) + { + for (i=0; mailboxlist && mailboxlist[i].mailbox; i++) + { + if (mailboxlist[i].mailbox[0]) + list_callback(mailboxlist[i].hier, + mailboxlist[i].mailbox, + LIST_ACL | mailboxlist[i].flags, + "LIST"); + } + } + free_mailboxlist(mailboxlist); + + if (rc == 0) + { + writes(tag); + writes(" OK ACL "); + writes(aclcmd); + writes(" completed.\r\n"); + } + else + { + errno=EINVAL; + } + return rc; +} + +static int fix_acl_delete(int (*func)(maildir_aclt *, const char *, + const maildir_aclt *), + maildir_aclt *newacl, + const char *aclstr) +{ + char *p, *q; + int rc; + + if (strchr(aclstr, ACL_DELETE_SPECIAL[0]) == NULL) + return (*func)(newacl, aclstr, NULL); + + + p=malloc(strlen(aclstr)+sizeof(ACL_DELETEFOLDER + ACL_DELETEMSGS + ACL_EXPUNGE)); + if (!p) + return -1; + + q=p; + while (*aclstr) + { + if (*aclstr != ACL_DELETE_SPECIAL[0]) + *q++ = *aclstr; + ++aclstr; + } + + strcpy(q, ACL_DELETEFOLDER ACL_DELETEMSGS ACL_EXPUNGE); + + rc=(*func)(newacl, p, NULL); + free(p); + return rc; +} + +static int fix_acl_delete2(maildir_aclt_list *aclt_list, + const char *identifier, + const char *aclstr) +{ + char *p, *q; + int rc; + + if (strchr(aclstr, ACL_DELETE_SPECIAL[0]) == NULL) + return maildir_aclt_list_add(aclt_list, identifier, aclstr, + NULL); + + + p=malloc(strlen(aclstr)+sizeof(ACL_DELETEFOLDER + ACL_DELETEMSGS + ACL_EXPUNGE)); + if (!p) + return -1; + + q=p; + while (*aclstr) + { + if (*aclstr != ACL_DELETE_SPECIAL[0]) + *q++ = *aclstr; + ++aclstr; + } + + strcpy(q, ACL_DELETEFOLDER ACL_DELETEMSGS ACL_EXPUNGE); + + rc=maildir_aclt_list_add(aclt_list, identifier, p, NULL); + free(p); + return rc; +} + +void aclminimum(const char *identifier) +{ + if (strcmp(identifier, "administrators") == 0 || + strcmp(identifier, "group=administrators") == 0) + { + writes(ACL_ALL); + return; + } + + if (strcmp(identifier, "-administrators") == 0 || + strcmp(identifier, "-group=administrators") == 0) + { + writes("\"\""); + return; + } + + writes(*identifier == '-' ? "\"\"":ACL_ADMINISTER ACL_LOOKUP); + writes(" " ACL_CREATE + " " ACL_EXPUNGE + " " ACL_INSERT + " " ACL_POST + " " ACL_READ + " " ACL_SEEN + " " ACL_DELETEMSGS + " " ACL_WRITE + " " ACL_DELETEFOLDER); +} + +static void aclfailed(const char *mailbox, const char *identifier) +{ + if (!identifier) + { + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\" "); + writes(strerror(errno)); + writes("\r\n"); + return; + } + + writes("* RIGHTS-INFO \""); + writeqs(mailbox); + writes("\" \""); + writeqs(identifier); + writes("\" "); + + aclminimum(identifier); + writes("\r\n"); +} + +static int acl_settable_folder(char *mailbox, + struct maildir_info *mi) +{ + if (maildir_info_imap_find(mi, mailbox, getenv("AUTHENTICATED")) < 0) + { + aclfailed(mailbox, NULL); + *mailbox=0; + return (-1); + } + + if (mi->homedir == NULL || mi->maildir == NULL) + { + writes("* ACLFAILED \""); + writeqs(mailbox); + writes("\" ACLs may not be modified for special mailbox\r\n"); + maildir_info_destroy(mi); + *mailbox=0; + return -1; + } + return 0; +} + +int acl_lock(const char *homedir, + int (*func)(void *), + void *void_arg) +{ + struct imapscaninfo ii; + int rc; + + imapscan_init(&ii); + rc=imapmaildirlock(&ii, homedir, func, void_arg); + imapscan_free(&ii); + return rc; +} + +static int do_acl_mod_0(void *); + +struct do_acl_info { + maildir_aclt_list *aclt_list; + struct maildir_info *mi; + const char *identifier; + const char *newrights; + const char **acl_error; +}; + + +static int do_acl_mod(maildir_aclt_list *aclt_list, + struct maildir_info *mi, + const char *identifier, + const char *newrights, + const char **acl_error) +{ + struct do_acl_info dai; + + *acl_error=NULL; + + dai.aclt_list=aclt_list; + dai.mi=mi; + dai.identifier=identifier; + dai.newrights=newrights; + dai.acl_error=acl_error; + return acl_lock(mi->homedir, do_acl_mod_0, &dai); +} + +static int do_acl_mod_0(void *void_arg) +{ + struct do_acl_info *dai= + (struct do_acl_info *)void_arg; + maildir_aclt_list *aclt_list=dai->aclt_list; + struct maildir_info *mi=dai->mi; + const char *identifier=dai->identifier; + const char *newrights=dai->newrights; + const char **acl_error=dai->acl_error; + + if (newrights[0] == '+') + { + maildir_aclt newacl; + const maildir_aclt *oldacl; + + if (fix_acl_delete(&maildir_aclt_init, + &newacl, newrights+1) < 0 + || ((oldacl=maildir_aclt_list_find(aclt_list, identifier)) + != NULL && maildir_aclt_add(&newacl, NULL, oldacl) < 0) + || maildir_aclt_list_add(aclt_list, identifier, NULL, + &newacl) < 0 || + acl_write_folder(aclt_list, mi->homedir, + mi->maildir, + mi->owner, acl_error) < 0) + + { + maildir_aclt_destroy(&newacl); + return -1; + } + maildir_aclt_destroy(&newacl); + } + else if (newrights[0] == '-') + { + maildir_aclt newacl; + const maildir_aclt *oldacl; + + oldacl=maildir_aclt_list_find(aclt_list, identifier); + + if (maildir_aclt_init(&newacl, oldacl == NULL ? "":NULL, + oldacl) < 0 + || fix_acl_delete(&maildir_aclt_del, + &newacl, newrights+1) < 0 + || (strlen(maildir_aclt_ascstr(&newacl)) == 0 ? + maildir_aclt_list_del(aclt_list, identifier): + maildir_aclt_list_add(aclt_list, identifier, + NULL, &newacl)) < 0 || + acl_write_folder(aclt_list, mi->homedir, + mi->maildir, + mi->owner, + acl_error) < 0) + { + maildir_aclt_destroy(&newacl); + return -1; + } + maildir_aclt_destroy(&newacl); + } + else + { + acl_error=NULL; + + if ((newrights[0] == 0 ? + maildir_aclt_list_del(aclt_list, identifier): + fix_acl_delete2(aclt_list, identifier, newrights)) < 0 + || acl_write_folder(aclt_list, mi->homedir, + mi->maildir, mi->owner, + acl_error) < 0) + { + return -1; + } + } + + return 0; +} + +static int aclstore(const char *tag, struct temp_acl_mailbox_list *mailboxes) +{ + struct imaptoken *curtoken; + char *identifier; + size_t i; + + if ((curtoken=nexttoken_nouc())->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return -1; + + if ((identifier=strdup(curtoken->tokenbuf)) == NULL) + write_error_exit(0); + + if ((curtoken=nexttoken_nouc())->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + { + free(identifier); + return -1; + } + + for (i=0; mailboxes && mailboxes[i].mailbox; i++) + { + maildir_aclt_list aclt_list; + const char *acl_error; + struct maildir_info mi; + + if (acl_settable_folder(mailboxes[i].mailbox, &mi)) + continue; + + { + CHECK_RIGHTSM(mailboxes[i].mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes("* ACLFAILED \""); + writeqs(mailboxes[i].mailbox); + writes("\""); + accessdenied("ACL STORE", + mailboxes[i].mailbox, + ACL_ADMINISTER); + maildir_info_destroy(&mi); + mailboxes[i].mailbox[0]=0; + continue; + } + } + + if (acl_read_folder(&aclt_list, mi.homedir, mi.maildir)) + { + aclfailed(mailboxes[i].mailbox, NULL); + maildir_info_destroy(&mi); + continue; + } + + if (do_acl_mod(&aclt_list, &mi, identifier, curtoken->tokenbuf, + &acl_error) < 0) + { + aclfailed(mailboxes[i].mailbox, acl_error); + + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&mi); + continue; + } + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&mi); + } + + free(identifier); + return 0; +} + +struct aclset_info { + struct maildir_info *mi; + maildir_aclt_list *newlist; + const char **acl_error; +}; + +static int do_aclset(void *); + +static int aclset(const char *tag, struct temp_acl_mailbox_list *mailboxes) +{ + struct imaptoken *curtoken; + char *identifier; + maildir_aclt_list newlist; + size_t i; + + maildir_aclt_list_init(&newlist); + + while ((curtoken=nexttoken_nouc())->tokentype != IT_EOL) + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return -1; + if ((identifier=strdup(curtoken->tokenbuf)) == NULL) + write_error_exit(0); + + if ((curtoken=nexttoken_nouc())->tokentype + != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + { + free(identifier); + maildir_aclt_list_destroy(&newlist); + return -1; + } + + if (fix_acl_delete2(&newlist, identifier, + curtoken->tokenbuf) < 0) + { + maildir_aclt_list_destroy(&newlist); + writes(tag); + writes(" NO ACL SET <"); + writes(identifier); + writes(", "); + writes(curtoken->tokenbuf); + writes("> failed.\r\n"); + free(identifier); + return 0; + } + free(identifier); + } + + for (i=0; mailboxes && mailboxes[i].mailbox; i++) + { + const char *acl_error; + struct maildir_info mi; + struct aclset_info ai; + + if (acl_settable_folder(mailboxes[i].mailbox, &mi)) + continue; + + { + CHECK_RIGHTSM(mailboxes[i].mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + maildir_info_destroy(&mi); + writes("* ACLFAILED \""); + writeqs(mailboxes[i].mailbox); + writes("\""); + accessdenied("ACL SET", mailboxes[i].mailbox, + ACL_ADMINISTER); + mailboxes[i].mailbox[0]=0; + continue; + } + } + + acl_error=NULL; + ai.mi=&mi; + ai.acl_error= &acl_error; + ai.newlist= &newlist; + + if (acl_lock(mi.homedir, do_aclset, &ai)) + { + aclfailed(mailboxes[i].mailbox, acl_error); + maildir_info_destroy(&mi); + mailboxes[i].mailbox[0]=0; + continue; + } + maildir_info_destroy(&mi); + } + maildir_aclt_list_destroy(&newlist); + return 0; +} + +static int do_aclset(void *void_arg) +{ + struct aclset_info *ai=(struct aclset_info *)void_arg; + + return acl_write_folder(ai->newlist, ai->mi->homedir, + ai->mi->maildir, + ai->mi->owner, ai->acl_error); +} + +struct acldelete_info { + const char *mailbox; + const char *identifier; + struct maildir_info *mi; +}; + +static int do_acldelete(void *); + +static int acldelete(const char *tag, struct temp_acl_mailbox_list *mailboxes) +{ + struct imaptoken *curtoken; + const char *identifier; + size_t i; + + if ((curtoken=nexttoken_nouc())->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return -1; + + identifier=curtoken->tokenbuf; + + for (i=0; mailboxes && mailboxes[i].mailbox; i++) + { + struct maildir_info mi; + struct acldelete_info ai; + + if (acl_settable_folder(mailboxes[i].mailbox, &mi)) + continue; + + { + CHECK_RIGHTSM(mailboxes[i].mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes("* ACLFAILED \""); + writeqs(mailboxes[i].mailbox); + writes("\""); + accessdenied("ACL DELETE", + mailboxes[i].mailbox, + ACL_ADMINISTER); + maildir_info_destroy(&mi); + mailboxes[i].mailbox[0]=0; + continue; + } + } + + ai.mailbox=mailboxes[i].mailbox; + ai.mi= &mi; + ai.identifier=identifier; + if (acl_lock(mi.homedir, do_acldelete, &ai)) + mailboxes[i].mailbox[0]=0; + maildir_info_destroy(&mi); + } + return 0; +} + +static int do_acldelete(void *void_arg) +{ + struct acldelete_info *ai= + (struct acldelete_info *)void_arg; + const char *mailbox=ai->mailbox; + struct maildir_info *mi=ai->mi; + + maildir_aclt_list aclt_list; + const char *acl_error; + + if (acl_read_folder(&aclt_list, mi->homedir, mi->maildir) < 0) + { + writes("* NO Error reading ACLs for "); + writes(mailbox); + writes(": "); + writes(strerror(errno)); + writes("\r\n"); + return -1; + } + + acl_error=NULL; + + if (maildir_aclt_list_del(&aclt_list, ai->identifier) < 0 || + acl_write_folder(&aclt_list, mi->homedir, mi->maildir, + mi->owner, &acl_error) < 0) + { + aclfailed(mailbox, acl_error); + maildir_aclt_list_destroy(&aclt_list); + return -1; + } + maildir_aclt_list_destroy(&aclt_list); + return 0; +} + + +static void accessdenied(const char *cmd, const char *folder, + const char *acl_required) +{ + writes(" NO Access denied for "); + writes(cmd); + writes(" on "); + writes(folder); + writes(" (ACL \""); + writes(acl_required); + writes("\" required)\r\n"); +} + +/* Even if the folder does not exist, if there are subfolders it exists +** virtually. +*/ + +static int folder_exists_cb(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg) +{ + *(int *)void_arg=1; + return 0; +} + +static int folder_exists(const char *folder) +{ + int flag=0; + + if (mailbox_scan("", folder, LIST_CHECK1FOLDER, + folder_exists_cb, &flag)) + return 0; + return flag; +} + +int do_folder_delete(char *mailbox_name) +{ + maildir_aclt_list l; + const char *acl_error; + struct maildir_info mi; + + if (maildir_info_imap_find(&mi, mailbox_name, getenv("AUTHENTICATED")) + < 0) + return -1; + + if (mi.homedir == NULL || mi.maildir == NULL) + { + maildir_info_destroy(&mi); + return -1; + } + + if (acl_read_folder(&l, mi.homedir, mi.maildir) < 0) + return -1; + + if (strcmp(mi.maildir, INBOX)) + { + char *p=maildir_name2dir(mi.homedir, mi.maildir); + + if (p && is_reserved(p) == 0 && mddelete(p) == 0) + { + if (folder_exists(mailbox_name)) + { + acl_write_folder(&l, mi.homedir, + mi.maildir, NULL, + &acl_error); + } + maildir_aclt_list_destroy(&l); + maildir_quota_recalculate(mi.homedir); + free(p); + maildir_info_destroy(&mi); + return 0; + } + + if (p) + free(p); + } + maildir_aclt_list_destroy(&l); + return -1; +} + +int acl_flags_adjust(const char *access_rights, + struct imapflags *flags) +{ + if (strchr(access_rights, ACL_DELETEMSGS[0]) == NULL) + flags->deleted=0; + if (strchr(access_rights, ACL_SEEN[0]) == NULL) + flags->seen=0; + if (strchr(access_rights, ACL_WRITE[0]) == NULL) + { + flags->answered=flags->flagged=flags->drafts=0; + return 1; + } + return 0; +} + +static int append(const char *tag, const char *mailbox, const char *path) +{ + + struct imapflags flags; + struct libmail_kwMessage *keywords; + time_t timestamp=0; + unsigned long new_uidv, new_uid; + char access_rights[8]; + struct imaptoken *curtoken; + + if (access(path, 0)) + { + writes(tag); + writes(" NO [TRYCREATE] Must create mailbox before append\r\n"); + return (0); + } + + { + CHECK_RIGHTSM(mailbox, + append_rights, + ACL_INSERT ACL_DELETEMSGS + ACL_SEEN ACL_WRITE); + + if (strchr(append_rights, ACL_INSERT[0]) == NULL) + { + writes(tag); + accessdenied("APPEND", + mailbox, + ACL_INSERT); + return 0; + } + + strcpy(access_rights, append_rights); + } + + if (current_mailbox && + strcmp(path, current_mailbox) == 0 && current_mailbox_ro) + { + writes(tag); + writes(" NO Current box is selected READ-ONLY.\r\n"); + return (0); + } + + curtoken=nexttoken_noparseliteral(); + memset(&flags, 0, sizeof(flags)); + if ((keywords=libmail_kwmCreate()) == NULL) + write_error_exit(0); + + if (curtoken->tokentype == IT_LPAREN) + { + if (get_flagsAndKeywords(&flags, &keywords)) + { + libmail_kwmDestroy(keywords); + return (-1); + } + curtoken=nexttoken_noparseliteral(); + } + else if (curtoken->tokentype == IT_ATOM) + { + if (get_flagname(curtoken->tokenbuf, &flags)) + { + if (!valid_keyword(curtoken->tokenbuf)) + { + libmail_kwmDestroy(keywords); + return -1; + } + + libmail_kwmSetName(current_maildir_info.keywordList, + keywords, + curtoken->tokenbuf); + } + curtoken=nexttoken_noparseliteral(); + } + else if (curtoken->tokentype == IT_NIL) + curtoken=nexttoken_noparseliteral(); + + if (curtoken->tokentype == IT_QUOTED_STRING) + { + timestamp=decode_date_time(curtoken->tokenbuf); + if (timestamp == 0) + { + libmail_kwmDestroy(keywords); + return (-1); + } + curtoken=nexttoken_noparseliteral(); + } + else if (curtoken->tokentype == IT_NIL) + curtoken=nexttoken_noparseliteral(); + + if (curtoken->tokentype != IT_LITERAL_STRING_START) + { + libmail_kwmDestroy(keywords); + return (-1); + } + + acl_flags_adjust(access_rights, &flags); + + if (store_mailbox(tag, path, &flags, + acl_flags_adjust(access_rights, &flags) + ? NULL:keywords, + timestamp, + curtoken->tokennum, &new_uidv, &new_uid)) + { + libmail_kwmDestroy(keywords); + unread('\n'); + return (0); + } + libmail_kwmDestroy(keywords); + + if (nexttoken()->tokentype != IT_EOL) + { + return (-1); + } + + dirsync(path); + writes(tag); + writes(" OK [APPENDUID "); + writen(new_uidv); + writes(" "); + writen(new_uid); + writes("] APPEND Ok.\r\n"); + return (0); +} + + +/* Check for 'c' rights on the parent directory. */ + +static int check_parent_create(const char *tag, + const char *cmd, char *folder) +{ + char *parentPtr; + + parentPtr=strrchr(folder, HIERCH); + + if (parentPtr) + { + *parentPtr=0; + + { + CHECK_RIGHTSM(folder, + create_rights, + ACL_CREATE); + + if (create_rights[0]) + { + if (parentPtr) + *parentPtr=HIERCH; + return 0; + } + } + } + + writes(tag); + accessdenied(cmd, folder, ACL_CREATE); + if (parentPtr) + *parentPtr=HIERCH; + return -1; +} + +/* Convert ACL1 identifiers to ACL2 */ + +static char *acl2_identifier(const char *tag, + const char *identifier) +{ + const char *ident_orig=identifier; + + char *p; + int isneg=0; + + if (*identifier == '-') + { + isneg=1; + ++identifier; + } + + if (strcmp(identifier, "anyone") == 0 || + strcmp(identifier, "anonymous") == 0 || + strcmp(identifier, "authuser") == 0 || + strcmp(identifier, "owner") == 0 || + strcmp(identifier, "administrators") == 0) + return my_strdup(ident_orig); + + if (strchr(identifier, '=')) + { + writes(tag); + writes(" NO Invalid ACL identifier.\r\n"); + return NULL; + } + + p=malloc(sizeof("-user=")+strlen(identifier)); + + if (!p) + write_error_exit(0); + return strcat(strcat(strcpy(p, isneg ? "-":""), "user="), identifier); +} + + +int folder_rename(struct maildir_info *mi1, + struct maildir_info *mi2, + const char **errmsg) +{ + char *old_mailbox, *new_mailbox; + + if (mi1->homedir == NULL || mi1->maildir == NULL) + { + *errmsg="Invalid mailbox name"; + return -1; + } + + if (mi2->homedir == NULL || mi2->maildir == NULL) + { + *errmsg="Invalid new mailbox name"; + return -1; + } + + if (current_mailbox) + { + char *mailbox=maildir_name2dir(mi1->homedir, + mi1->maildir); + size_t l; + + if (!mailbox) + { + *errmsg="Invalid mailbox name"; + return -1; + } + + l=strlen(mailbox); + + if (strncmp(mailbox, current_mailbox, l) == 0 && + (current_mailbox[l] == 0 || + current_mailbox[l] == HIERCH)) + { + free(mailbox); + *errmsg="Can't RENAME the currently-open folder"; + return -1; + } + free(mailbox); + } + + if (strcmp(mi1->homedir, mi2->homedir)) + { + *errmsg="Cannot move a folder to a different account."; + return -1; + } + + if (strcmp(mi1->maildir, INBOX) == 0 || + strcmp(mi2->maildir, INBOX) == 0) + { + *errmsg="INBOX rename not implemented."; + return -1; + } + + if (is_reserved_name(mi1->maildir) || + is_reserved_name(mi2->maildir)) + { + *errmsg="Reserved folder name - cannot rename."; + return -1; + } + + /* Depend on maildir_name2dir returning ./.folder, see + ** maildir_rename() call. */ + + if ((old_mailbox=maildir_name2dir(".", mi1->maildir)) == NULL || + strncmp(old_mailbox, "./", 2)) + { + if (old_mailbox) + free(old_mailbox); + *errmsg="Internal error in RENAME: maildir_name2dir failed" + " for the old folder rename."; + return -1; + } + + if ((new_mailbox=maildir_name2dir(".", mi2->maildir)) == NULL || + strncmp(new_mailbox, "./", 2)) + { + free(old_mailbox); + if (new_mailbox) + free(new_mailbox); + *errmsg="Internal error in RENAME: maildir_name2dir failed" + " for the new folder rename."; + return -1; + } + + fetch_free_cache(); + + if (maildir_rename(mi1->homedir, + old_mailbox+2, new_mailbox+2, + MAILDIR_RENAME_FOLDER | + MAILDIR_RENAME_SUBFOLDERS, + &rename_callback)) + { + free(old_mailbox); + free(new_mailbox); + + *errmsg="@RENAME failed: "; + return -1; + } + + maildir_quota_recalculate(mi1->homedir); + free(old_mailbox); + free(new_mailbox); + return 0; +} + +static int validate_charset(const char *tag, char **charset) +{ + libmail_u_convert_handle_t conv; + unicode_char *ucptr; + size_t ucsize; + + if (*charset == NULL) + *charset=my_strdup("ISO-8859-1"); + + conv=libmail_u_convert_tou_init(*charset, &ucptr, &ucsize, 1); + + if (!conv) + { + writes(tag); + writes(" NO [BADCHARSET] The requested character set is not supported.\r\n"); + return (-1); + } + if (libmail_u_convert_deinit(conv, NULL) == 0) + free(ucptr); + return (0); +} + +int do_imap_command(const char *tag) +{ +struct imaptoken *curtoken=nexttoken(); +int uid=0; + + if (curtoken->tokentype != IT_ATOM) return (-1); + + /* Commands that work in authenticated state */ + + if (strcmp(curtoken->tokenbuf, "CAPABILITY") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + writes("* CAPABILITY "); + imapcapability(); + writes("\r\n"); + writes(tag); + writes(" OK CAPABILITY completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "NOOP") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + if (current_mailbox) + doNoop(1); + writes(tag); + writes(" OK NOOP completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "ID") == 0) + { + if (doId() < 0) + return (-1); + writes(tag); + writes(" OK ID completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "IDLE") == 0) + { + const char *p; + + if (nexttoken()->tokentype != IT_EOL) return (-1); + + read_eol(); + + if ((p=getenv("IMAP_ENHANCEDIDLE")) == NULL + || !atoi(p) + || imapenhancedidle()) + imapidle(); + curtoken=nexttoken(); + if (strcmp(curtoken->tokenbuf, "DONE") == 0) + { + if (current_mailbox) + doNoop(0); + writes(tag); + writes(" OK IDLE completed\r\n"); + return (0); + } + return (-1); + } + if (strcmp(curtoken->tokenbuf, "LOGOUT") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + fetch_free_cache(); + writes("* BYE Courier-IMAP server shutting down\r\n"); + writes(tag); + writes(" OK LOGOUT completed\r\n"); + writeflush(); + emptytrash(); + logoutmsg(); + bye(); + } + + if (strcmp(curtoken->tokenbuf, "LIST") == 0 + || strcmp(curtoken->tokenbuf, "LSUB") == 0) + { + char *reference, *name; + int rc; + char cmdbuf[5]; + int list_flags=0; + + strcpy(cmdbuf, curtoken->tokenbuf); + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype == IT_LPAREN) + { + while ((curtoken=nexttoken())->tokentype != IT_RPAREN) + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return (-1); + + if (strcmp(curtoken->tokenbuf, "ACL") == 0) + list_flags |= LIST_ACL; + if (strcmp(curtoken->tokenbuf, "MYRIGHTS")==0) + list_flags |= LIST_MYRIGHTS; + if (strcmp(curtoken->tokenbuf, + "POSTADDRESS")==0) + list_flags |= LIST_POSTADDRESS; + } + + curtoken=nexttoken_nouc(); + } + + + if (curtoken->tokentype == IT_NIL) + reference=my_strdup(""); + else + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return (-1); + reference=my_strdup(curtoken->tokenbuf); + } + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype == IT_NIL) + name=my_strdup(""); + else + { + if (curtoken->tokentype != IT_QUOTED_STRING && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_NUMBER) + return (-1); + name=my_strdup(curtoken->tokenbuf); + } + if (nexttoken()->tokentype != IT_EOL) return (-1); + + if (strcmp(cmdbuf, "LIST")) + list_flags |= LIST_SUBSCRIBED; + + rc=mailbox_scan(reference, name, + list_flags, + list_callback, cmdbuf); + + free(reference); + free(name); + if (rc == 0) + { + writes(tag); + writes(" OK "); + writes(cmdbuf); + writes(" completed\r\n"); + } + else + { + writes(tag); + writes(" NO "); + writes(strerror(errno)); + writes("\r\n"); + rc=0; + } + writeflush(); + return (rc); + } + + if (strcmp(curtoken->tokenbuf, "APPEND") == 0) + { + struct imaptoken *tok=nexttoken_nouc(); + struct maildir_info mi; + + if (tok->tokentype != IT_NUMBER && + tok->tokentype != IT_ATOM && + tok->tokentype != IT_QUOTED_STRING) + return (-1); + + if (maildir_info_imap_find(&mi, tok->tokenbuf, + getenv("AUTHENTICATED")) < 0) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi.homedir && mi.maildir) + { + char *p=maildir_name2dir(mi.homedir, mi.maildir); + int rc; + + if (!p) + { + maildir_info_destroy(&mi); + writes(tag); + accessdenied("APPEND", + tok->tokenbuf, + ACL_INSERT); + return 0; + } + + rc=append(tag, tok->tokenbuf, p); + free(p); + maildir_info_destroy(&mi); + return (rc); + } + else if (mi.mailbox_type == MAILBOXTYPE_OLDSHARED) + { + char *p=strchr(tok->tokenbuf, '.'); + + if (p && (p=maildir_shareddir(".", p+1)) != NULL) + { + int rc; + char *q=malloc(strlen(p)+sizeof("/shared")); + + if (!q) write_error_exit(0); + + strcat(strcpy(q, p), "/shared"); + free(p); + rc=append(tag, tok->tokenbuf, q); + free(q); + maildir_info_destroy(&mi); + return rc; + } + } + + writes(tag); + accessdenied("APPEND", "folder", ACL_INSERT); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "GETQUOTAROOT") == 0) + { + char qroot[20]; + struct maildir_info minfo; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + if (maildir_info_imap_find(&minfo, curtoken->tokenbuf, + getenv("AUTHENTICATED"))) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + switch (minfo.mailbox_type) { + case MAILBOXTYPE_INBOX: + strcpy(qroot, "ROOT"); + break; + case MAILBOXTYPE_OLDSHARED: + strcpy(qroot, "SHARED"); + break; + case MAILBOXTYPE_NEWSHARED: + strcpy(qroot, "PUBLIC"); + break; + } + maildir_info_destroy(&minfo); + + writes("*"); + writes(" QUOTAROOT \""); + writeqs(curtoken->tokenbuf); + writes("\" \""); + writes(qroot); + writes("\"\r\n"); + quotainfo_out(qroot); + writes(tag); + writes(" OK GETQUOTAROOT Ok.\r\n"); + return(0); + } + + + if (strcmp(curtoken->tokenbuf, "SETQUOTA") == 0) + { + writes(tag); + writes(" NO SETQUOTA No permission.\r\n"); + return(0); + } + + if (strcmp(curtoken->tokenbuf, "GETQUOTA") == 0) + { + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + quotainfo_out(curtoken->tokenbuf); + writes(tag); + writes(" OK GETQUOTA Ok.\r\n"); + return(0); + } + + if (strcmp(curtoken->tokenbuf, "STATUS") == 0) + { + char *mailbox; + int get_messages=0, + get_recent=0, + get_uidnext=0, + get_uidvalidity=0, + get_unseen=0; + + struct imapscaninfo other_info, *loaded_infoptr, + *infoptr; + const char *p; + char *orig_mailbox; + int oneonly; + + curtoken=nexttoken_nouc(); + mailbox=parse_mailbox_error(tag, curtoken, 0, 0); + if ( mailbox == 0) + return (0); + + orig_mailbox=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + + oneonly=0; + if (curtoken->tokentype != IT_LPAREN) + { + if (curtoken->tokentype != IT_ATOM) + { + free(mailbox); + free(orig_mailbox); + return (-1); + } + oneonly=1; + } + else nexttoken(); + + while ((curtoken=currenttoken())->tokentype == IT_ATOM) + { + if (strcmp(curtoken->tokenbuf, "MESSAGES") == 0) + get_messages=1; + if (strcmp(curtoken->tokenbuf, "RECENT") == 0) + get_recent=1; + if (strcmp(curtoken->tokenbuf, "UIDNEXT") == 0) + get_uidnext=1; + if (strcmp(curtoken->tokenbuf, "UIDVALIDITY") == 0) + get_uidvalidity=1; + if (strcmp(curtoken->tokenbuf, "UNSEEN") == 0) + get_unseen=1; + nexttoken(); + if (oneonly) break; + } + + if ((!oneonly && curtoken->tokentype != IT_RPAREN) || + nexttoken()->tokentype != IT_EOL) + { + free(mailbox); + free(orig_mailbox); + return (-1); + } + + { + CHECK_RIGHTSM(orig_mailbox, status_rights, ACL_READ); + + if (!status_rights[0]) + { + writes(tag); + accessdenied("STATUS", orig_mailbox, + ACL_READ); + free(mailbox); + free(orig_mailbox); + return 0; + } + } + + + if (current_mailbox && strcmp(current_mailbox, mailbox) == 0) + { + loaded_infoptr=0; + infoptr= ¤t_maildir_info; + } + else + { + loaded_infoptr= &other_info; + infoptr=loaded_infoptr; + + imapscan_init(loaded_infoptr); + + if (imapscan_maildir(infoptr, mailbox, 1, 1, NULL)) + { + writes(tag); + writes(" NO [ALERT] STATUS failed\r\n"); + free(mailbox); + free(orig_mailbox); + return (0); + } + } + + writes("*"); + writes(" STATUS \""); + writeqs(orig_mailbox); + writes("\" ("); + p=""; + if (get_messages) + { + writes("MESSAGES "); + writen(infoptr->nmessages+infoptr->left_unseen); + p=" "; + } + if (get_recent) + { + unsigned long n=infoptr->left_unseen; + unsigned long i; + + for (i=0; i<infoptr->nmessages; i++) + if (infoptr->msgs[i].recentflag) + ++n; + writes(p); + writes("RECENT "); + writen(n); + p=" "; + } + + if (get_uidnext) + { + writes(p); + writes("UIDNEXT "); + writen(infoptr->nextuid); + p=" "; + } + + if (get_uidvalidity) + { + writes(p); + writes("UIDVALIDITY "); + writen(infoptr->uidv); + p=" "; + } + + if (get_unseen) + { + unsigned long n=infoptr->left_unseen, i; + + for (i=0; i<infoptr->nmessages; i++) + { + const char *p=infoptr->msgs[i].filename; + + p=strrchr(p, MDIRSEP[0]); + if (p && strncmp(p, MDIRSEP "2,", 3) == 0 && + strchr(p, 'S')) continue; + ++n; + } + writes(p); + writes("UNSEEN "); + writen(n); + } + writes(")\r\n"); + if (loaded_infoptr) + imapscan_free(loaded_infoptr); + free(mailbox); + free(orig_mailbox); + writes(tag); + writes(" OK STATUS Completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "CREATE") == 0) + { + char *mailbox, *orig_mailbox, *p; + int isdummy; + struct maildir_info mi; + struct imapscaninfo minfo; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + isdummy=0; + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') + { + *p=0; + isdummy=1; /* Ignore hierarchy creation */ + } + + if (maildir_info_imap_find(&mi, curtoken->tokenbuf, + getenv("AUTHENTICATED"))) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (!mi.homedir || !mi.maildir) + { + maildir_info_destroy(&mi); + writes(tag); + accessdenied("CREATE", + curtoken->tokenbuf, + ACL_CREATE); + maildir_info_destroy(&mi); + return (0); + } + + mailbox=maildir_name2dir(mi.homedir, mi.maildir); + if (!mailbox) + { + writes(tag); + writes(" NO Invalid mailbox name\r\n"); + maildir_info_destroy(&mi); + return (0); + } + + if (strcmp(mailbox, ".") == 0) + { + writes(tag); + writes(" NO INBOX already exists!\r\n"); + free(mailbox); + maildir_info_destroy(&mi); + return (0); + } + + if (check_parent_create(tag, "CREATE", curtoken->tokenbuf)) + { + free(mailbox); + maildir_info_destroy(&mi); + return (0); + } + + if (isdummy) *p=HIERCH; + orig_mailbox=my_strdup(curtoken->tokenbuf); + + if (nexttoken()->tokentype != IT_EOL) + { + free(mailbox); + free(orig_mailbox); + maildir_info_destroy(&mi); + return (-1); + } + + if (!isdummy) + { + int did_exist; + maildir_aclt_list l; + + if ((did_exist=folder_exists(orig_mailbox)) != 0) + { + if (acl_read_folder(&l, + mi.homedir, + mi.maildir) < 0) + { + free(mailbox); + free(orig_mailbox); + writes(tag); + writes(" NO Cannot create this folder" + ".\r\n"); + maildir_info_destroy(&mi); + return (0); + } + maildir_acl_delete(mi.homedir, mi.maildir); + /* Clear out fluff */ + } + + if (mdcreate(mailbox)) + { + if (did_exist) + maildir_aclt_list_destroy(&l); + free(mailbox); + free(orig_mailbox); + writes(tag); + writes(" NO Cannot create this folder.\r\n"); + maildir_info_destroy(&mi); + return (0); + } + if (did_exist) + { + const char *acl_error; + + acl_write_folder(&l, mi.homedir, + mi.maildir, NULL, + &acl_error); + maildir_aclt_list_destroy(&l); + } + } + writes(tag); + writes(" OK \""); + writeqs(orig_mailbox); + writes("\" created.\r\n"); + + /* + ** This is a dummy call to acl_read_folder that initialized + ** the default ACLs for this folder to its parent. + */ + + { + CHECK_RIGHTSM(curtoken->tokenbuf, create_rights, + ACL_CREATE); + } + + imapscan_init(&minfo); + imapscan_maildir(&minfo, mailbox, 0,0, NULL); + imapscan_free(&minfo); + + free(mailbox); + free(orig_mailbox); + maildir_info_destroy(&mi); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "DELETE") == 0) + { + char *mailbox; + char *p; + char *mailbox_name; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') /* Ignore hierarchy DELETE */ + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes(tag); + writes(" OK Folder directory delete punted.\r\n"); + return (0); + } + + mailbox_name=my_strdup(curtoken->tokenbuf); + mailbox=parse_mailbox_error(tag, curtoken, 1, 0); + if ( mailbox == 0) + { + free(mailbox_name); + return (0); + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(mailbox_name); + free(mailbox); + return (-1); + } + + if (current_mailbox && strcmp(mailbox, current_mailbox) == 0) + { + free(mailbox_name); + free(mailbox); + writes(tag); + writes(" NO Cannot delete currently-open folder.\r\n"); + return (0); + } + + if (strncmp(curtoken->tokenbuf, SHARED HIERCHS, + sizeof(SHARED HIERCHS)-1) == 0) + { + maildir_shared_unsubscribe(0, curtoken->tokenbuf+ + sizeof(SHARED HIERCHS)-1); + free(mailbox_name); + free(mailbox); + writes(tag); + writes(" OK UNSUBSCRIBEd a shared folder.\r\n"); + return (0); + } + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + delete_rights, + ACL_DELETEFOLDER); + if (delete_rights[0] == 0) + { + free(mailbox_name); + free(mailbox); + writes(tag); + accessdenied("DELETE", + curtoken->tokenbuf, + ACL_DELETEFOLDER); + return 0; + } + } + + if (!broken_uidvs()) + sleep(2); /* Make sure we never recycle them*/ + + fetch_free_cache(); + + + if (do_folder_delete(mailbox_name)) + { + writes(tag); + writes(" NO Cannot delete this folder.\r\n"); + } + else + { + writes(tag); + writes(" OK Folder deleted.\r\n"); + } + + free(mailbox_name); + free(mailbox); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "RENAME") == 0) + { + char *p; + struct maildir_info mi1, mi2; + const char *errmsg; + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + writes(tag); + writes(" NO Invalid mailbox\r\n"); + return (0); + } + + if ((p=strrchr(curtoken->tokenbuf, HIERCH)) && p[1] == 0) + *p=0; + + if (maildir_info_imap_find(&mi1, curtoken->tokenbuf, + getenv("AUTHENTICATED")) < 0) + { + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi1.homedir == NULL || mi1.maildir == NULL) + { + maildir_info_destroy(&mi1); + writes(tag); + writes(" NO Invalid mailbox\r\n"); + return (0); + } + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + rename_rights, ACL_DELETEFOLDER); + + if (rename_rights[0] == 0) + { + maildir_info_destroy(&mi1); + writes(tag); + accessdenied("RENAME", curtoken->tokenbuf, + ACL_DELETEFOLDER); + return (0); + } + } + + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + maildir_info_destroy(&mi1); + return (-1); + } + + if ((p=strrchr(curtoken->tokenbuf, HIERCH)) && p[1] == 0) + { + *p=0; + } + + + if (maildir_info_imap_find(&mi2, curtoken->tokenbuf, + getenv("AUTHENTICATED")) < 0) + { + maildir_info_destroy(&mi1); + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (check_parent_create(tag, "RENAME", curtoken->tokenbuf)) + { + maildir_info_destroy(&mi1); + maildir_info_destroy(&mi2); + return 0; + } + + if (nexttoken()->tokentype != IT_EOL) + { + maildir_info_destroy(&mi1); + maildir_info_destroy(&mi2); + return (-1); + } + + if (!broken_uidvs()) + sleep(2); + /* Make sure IMAP uidvs are not recycled */ + + if (folder_rename(&mi1, &mi2, &errmsg)) + { + writes(tag); + writes(" NO "); + writes(*errmsg == '@' ? errmsg+1:errmsg); + if (*errmsg == '@') + writes(strerror(errno)); + writes("\r\n"); + } + else + { + writes(tag); + writes(" OK Folder renamed.\r\n"); + } + + maildir_info_destroy(&mi1); + maildir_info_destroy(&mi2); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SELECT") == 0 || + strcmp(curtoken->tokenbuf, "EXAMINE") == 0) + { + char *mailbox; + int ro=curtoken->tokenbuf[0] == 'E'; + const char *p; + + + curtoken=nexttoken_nouc(); + + if (current_mailbox) + { + free(current_mailbox); + imapscan_free(¤t_maildir_info); + imapscan_init(¤t_maildir_info); + current_mailbox=0; + } + + if (current_mailbox_acl) + free(current_mailbox_acl); + current_mailbox_acl=0; + + mailbox=parse_mailbox_error(tag, curtoken, 0, 1); + if ( mailbox == 0) + return (0); + + current_mailbox_acl=get_myrightson(curtoken->tokenbuf); + if (current_mailbox_acl == NULL) + { + free(mailbox); + writes(tag); + writes(" NO Unable to read ACLs for "); + writes(curtoken->tokenbuf); + writes(": "); + writes(strerror(errno)); + writes("\r\n"); + return 0; + } + + if (strchr(current_mailbox_acl, ACL_READ[0]) == NULL) + { + free(mailbox); + free(current_mailbox_acl); + current_mailbox_acl=NULL; + writes(tag); + accessdenied("SELECT/EXAMINE", curtoken->tokenbuf, + ACL_READ); + return 0; + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(mailbox); + return (-1); + } + + if (imapscan_maildir(¤t_maildir_info, mailbox, 0, ro, + NULL)) + { + free(mailbox); + writes(tag); + writes(" NO Unable to open this mailbox.\r\n"); + return (0); + } + current_mailbox=mailbox; + + /* check if this is a shared read-only folder */ + + if (is_sharedsubdir(mailbox) && + maildir_sharedisro(mailbox)) + ro=1; + + current_mailbox_ro=ro; + + mailboxflags(ro); + mailboxmetrics(); + writes("* OK [UIDVALIDITY "); + writen(current_maildir_info.uidv); + writes("] Ok\r\n"); + myrights(); + writes(tag); + + for (p=current_mailbox_acl; *p; p++) + if (strchr(ACL_INSERT ACL_EXPUNGE + ACL_SEEN ACL_WRITE ACL_DELETEMSGS, + *p)) + break; + + if (*p == 0) + ro=1; + + writes(ro ? " OK [READ-ONLY] Ok\r\n":" OK [READ-WRITE] Ok\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SUBSCRIBE") == 0) + { + char *mailbox; + char *p; + struct maildir_info mi; + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') /* Ignore hierarchy DELETE */ + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes(tag); + writes(" OK Folder directory subscribe punted.\r\n"); + return (0); + } + + mailbox=my_strdup(curtoken->tokenbuf); + if (nexttoken()->tokentype != IT_EOL) + return (-1); + + if (maildir_info_imap_find(&mi, mailbox, + getenv("AUTHENTICATED")) < 0) + { + free(mailbox); + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi.mailbox_type != MAILBOXTYPE_OLDSHARED) + { + maildir_info_destroy(&mi); + subscribe(mailbox); + free(mailbox); + writes(tag); + writes(" OK Folder subscribed.\r\n"); + return (0); + } + maildir_info_destroy(&mi); + + p=strchr(mailbox, '.'); + + p=p ? maildir_shareddir(".", p+1):NULL; + + if (p == NULL || access(p, 0) == 0) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK Already subscribed.\r\n"); + return (0); + } + + if (!p || maildir_shared_subscribe(0, strchr(mailbox, '.')+1)) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" NO Cannot subscribe to this folder.\r\n"); + return (0); + } + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK SUBSCRIBE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "UNSUBSCRIBE") == 0) + { + char *mailbox; + char *p; + struct maildir_info mi; + + curtoken=nexttoken_nouc(); + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + p=strrchr(curtoken->tokenbuf, HIERCH); + if (p && p[1] == '\0') /* Ignore hierarchy DELETE */ + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes(tag); + writes(" OK Folder directory unsubscribe punted.\r\n"); + return (0); + } + + mailbox=my_strdup(curtoken->tokenbuf); + if (nexttoken()->tokentype != IT_EOL) + return (-1); + + if (maildir_info_imap_find(&mi, mailbox, + getenv("AUTHENTICATED")) < 0) + { + free(mailbox); + writes(tag); + writes(" NO Invalid mailbox name.\r\n"); + return (0); + } + + if (mi.mailbox_type != MAILBOXTYPE_OLDSHARED) + { + maildir_info_destroy(&mi); + unsubscribe(mailbox); + free(mailbox); + writes(tag); + writes(" OK Folder unsubscribed.\r\n"); + return (0); + } + maildir_info_destroy(&mi); + + p=strchr(mailbox, '.'); + + p=p ? maildir_shareddir(".", p+1):NULL; + + + if (p == NULL || access(p, 0)) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK Already unsubscribed.\r\n"); + return (0); + } + + fetch_free_cache(); + + if (!p || maildir_shared_unsubscribe(0, + strchr(mailbox, '.')+1)) + { + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" NO Cannot subscribe to this folder.\r\n"); + return (0); + } + if (p) + free(p); + free(mailbox); + writes(tag); + writes(" OK UNSUBSCRIBE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "NAMESPACE") == 0) + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + writes("* NAMESPACE ((\"INBOX.\" \".\")) NIL " + "((\"#shared.\" \".\")(\"" + SHARED ".\" \".\"))\r\n"); + writes(tag); + writes(" OK NAMESPACE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "ACL") == 0) + { + if (aclcmd(tag)) + { + writes(tag); + writes(" ACL FAILED: "); + writes(strerror(errno)); + writes("\r\n"); + } + return 0; + } + + /* RFC 2086 */ + + if (strcmp(curtoken->tokenbuf, "SETACL") == 0 || + strcmp(curtoken->tokenbuf, "DELETEACL") == 0) + { + char *mailbox; + char *identifier; + struct maildir_info mi; + maildir_aclt_list aclt_list; + const char *acl_error; + int doset=curtoken->tokenbuf[0] == 'S'; + const char *origcmd=doset ? "SETACL":"DELETEACL"; + + curtoken=nexttoken_nouc(); + + mailbox=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mailbox) + return 0; + free(mailbox); + + mailbox=my_strdup(curtoken->tokenbuf); + + if (maildir_info_imap_find(&mi, mailbox, + getenv("AUTHENTICATED")) < 0) + { + writes(tag); + writes(" NO Invalid mailbox.\r\n"); + free(mailbox); + return 0; + } + + if (mi.homedir == NULL || mi.maildir == NULL) + { + maildir_info_destroy(&mi); + writes(tag); + writes(" NO Cannot set ACLs for this mailbox\r\n"); + free(mailbox); + return 0; + } + + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + break; + default: + maildir_info_destroy(&mi); + free(mailbox); + return -1; + } + + identifier=acl2_identifier(tag, curtoken->tokenbuf); + + if (identifier == NULL) + { + maildir_info_destroy(&mi); + free(mailbox); + return 0; + } + + if (doset) + { + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + break; + default: + free(identifier); + maildir_info_destroy(&mi); + free(mailbox); + return -1; + } + } + + { + CHECK_RIGHTSM(mailbox, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes(tag); + accessdenied(origcmd, mailbox, + ACL_ADMINISTER); + free(identifier); + maildir_info_destroy(&mi); + free(mailbox); + return 0; + } + } + + if (acl_read_folder(&aclt_list, mi.homedir, mi.maildir)) + { + writes(tag); + writes(" NO Cannot read existing ACLs.\r\n"); + free(identifier); + maildir_info_destroy(&mi); + free(mailbox); + return 0; + } + + if (do_acl_mod(&aclt_list, &mi, identifier, + doset ? curtoken->tokenbuf:"", + &acl_error) < 0) + { + writes(tag); + writes(acl_error ? + " NO Cannot modify ACLs as requested.\r\n" : + " NO Cannot modify ACLs on this mailbox.\r\n"); + } + else + { + char *p=get_myrightson(mailbox); + + if (p) + free(p); + /* Side effect - change current folder's ACL */ + + writes(tag); + writes(" OK ACLs updated.\r\n"); + } + + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&mi); + free(identifier); + free(mailbox); + return 0; + } + + if (strcmp(curtoken->tokenbuf, "GETACL") == 0) + { + maildir_aclt_list l; + char *mailbox_owner; + char *mb; + + curtoken=nexttoken_nouc(); + + mb=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mb) + return 0; + free(mb); + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + acl_rights, + ACL_ADMINISTER); + if (acl_rights[0] == 0) + { + writes(tag); + accessdenied("GETACL", curtoken->tokenbuf, + ACL_ADMINISTER); + return 0; + } + } + + if (get_acllist(&l, curtoken->tokenbuf, + &mailbox_owner) < 0) + { + writes(tag); + writes(" NO Cannot retrieve ACLs for mailbox.\r\n"); + return 0; + } + free(mailbox_owner); + + writes("* ACL \""); + writeqs(curtoken->tokenbuf); + writes("\""); + maildir_aclt_list_enum(&l, getacl_cb, NULL); + writes("\r\n"); + writes(tag); + writes(" OK GETACL completed.\r\n"); + maildir_aclt_list_destroy(&l); + return 0; + } + + if (strcmp(curtoken->tokenbuf, "LISTRIGHTS") == 0) + { + maildir_aclt_list l; + char *mailbox_owner; + char *mb; + + curtoken=nexttoken_nouc(); + + mb=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mb) + return 0; + free(mb); + + { + char *myrights=get_myrightson(curtoken->tokenbuf); + + if (!strchr(myrights, ACL_LOOKUP[0]) && + !strchr(myrights, ACL_READ[0]) && + !strchr(myrights, ACL_INSERT[0]) && + !strchr(myrights, ACL_CREATE[0]) && + !strchr(myrights, ACL_DELETEFOLDER[0]) && + !strchr(myrights, ACL_EXPUNGE[0]) && + !strchr(myrights, ACL_ADMINISTER[0])) + { + free(myrights); + writes(tag); + accessdenied("GETACL", curtoken->tokenbuf, + ACL_ADMINISTER); + return 0; + } + free(myrights); + } + + if (get_acllist(&l, curtoken->tokenbuf, + &mailbox_owner) < 0) + { + writes(tag); + writes(" NO Cannot retrieve ACLs for mailbox.\r\n"); + return 0; + } + + mb=my_strdup(curtoken->tokenbuf); + + switch ((curtoken=nexttoken_nouc())->tokentype) { + case IT_QUOTED_STRING: + case IT_ATOM: + case IT_NUMBER: + break; + default: + free(mb); + free(mailbox_owner); + maildir_aclt_list_destroy(&l); + return -1; + } + + writes("* LISTRIGHTS \""); + writeqs(mb); + writes("\" \""); + writeqs(curtoken->tokenbuf); + writes("\""); + free(mb); + + + if (curtoken->tokenbuf[0] == '-' && + (MAILDIR_ACL_ANYONE(curtoken->tokenbuf+1) || + (strncmp(mailbox_owner, "user=", 5) == 0 && + strcmp(curtoken->tokenbuf+1, mailbox_owner+5) == 0))) + { + writes(" \"\" " + ACL_CREATE " " + ACL_DELETE_SPECIAL " " + ACL_INSERT " " + ACL_POST " " + ACL_READ " " + ACL_SEEN " " + ACL_WRITE "\r\n"); + } + else if (strncmp(mailbox_owner, "user=", 5) == 0 && + strcmp(curtoken->tokenbuf, mailbox_owner+5) == 0) + { + writes(" \"" + ACL_ADMINISTER + ACL_LOOKUP "\" " + ACL_CREATE " " + ACL_DELETE_SPECIAL " " + ACL_INSERT " " + ACL_POST " " + ACL_READ " " + ACL_SEEN " " + ACL_WRITE "\r\n"); + } + else + { + writes(" \"\" " + ACL_ADMINISTER " " + ACL_CREATE " " + ACL_DELETE_SPECIAL " " + ACL_INSERT " " + ACL_LOOKUP " " + ACL_POST " " + ACL_READ " " + ACL_SEEN " " + ACL_WRITE "\r\n"); + } + writes(tag); + writes(" OK LISTRIGHTS completed.\r\n"); + free(mailbox_owner); + maildir_aclt_list_destroy(&l); + return 0; + } + + if (strcmp(curtoken->tokenbuf, "MYRIGHTS") == 0) + { + char *mb; + + curtoken=nexttoken_nouc(); + + mb=parse_mailbox_error(tag, curtoken, 0, 0); + if (!mb) + return 0; + free(mb); + + { + char *myrights=get_myrightson(curtoken->tokenbuf); + + if (!strchr(myrights, ACL_LOOKUP[0]) && + !strchr(myrights, ACL_READ[0]) && + !strchr(myrights, ACL_INSERT[0]) && + !strchr(myrights, ACL_CREATE[0]) && + !strchr(myrights, ACL_DELETEFOLDER[0]) && + !strchr(myrights, ACL_EXPUNGE[0]) && + !strchr(myrights, ACL_ADMINISTER[0])) + { + free(myrights); + writes(tag); + accessdenied("GETACL", curtoken->tokenbuf, + ACL_ADMINISTER); + return 0; + } + free(myrights); + } + + mb=get_myrightson(curtoken->tokenbuf); + + if (!mb) + { + writes(tag); + writes(" NO Cannot retrieve ACLs for mailbox.\r\n"); + return 0; + } + + writes("* MYRIGHTS \""); + writeqs(curtoken->tokenbuf); + writes("\" \""); + + writeacl1(mb); + free(mb); + writes("\"\r\n"); + writes(tag); + writes(" OK MYRIGHTS completed.\r\n"); + return 0; + } + + /* mailbox commands */ + + if (current_mailbox == 0) return (-1); + + if (strcmp(curtoken->tokenbuf, "UID") == 0) + { + uid=1; + if ((curtoken=nexttoken())->tokentype != IT_ATOM) + return (-1); + if (strcmp(curtoken->tokenbuf, "COPY") && + strcmp(curtoken->tokenbuf, "FETCH") && + strcmp(curtoken->tokenbuf, "SEARCH") && + strcmp(curtoken->tokenbuf, "THREAD") && + strcmp(curtoken->tokenbuf, "SORT") && + strcmp(curtoken->tokenbuf, "STORE") && + strcmp(curtoken->tokenbuf, "EXPUNGE")) + return (-1); + } + + if (strcmp(curtoken->tokenbuf, "CLOSE") == 0) + { + if (nexttoken()->tokentype != IT_EOL) + return (-1); + + if (!current_mailbox_ro + && strchr(current_mailbox_acl, ACL_EXPUNGE[0])) + expunge(); + free(current_mailbox); + imapscan_free(¤t_maildir_info); + imapscan_init(¤t_maildir_info); + current_mailbox=0; + writes(tag); + writes(" OK mailbox closed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "FETCH") == 0) + { + struct fetchinfo *fi; + char *msgset; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + + if ((curtoken=nexttoken())->tokentype != IT_LPAREN) + { + if (curtoken->tokentype != IT_ATOM) + { + free(msgset); + return (-1); + } + fi=fetchinfo_alloc(1); + } + else + { + (void)nexttoken(); + fi=fetchinfo_alloc(0); + if (fi && currenttoken()->tokentype != IT_RPAREN) + { + fetchinfo_free(fi); + fi=0; + } + nexttoken(); + } + + if (fi == 0 || currenttoken()->tokentype != IT_EOL) + { + free(msgset); + if (fi) fetchinfo_free(fi); + return (-1); + } + + do_msgset(msgset, &do_fetch, fi, uid); + fetchinfo_free(fi); + free(msgset); + writes(tag); + writes(" OK FETCH completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "STORE") == 0) + { + char *msgset; + struct storeinfo storeinfo_s; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + + (void)nexttoken(); + current_maildir_info.keywordList->keywordAddedRemoved=0; + + if (storeinfo_init(&storeinfo_s) || + currenttoken()->tokentype != IT_EOL) + { + if (storeinfo_s.keywords) + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + return (-1); + } + + /* Do not change \Deleted if this is a readonly mailbox */ + + if (current_mailbox_ro && storeinfo_s.flags.deleted) + { + if (storeinfo_s.keywords) + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + writes(tag); + writes(" NO Current box is selected READ-ONLY.\r\n"); + return (0); + } + + fetch_free_cache(); + + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + + current_maildir_info.keywordList->keywordAddedRemoved=0; + + if (do_msgset(msgset, &do_store, &storeinfo_s, uid)) + { + if (storeinfo_s.keywords) + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + if (current_maildir_info.keywordList + ->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + + writes(tag); + writes(" NO [ALERT] You exceeded your mail quota.\r\n"); + return (0); + } + if (storeinfo_s.keywords) + { + struct imap_addRemoveKeywordInfo imapInfo; + + switch (storeinfo_s.plusminus) { + case '+': + case '-': + + imapInfo.msgset=msgset; + imapInfo.uid=uid; + + if (!fastkeywords() && + addRemoveKeywords(&imap_addRemoveKeywords, + &imapInfo, &storeinfo_s)) + { + libmail_kwmDestroy(storeinfo_s.keywords); + free(msgset); + writes(tag); + writes(" NO An error occured while" + " updating keywords: "); + writes(strerror(errno)); + writes(".\r\n"); + return 0; + } + break; + } + + libmail_kwmDestroy(storeinfo_s.keywords); + } + free(msgset); + if (current_maildir_info.keywordList->keywordAddedRemoved) + mailboxflags(current_mailbox_ro); + writes(tag); + writes(" OK STORE completed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SEARCH") == 0) + { + char *charset=0; + struct searchinfo *si, *sihead; + unsigned long i; + + curtoken=nexttoken_okbracket(); + if (curtoken->tokentype == IT_ATOM && + strcmp(curtoken->tokenbuf, "CHARSET") == 0) + { + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + charset=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + } + + if (validate_charset(tag, &charset)) + { + if (charset) + free(charset); + return (0); + } + + if ((si=alloc_parsesearch(&sihead)) == 0) + { + free(charset); + return (-1); + } + if (currenttoken()->tokentype != IT_EOL) + { + free(charset); + free_search(sihead); + return (-1); + } + +#if 0 + writes("* OK "); + debug_search(si); + writes("\r\n"); +#endif + writes("* SEARCH"); + dosearch(si, sihead, charset, uid); + writes("\r\n"); + free(charset); + + for (i=0; i<current_maildir_info.nmessages; i++) + if (current_maildir_info.msgs[i].changedflags) + fetchflags(i); + writes(tag); + writes(" OK SEARCH done.\r\n"); + free_search(sihead); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "THREAD") == 0) + { + char *charset=0; + struct searchinfo *si, *sihead; + unsigned long i; + + /* The following jazz is mainly for future extensions */ + + void (*thread_func)(struct searchinfo *, struct searchinfo *, + const char *, int); + search_type thread_type; + + { + const char *p=getenv("IMAP_DISABLETHREADSORT"); + int n= p ? atoi(p):0; + + if (n > 0) + { + writes(tag); + writes(" NO This command is disabled by the system administrator.\r\n"); + return (0); + } + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + if (strcmp(curtoken->tokenbuf, "ORDEREDSUBJECT") == 0) + { + thread_func=dothreadorderedsubj; + thread_type=search_orderedsubj; + } + else if (strcmp(curtoken->tokenbuf, "REFERENCES") == 0) + { + thread_func=dothreadreferences; + thread_type=search_references1; + } + else + { + return (-1); + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + return (-1); + + charset=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + + if ((si=alloc_parsesearch(&sihead)) == 0) + { + if (charset) free(charset); + return (-1); + } + + si=alloc_searchextra(si, &sihead, thread_type); + + if (currenttoken()->tokentype != IT_EOL) + { + if (charset) free(charset); + free_search(sihead); + return (-1); + } + + if (validate_charset(tag, &charset)) + { + if (charset) + free(charset); + return (0); + } + + writes("* THREAD "); + (*thread_func)(si, sihead, charset, uid); + writes("\r\n"); + free(charset); + + for (i=0; i<current_maildir_info.nmessages; i++) + if (current_maildir_info.msgs[i].changedflags) + fetchflags(i); + writes(tag); + writes(" OK THREAD done.\r\n"); + free_search(sihead); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "SORT") == 0) + { + char *charset=0; + struct searchinfo *si, *sihead; + unsigned long i; + struct temp_sort_stack *ts=0; + + { + const char *p=getenv("IMAP_DISABLETHREADSORT"); + int n= p ? atoi(p):0; + + if (n > 0) + { + writes(tag); + writes(" NO This command is disabled by the system administrator.\r\n"); + return (0); + } + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_LPAREN) return (-1); + while ((curtoken=nexttoken())->tokentype != IT_RPAREN) + { + search_type st; + struct temp_sort_stack *newts; + + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + free_temp_sort_stack(ts); + return (-1); + } + + if (strcmp(curtoken->tokenbuf, "SUBJECT") == 0) + { + st=search_orderedsubj; + } + else if (strcmp(curtoken->tokenbuf, "ARRIVAL") == 0) + { + st=search_arrival; + } + else if (strcmp(curtoken->tokenbuf, "CC") == 0) + { + st=search_cc; + } + else if (strcmp(curtoken->tokenbuf, "DATE") == 0) + { + st=search_date; + } + else if (strcmp(curtoken->tokenbuf, "FROM") == 0) + { + st=search_from; + } + else if (strcmp(curtoken->tokenbuf, "REVERSE") == 0) + { + st=search_reverse; + } + else if (strcmp(curtoken->tokenbuf, "SIZE") == 0) + { + st=search_size; + } + else if (strcmp(curtoken->tokenbuf, "TO") == 0) + { + st=search_to; + } + else + { + free_temp_sort_stack(ts); + return (-1); + } + + newts=(struct temp_sort_stack *)malloc( + sizeof(struct temp_sort_stack)); + if (!newts) write_error_exit(0); + newts->next=ts; + newts->type=st; + ts=newts; + } + + if (ts == 0 /* No criteria */ + || ts->type == search_reverse) + /* Can't end with the REVERSE keyword */ + { + free_temp_sort_stack(ts); + return (-1); + } + + curtoken=nexttoken(); + if (curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + free_temp_sort_stack(ts); + return (-1); + } + + charset=my_strdup(curtoken->tokenbuf); + curtoken=nexttoken(); + + if ((si=alloc_parsesearch(&sihead)) == 0) + { + if (charset) free(charset); + free_temp_sort_stack(ts); + return (-1); + } + + while (ts) + { + struct temp_sort_stack *cts=ts; + + ts=cts->next; + si=alloc_searchextra(si, &sihead, cts->type); + free(cts); + } + + if (currenttoken()->tokentype != IT_EOL) + { + if (charset) free(charset); + free_search(sihead); + return (-1); + } + + if (validate_charset(tag, &charset)) + { + if (charset) free(charset); + free_search(sihead); + return (0); + } + + writes("* SORT"); + dosortmsgs(si, sihead, charset, uid); + writes("\r\n"); + free(charset); + + for (i=0; i<current_maildir_info.nmessages; i++) + if (current_maildir_info.msgs[i].changedflags) + fetchflags(i); + writes(tag); + writes(" OK SORT done.\r\n"); + free_search(sihead); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "CHECK") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + doNoop(0); + writes(tag); + writes(" OK CHECK completed\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "EXPUNGE") == 0) + { + if (strchr(current_mailbox_acl, ACL_EXPUNGE[0]) == NULL) + { + writes(tag); + accessdenied("EXPUNGE", "current mailbox", + ACL_EXPUNGE); + return 0; + } + + if (current_mailbox_ro) + { + writes(tag); + writes(" NO Cannot expunge read-only mailbox.\r\n"); + return 0; + } + + if (uid) + { + char *msgset; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + if (nexttoken()->tokentype != IT_EOL) return (-1); + + do_msgset(msgset, &uid_expunge, NULL, 1); + free(msgset); + } + else + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + expunge(); + } + doNoop(0); + writes(tag); + writes(" OK EXPUNGE completed\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "COPY") == 0) + { + struct maildirsize quotainfo; + char *mailbox; + char *msgset; + struct copyquotainfo cqinfo; + int has_quota; + int isshared; + struct do_copy_info copy_info; + unsigned long copy_uidv; + char access_rights[8]; + + curtoken=nexttoken(); + if (!ismsgset(curtoken)) return (-1); + msgset=my_strdup(curtoken->tokenbuf); + + curtoken=nexttoken_nouc(); + + if (curtoken->tokentype != IT_NUMBER && + curtoken->tokentype != IT_ATOM && + curtoken->tokentype != IT_QUOTED_STRING) + { + free(msgset); + return (-1); + } + + mailbox=decode_valid_mailbox(curtoken->tokenbuf, 1); + + if (!mailbox) + { + struct maildir_info mi; + + free(msgset); + + if (maildir_info_imap_find(&mi, curtoken->tokenbuf, + getenv("AUTHENTICATED")) + == 0) + { + if (nexttoken()->tokentype == IT_EOL) + { + maildir_info_destroy(&mi); + writes(tag); + writes(" NO [TRYCREATE] Mailbox does not exist.\r\n"); + return (0); + } + maildir_info_destroy(&mi); + } + return (-1); + } + + { + CHECK_RIGHTSM(curtoken->tokenbuf, + append_rights, + ACL_INSERT ACL_DELETEMSGS + ACL_SEEN ACL_WRITE); + + if (strchr(append_rights, ACL_INSERT[0]) == NULL) + { + writes(tag); + accessdenied("COPY", + curtoken->tokenbuf, + ACL_INSERT); + return 0; + } + + strcpy(access_rights, append_rights); + } + + if (nexttoken()->tokentype != IT_EOL) + { + free(msgset); + return (-1); + } + + if (access(mailbox, 0)) + { + writes(tag); + writes(" NO [TRYCREATE] Mailbox does not exist.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + + fetch_free_cache(); + cqinfo.destmailbox=mailbox; + cqinfo.acls=access_rights; + + /* + ** If the destination is a shared folder, copy it into the + ** real shared folder. + */ + + isshared=0; + if (is_sharedsubdir(cqinfo.destmailbox)) + { + char *p=malloc(strlen(cqinfo.destmailbox)+sizeof("/shared")); + + if (!p) write_error_exit(0); + strcat(strcpy(p, cqinfo.destmailbox), "/shared"); + + free(mailbox); + mailbox=cqinfo.destmailbox=p; + isshared=1; + } + + cqinfo.nbytes=0; + cqinfo.nfiles=0; + + has_quota=0; + if (!isshared && maildirquota_countfolder(cqinfo.destmailbox)) + { + if (maildir_openquotafile("ainfo, + ".") == 0) + { + if (quotainfo.fd >= 0) + has_quota=1; + maildir_closequotafile("ainfo); + } + + if (has_quota > 0 && + do_msgset(msgset, &do_copy_quota_calc, &cqinfo, + uid)) + has_quota= -1; + } + + if (has_quota > 0 && cqinfo.nfiles > 0) + { + + if (maildir_quota_add_start(".", "ainfo, + cqinfo.nbytes, + cqinfo.nfiles, + getenv("MAILDIRQUOTA"))) + { + writes(tag); + writes( + " NO [ALERT] You exceeded your mail quota.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + + maildir_quota_add_end("ainfo, + cqinfo.nbytes, + cqinfo.nfiles); + } + + if (is_outbox(mailbox)) + { + int counter=0; + + if (do_msgset(msgset, &do_count, &counter, uid) || + counter > 1) + { + writes(tag); + writes(" NO [ALERT] Only one message may be sent at a time.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + } + + copy_info.mailbox=mailbox; + copy_info.uidplus_list=NULL; + copy_info.uidplus_tail= ©_info.uidplus_list; + copy_info.acls=access_rights; + + if (has_quota < 0 || + do_msgset(msgset, &do_copy_message, ©_info, uid) || + uidplus_fill(copy_info.mailbox, copy_info.uidplus_list, + ©_uidv)) + { + uidplus_abort(copy_info.uidplus_list); + writes(tag); + writes(" NO [ALERT] COPY failed - no write permission or out of disk space.\r\n"); + free(msgset); + free(mailbox); + return (0); + } + + dirsync(mailbox); + + writes(tag); + writes(" OK"); + + if (copy_info.uidplus_list != NULL) + { + writes(" [COPYUID "); + writen(copy_uidv); + uidplus_writemsgset(copy_info.uidplus_list, 0); + uidplus_writemsgset(copy_info.uidplus_list, 1); + writes("]"); + } + + writes(" COPY completed.\r\n"); + uidplus_free(copy_info.uidplus_list); + + free(msgset); + free(mailbox); + return (0); + } + return (-1); +} + +static void dogethostname() +{ +char buf[2048]; +char *p; + + if (gethostname(buf, sizeof(buf)) < 0) + strcpy(buf, "courier-imap"); + p=malloc(strlen(buf)+sizeof("HOSTNAME=")); + if (!p) + write_error_exit(0); + strcat(strcpy(p, "HOSTNAME="), buf); + putenv(p); +} + +#if 0 +static char *getcurdir() +{ +char *pathbuf=0; +size_t pathlen=256; + + for (;;) + { + if ((pathbuf=pathbuf ? realloc(pathbuf, pathlen): + malloc(pathlen)) == 0) + write_error_exit(0); + if (getcwd(pathbuf, pathlen-1)) + return (pathbuf); + if (errno != ERANGE) + { + free(pathbuf); + return (0); + } + pathlen += 256; + } +} + +static char *getimapscanpath(const char *argv0) +{ +char *p, *q; + + if (*argv0 != '/') + { + p=getcurdir(); + if (!p) + { + perror("getcwd"); + exit(1); + } + q=malloc(strlen(p)+strlen(argv0)+sizeof("//imapscan")); + if (!q) write_error_exit(0); + strcat(strcat(strcpy(q, p), "/"), argv0); + } + else + { + q=malloc(strlen(argv0)+sizeof("imapscan")); + if (!q) write_error_exit(0); + strcpy(q, argv0); + } + p=strrchr(q, '/'); + if (p && p[1] == 0) + { + *p=0; + p=strrchr(q, '/'); + } + + if (p) + p[1]=0; + else *q=0; + + strcat(q, "imapscan"); + return (q); +} +#endif + +static void chkdisabled(const char *ip, const char *port) +{ + const char *p; + if (auth_getoptionenvint("disableimap")) + { + writes("* BYE IMAP access disabled for this account.\r\n"); + writeflush(); + exit(0); + } + + if ( auth_getoptionenvint("disableinsecureimap") + && ((p=getenv("IMAP_TLS")) == NULL || !atoi(p))) + { + writes("* BYE IMAP access disabled via insecure connection.\r\n"); + writeflush(); + exit(0); + } + + fprintf(stderr, "INFO: LOGIN, user=%s, ip=[%s], port=[%s], protocol=%s\n", + getenv("AUTHENTICATED"), ip, port, + protocol); +} + +static int chk_clock_skew() +{ + static const char fn[]="tmp/courier-imap.clockskew.chk"; + struct stat stat_buf; + int fd; + time_t t; + + unlink(fn); + fd=open(fn, O_RDWR|O_TRUNC|O_CREAT, 0666); + time(&t); + + if (fd < 0) + return 0; /* Something else is wrong */ + + if (fstat(fd, &stat_buf) < 0) + { + close(fd); + return -1; /* Something else is wrong */ + } + close(fd); + unlink(fn); + + if (stat_buf.st_mtime < t - 30 || stat_buf.st_mtime > t+30) + return -1; + return 0; +} + +#if SMAP + +static int is_smap() +{ + const char *p; + + p=getenv("PROTOCOL"); + + if (p && strcmp(p, "SMAP1") == 0) + return 1; + return 0; +} + +#else + +#define is_smap() 0 + +#endif + + +int main(int argc, char **argv) +{ + const char *ip; + const char *p; + const char *tag; + const char *port; + mode_t oldumask; + +#ifdef HAVE_SETVBUF_IOLBF + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); +#endif + time(&start_time); + if (argc > 1 && strcmp(argv[1], "--version") == 0) + { + printf("%s\n", PROGRAMVERSION); + exit(0); + } + + if ((tag=getenv("IMAPLOGINTAG")) != 0) + { + if (getenv("AUTHENTICATED") == NULL) + { + printf("* BYE AUTHENTICATED environment variable not set.\r\n"); + fflush(stdout); + exit(0); + } + } + else + { + const char *p; + + putenv("TCPREMOTEIP=127.0.0.1"); + putenv("TCPREMOTEPORT=0"); + + p=getenv("AUTHENTICATED"); + if (!p || !*p) + { + struct passwd *pw=getpwuid(getuid()); + char *me; + + if (!pw) + { + fprintf(stderr, + "ERR: uid %lu not found in passwd file\n", + (unsigned long)getuid()); + exit(1); + } + + me=malloc(sizeof("AUTHENTICATED=")+strlen(pw->pw_name)); + if (!me) + write_error_exit(0); + + strcat(strcpy(me, "AUTHENTICATED="), pw->pw_name); + putenv(me); + } + } + +#if HAVE_SETLOCALE + setlocale(LC_CTYPE, "C"); +#endif + + ip=getenv("TCPREMOTEIP"); + if (!ip || !*ip) exit(0); + + port=getenv("TCPREMOTEPORT"); + if (!port || !*port) exit(0); + + protocol=getenv("PROTOCOL"); + + if (!protocol || !*protocol) + protocol="IMAP"; + + putenv("IMAP_STARTTLS=NO"); /* No longer grok STARTTLS */ + + /* We use select() with a timeout, so use non-blocking filedescs */ + + if (fcntl(0, F_SETFL, O_NONBLOCK) || + fcntl(1, F_SETFL, O_NONBLOCK)) + { + perror("fcntl"); + exit(1); + } + + { + struct stat buf; + + if ( stat(".", &buf) < 0 || buf.st_mode & S_ISVTX) + { + fprintf(stderr, "INFO: LOCKED, user=%s, ip=[%s], port=[%s]\n", + getenv("AUTHENTICATED"), ip, port); + + if (is_smap()) + writes("-ERR "); + else + writes("* BYE "); + + writes("Your account is temporarily unavailable (+t bit set on home directory).\r\n"); + writeflush(); + exit(0); + } + } + + if (argc > 1) + p=argv[1]; + else + p=getenv("MAILDIR"); + + if (!p) + p="./Maildir"; +#if 0 + imapscanpath=getimapscanpath(argv[0]); +#endif + if (chdir(p)) + { + fprintf(stderr, "chdir %s: %s\n", p, strerror(errno)); + write_error_exit(strerror(errno)); + } + maildir_loginexec(); + + if (auth_getoptionenvint("disableshared")) + { + maildir_acl_disabled=1; + maildir_newshared_disabled=1; + } + + /* Remember my device/inode */ + + { + struct stat buf; + + if ( stat(".", &buf) < 0) + write_error_exit("Cannot stat current directory"); + + homedir_dev=buf.st_dev; + homedir_ino=buf.st_ino; + + errno=0; + + p=getenv("IMAP_MAILBOX_SANITY_CHECK"); + + if (!p || !*p) p="1"; + + if (atoi(p)) + { + if ( buf.st_uid != geteuid() || + buf.st_gid != getegid()) + write_error_exit("Account's mailbox directory is not owned by the correct uid or gid"); + } + } + + p=getenv("HOSTNAME"); + if (!p) + dogethostname(); + + if ((p=getenv("IMAP_TRASHFOLDERNAME")) != 0 && *p) + { + trash = strdup(p); + dot_trash = malloc(strlen(trash) + 2); + dot_trash[0] = '.'; + strcpy(&dot_trash[1], trash); + } + +#if 0 + mdcreate("." DRAFTS); +#endif + + if ((p=getenv("IMAPDEBUGFILE")) != 0 && *p && + access(p, 0) == 0) + { + oldumask = umask(027); + debugfile=fopen(p, "a"); + umask(oldumask); + if (debugfile==NULL) + write_error_exit(0); + } + initcapability(); + + emptytrash(); + signal(SIGPIPE, SIG_IGN); + + libmail_kwVerbotten=KEYWORD_IMAPVERBOTTEN; + libmail_kwCaseSensitive=0; + + if (!keywords()) + libmail_kwEnabled=0; + + maildir_info_munge_complex((p=getenv("IMAP_SHAREDMUNGENAMES")) && + atoi(p)); + +#if SMAP + if (is_smap()) + { + if (chk_clock_skew()) + { + writes("-ERR Clock skew detected. Check the clock on the file server\r\n"); + writeflush(); + exit(0); + } + + writes("+OK SMAP1 LOGIN Ok.\n"); + + smapflag=1; + + libmail_kwVerbotten=KEYWORD_SMAPVERBOTTEN; + libmail_kwCaseSensitive=1; + + chkdisabled(ip, port); + smap(); + logoutmsg(); + emptytrash(); + return (0); + } +#endif + + if (chk_clock_skew()) + { + writes("* BYE Clock skew detected. Check the clock on the file server\r\n"); + writeflush(); + exit(0); + } + + { + struct maildirwatch *w; + + if ((w=maildirwatch_alloc(".")) == NULL) + { + writes("* OK [ALERT] Filesystem notification initialization error -- contact your mail administrator (check for configuration errors with the FAM/Gamin library)\r\n"); + } + else + { + maildirwatch_free(w); + } + } + + if ((tag=getenv("IMAPLOGINTAG")) != 0) + { + writes(tag); + writes(" OK LOGIN Ok.\r\n"); + } + else + writes("* PREAUTH Ready.\r\n"); + writeflush(); + chkdisabled(ip, port); + imapscan_init(¤t_maildir_info); + mainloop(); + fetch_free_cached(); + bye(); + return (0); +} diff --git a/imap/imapd.cnf.gnutls b/imap/imapd.cnf.gnutls new file mode 100644 index 0000000..1078baf --- /dev/null +++ b/imap/imapd.cnf.gnutls @@ -0,0 +1,9 @@ +organization = "Courier Mail Server" +unit = "Automatically-generated IMAP SSL key" +locality = "New York" +state = "NY" +country = US +cn = "localhost" +serial = 001 +expiration_days = 365 +email = "postmaster@example.com" diff --git a/imap/imapd.cnf.openssl.in b/imap/imapd.cnf.openssl.in new file mode 100644 index 0000000..0c66526 --- /dev/null +++ b/imap/imapd.cnf.openssl.in @@ -0,0 +1,23 @@ + +RANDFILE = @certsdir@/imapd.rand + +[ req ] +default_bits = 1024 +encrypt_key = yes +distinguished_name = req_dn +x509_extensions = cert_type +prompt = no +default_md = sha1 + +[ req_dn ] +C=US +ST=NY +L=New York +O=Courier Mail Server +OU=Automatically-generated IMAP SSL key +CN=localhost +emailAddress=postmaster@example.com + + +[ cert_type ] +nsCertType = server diff --git a/imap/imapd.dist.in b/imap/imapd.dist.in new file mode 100644 index 0000000..269fdd6 --- /dev/null +++ b/imap/imapd.dist.in @@ -0,0 +1,462 @@ +##VERSION: $Id:$ +# +# imapd created from imapd.dist by sysconftool +# +# Do not alter lines that begin with ##, they are used when upgrading +# this configuration. +# +# Copyright 1998 - 2008 Double Precision, Inc. See COPYING for +# distribution information. +# +# This configuration file sets various options for the Courier-IMAP server +# when used with the couriertcpd server. +# A lot of the stuff here is documented in the manual page for couriertcpd. +# +# NOTE - do not use \ to split long variable contents on multiple lines. +# This will break the default imapd.rc script, which parses this file. +# +##NAME: ADDRESS:0 +# +# Address to listen on, can be set to a single IP address. +# +# ADDRESS=127.0.0.1 + +ADDRESS=0 + +##NAME: PORT:1 +# +# Port numbers that connections are accepted on. The default is 143, +# the standard IMAP port. +# +# Multiple port numbers can be separated by commas. When multiple port +# numbers are used it is possible to select a specific IP address for a +# given port as "ip.port". For example, "127.0.0.1.900,192.68.0.1.900" +# accepts connections on port 900 on IP addresses 127.0.0.1 and 192.68.0.1 +# The previous ADDRESS setting is a default for ports that do not have +# a specified IP address. + +PORT=143 + +##NAME: AUTHSERVICE:0 +# +# It's possible to authenticate using a different 'service' parameter +# depending on the connection's port. This only works with authentication +# modules that use the 'service' parameter, such as PAM. Example: +# +# AUTHSERVICE143=imap +# AUTHSERVICE993=imaps + +##NAME: MAXDAEMONS:0 +# +# Maximum number of IMAP servers started +# + +MAXDAEMONS=40 + +##NAME: MAXPERIP:0 +# +# Maximum number of connections to accept from the same IP address + +MAXPERIP=4 + +##NAME: PIDFILE:0 +# +# File where couriertcpd will save its process ID +# + +PIDFILE=@piddir@/imapd.pid + +##NAME: TCPDOPTS:0 +# +# Miscellaneous couriertcpd options that shouldn't be changed. +# + +TCPDOPTS="-nodnslookup -noidentlookup" + +##NAME: LOGGEROPTS:0 +# +# courierlogger(1) options. +# + +LOGGEROPTS="-name=imapd" + +##NAME: DEFDOMAIN:0 +# +# Optional default domain. If the username does not contain the +# first character of DEFDOMAIN, then it is appended to the username. +# If DEFDOMAIN and DOMAINSEP are both set, then DEFDOMAIN is appended +# only if the username does not contain any character from DOMAINSEP. +# You can set different default domains based on the the interface IP +# address using the -access and -accesslocal options of couriertcpd(1). + +#DEFDOMAIN="@example.com" + +##NAME: IMAP_CAPABILITY:1 +# +# IMAP_CAPABILITY specifies what most of the response should be to the +# CAPABILITY command. +# +# If you have properly configured Courier to use CRAM-MD5, CRAM-SHA1, or +# CRAM-SHA256 authentication (see INSTALL), set IMAP_CAPABILITY as follows: +# +# IMAP_CAPABILITY="IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA AUTH=CRAM-MD5 AUTH=CRAM-SHA1 AUTH=CRAM-SHA256 IDLE" +# + +IMAP_CAPABILITY="IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE" + +##NAME: KEYWORDS_CAPABILITY:0 +# +# IMAP_KEYWORDS=1 enables custom IMAP keywords. Set this option to 0 to +# disable custom keywords. +# +# IMAP_KEYWORDS=2 also enables custom IMAP keywords, but uses a slower +# algorithm. Use this setting if keyword-related problems occur when +# multiple IMAP clients are updating keywords on the same message. + +IMAP_KEYWORDS=1 + +##NAME: ACL_CAPABILITY:0 +# +# IMAP_ACL=1 enables IMAP ACL extension. Set this option to 0 to +# disable ACL capabilities announce. + +IMAP_ACL=1 + +##NAME: SMAP1_CAPABILITY:0 +# +# EXPERIMENTAL +# +# To enable the experimental "Simple Mail Access Protocol" extensions, +# uncomment the following setting. +# +# SMAP_CAPABILITY=SMAP1 + +##NAME: IMAP_CAPABILITY_ORIG:2 +# +# For use by webadmin + +IMAP_CAPABILITY_ORIG="IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA AUTH=CRAM-MD5 AUTH=CRAM-SHA1 AUTH=CRAM-SHA256 IDLE" + +##NAME: IMAP_PROXY:0 +# +# Enable proxying. See README.proxy + +IMAP_PROXY=0 + +##NAME: PROXY_HOSTNAME:0 +# +# Override value from gethostname() when checking if a proxy connection is +# required. +# +# PROXY_HOSTNAME= + +##NAME: IMAP_PROXY_FOREIGN:0 +# +# Proxying to non-Courier servers. Re-sends the CAPABILITY command after +# logging in to the remote server. May not work with all IMAP clients. + +IMAP_PROXY_FOREIGN=0 + +##NAME: IMAP_IDLE_TIMEOUT:0 +# +# This setting controls how often +# the server polls for changes to the folder, in IDLE mode (in seconds). + +IMAP_IDLE_TIMEOUT=60 + +##NAME: IMAP_MAILBOX_SANITY_CHECK:0 +# +# Sanity check -- make sure home directory and maildir's ownership matches +# the IMAP server's effective uid and gid + +IMAP_MAILBOX_SANITY_CHECK=1 + +##NAME: IMAP_CAPABILITY_TLS:0 +# +# The following setting will advertise SASL PLAIN authentication after +# STARTTLS is established. If you want to allow SASL PLAIN authentication +# with or without TLS then just comment this out, and add AUTH=PLAIN to +# IMAP_CAPABILITY + +IMAP_CAPABILITY_TLS="$IMAP_CAPABILITY AUTH=PLAIN" + +##NAME: IMAP_TLS_ORIG:0 +# +# For use by webadmin + +IMAP_CAPABILITY_TLS_ORIG="$IMAP_CAPABILITY_ORIG AUTH=PLAIN" + +##NAME: IMAP_DISABLETHREADSORT:0 +# +# Set IMAP_DISABLETHREADSORT to disable the THREAD and SORT commands - +# server side sorting and threading. +# +# Those capabilities will still be advertised, but the server will reject +# them. Set this option if you want to disable all the extra load from +# server-side threading and sorting. Not advertising those capabilities +# will simply result in the clients reading the entire folder, and sorting +# it on the client side. That will still put some load on the server. +# advertising these capabilities, but rejecting the commands, will stop this +# silliness. +# + +IMAP_DISABLETHREADSORT=0 + +##NAME: IMAP_CHECK_ALL_FOLDERS:0 +# +# Set IMAP_CHECK_ALL_FOLDERS to 1 if you want the server to check for new +# mail in every folder. Not all IMAP clients use the IMAP's new mail +# indicator, but some do. Normally new mail is checked only in INBOX, +# because it is a comparatively time consuming operation, and it would be +# a complete waste of time unless mail filters are used to deliver +# mail directly to folders. +# +# When IMAP clients are used which support new mail indication, and when +# mail filters are used to sort incoming mail into folders, setting +# IMAP_CHECK_ALL_FOLDERS to 1 will allow IMAP clients to announce new +# mail in folders. Note that this will result in slightly more load on the +# server. +# + +IMAP_CHECK_ALL_FOLDERS=0 + +##NAME: IMAP_OBSOLETE_CLIENT:0 +# +# Set IMAP_OBSOLETE_CLIENT if your IMAP client expects \\NoInferiors to mean +# what \\HasNoChildren really means. + +IMAP_OBSOLETE_CLIENT=0 + +##NAME: IMAP_UMASK:0 +# +# IMAP_UMASK sets the umask of the server process. The value of IMAP_UMASK is +# simply passed to the "umask" command. The default value is 022. +# +# This feature is mostly useful for shared folders, where the file permissions +# of the messages may be important. + +IMAP_UMASK=022 + +##NAME: IMAP_ULIMITD:0 +# +# IMAP_ULIMITD sets the maximum size of the data segment of the server +# process. The value of IMAP_ULIMITD is simply passed to the "ulimit -d" +# command (or ulimit -v). The argument to ulimi sets the upper limit on the +# size of the data segment of the server process, in kilobytes. The default +# value of 65536 sets a very generous limit of 64 megabytes, which should +# be more than plenty for anyone. +# +# This feature is used as an additional safety check that should stop +# any potential denial-of-service attacks that exploit any kind of +# a memory leak to exhaust all the available memory on the server. +# It is theoretically possible that obscenely huge folders will also +# result in the server running out of memory when doing server-side +# sorting (by my calculations you have to have at least 100,000 messages +# in a single folder, for that to happen). + +IMAP_ULIMITD=65536 + +##NAME: IMAP_USELOCKS:0 +# +# Setting IMAP_USELOCKS to 1 will use dot-locking to support concurrent +# multiple access to the same folder. This incurs slight additional +# overhead. Concurrent multiple access will still work without this setting, +# however occasionally a minor race condition may result in an IMAP client +# downloading the same message twice, or a keyword update will fail. +# +# IMAP_USELOCKS=1 is strongly recommended when shared folders are used. + +IMAP_USELOCKS=1 + +##NAME: IMAP_SHAREDINDEXFILE:0 +# +# The index of all accessible folders. Do not change this setting unless +# you know what you're doing. See README.sharedfolders for additional +# information. + +IMAP_SHAREDINDEXFILE=@sysconfdir@/shared/index + +##NAME: IMAP_ENHANCEDIDLE:0 +# +# If Courier was compiled with the File Alteration Monitor, setting +# IMAP_ENHANCEDIDLE to 1 enables enhanced IDLE mode, where multiple +# clients may open the same folder concurrently, and receive updates to +# folder contents in realtime. See the imapd(8) man page for additional +# information. +# +# IMPORTANT: IMAP_USELOCKS *MUST* also be set to 1, and IDLE must be included +# in the IMAP_CAPABILITY list. +# + +IMAP_ENHANCEDIDLE=0 + +##NAME: IMAP_TRASHFOLDERNAME:0 +# +# The name of the magic trash Folder. For MSOE compatibility, +# you can set IMAP_TRASHFOLDERNAME="Deleted Items". +# +# IMPORTANT: If you change this, you must also change IMAP_EMPTYTRASH + +IMAP_TRASHFOLDERNAME=Trash + +##NAME: IMAP_EMPTYTRASH:0 +# +# The following setting is optional, and causes messages from the given +# folder to be automatically deleted after the given number of days. +# IMAP_EMPTYTRASH is a comma-separated list of folder:days. The default +# setting, below, purges 7 day old messages from the Trash folder. +# Another useful setting would be: +# +# IMAP_EMPTYTRASH=Trash:7,Sent:30 +# +# This would also delete messages from the Sent folder (presumably copies +# of sent mail) after 30 days. This is a global setting that is applied to +# every mail account, and is probably useful in a controlled, corporate +# environment. +# +# Important: the purging is controlled by CTIME, not MTIME (the file time +# as shown by ls). It is perfectly ordinary to see stuff in Trash that's +# a year old. That's the file modification time, MTIME, that's displayed. +# This is generally when the message was originally delivered to this +# mailbox. Purging is controlled by a different timestamp, CTIME, which is +# changed when the file is moved to the Trash folder (and at other times too). +# +# You might want to disable this setting in certain situations - it results +# in a stat() of every file in each folder, at login and logout. +# + +IMAP_EMPTYTRASH=Trash:7 + +##NAME: IMAP_MOVE_EXPUNGE_TO_TRASH:0 +# +# Set IMAP_MOVE_EXPUNGE_TO_TRASH to move expunged messages to Trash. This +# effectively allows an undo of message deletion by fishing the deleted +# mail from trash. Trash can be manually expunged as usually, and mail +# will get automatically expunged from Trash according to IMAP_EMPTYTRASH. +# +# NOTE: shared folders are still expunged as usual. Shared folders are +# not affected. +# + +IMAP_MOVE_EXPUNGE_TO_TRASH=0 + +##NAME: IMAP_LOG_DELETIONS:0 +# +# +# Set IMAP_LOG_DELETIONS to log all message deletions to syslog. +# +# IMAP_LOG_DELETIONS=1 + +##NAME: IMAPDEBUGFILE:0 +# +# IMAPDEBUGFILE="imaplog.dat" +# +# Generate diagnostic logging of IMAP commands. +# +# Set this globally, restart the server. Touch this file in an account's +# maildir directory, and Courier-IMAP will append all IMAP commands received +# for new sessions for this account. NOTE: existing IMAP sessions are not +# affected, only new IMAP logins. + + +##NAME: OUTBOX:0 +# +# The next set of options deal with the "Outbox" enhancement. +# Uncomment the following setting to create a special folder, named +# INBOX.Outbox +# +# OUTBOX=.Outbox + +##NAME: SENDMAIL:0 +# +# If OUTBOX is defined, mail can be sent via the IMAP connection by copying +# a message to the INBOX.Outbox folder. For all practical matters, +# INBOX.Outbox looks and behaves just like any other IMAP folder. If this +# folder doesn't exist it must be created by the IMAP mail client, just +# like any other IMAP folder. The kicker: any message copied or moved to +# this folder is will be E-mailed by the Courier-IMAP server, by running +# the SENDMAIL program. Therefore, messages copied or moved to this +# folder must be well-formed RFC-2822 messages, with the recipient list +# specified in the To:, Cc:, and Bcc: headers. Courier-IMAP relies on +# SENDMAIL to read the recipient list from these headers (and delete the Bcc: +# header) by running the command "$SENDMAIL -oi -t -f $SENDER", with the +# message piped on standard input. $SENDER will be the return address +# of the message, which is set by the authentication module. +# +# DO NOT MODIFY SENDMAIL, below, unless you know what you're doing. +# + +SENDMAIL=@SENDMAIL@ + +##NAME: HEADERFROM:0 +# +# For administrative and oversight purposes, the return address, $SENDER +# will also be saved in the X-IMAP-Sender mail header. This header gets +# added to the sent E-mail (but it doesn't get saved in the copy of the +# message that's saved in the folder) +# +# WARNING - By enabling OUTBOX above, *every* IMAP mail client will receive +# the magic OUTBOX treatment. Therefore advance LARTing is in order for +# _all_ of your lusers, until every one of them is aware of this. Otherwise if +# OUTBOX is left at its default setting - a folder name that might be used +# accidentally - some people may be in for a rude surprise. You can redefine +# the name of the magic folder by changing OUTBOX, above. You should do that +# and pick a less-obvious name. Perhaps brand it with your organizational +# name ( OUTBOX=.WidgetsAndSonsOutbox ) + +HEADERFROM=X-IMAP-Sender + +##NAME: ID_FIELDS:0 +# +# Have the server be polite, and identify its version to the client. The client +# must be logged in before the server will identify itself. Additionally, +# the client will mutually supply its own software version, and the server will +# log it. +# +# Although the server's banner message identifies itself, in free-form manner, +# this the ID IMAP extension, for clients to log. +# +# IMAP_ID_FIELDS is the sum of the following values: +# +# 1 - identify the version of the IMAP server +# 2 - identify the operating system (if available) +# 4 - identify the operating system release (if available) +# +# A value of 0 identifies the server software only. +# +# Uncomment this setting to enable the IMAP ID extension. One reason you might +# want to enable it is to log the clients' software version. Enabling this +# setting will mutually log the client's software, in the system logs. +# +# IMAP_ID_FIELDS=0 + +##NAME: OUTBOX_MULTIPLE_SEND:0 +# +# Remove the following comment to allow a COPY of more than one message to +# the Outbox, at a time. +# +# OUTBOX_MULTIPLE_SEND=1 + +##NAME: IMAPDSTART:0 +# +# IMAPDSTART is not used directly. Rather, this is a convenient flag to +# be read by your system startup script in /etc/rc.d, like this: +# +# . @sysconfdir@/imapd +# +# case x$IMAPDSTART in +# x[yY]*) +# @libexecdir@/imapd.rc start +# ;; +# esac +# +# The default setting is going to be NO, so you'll have to manually flip +# it to yes. + +IMAPDSTART=NO + +##NAME: MAILDIRPATH:0 +# +# MAILDIRPATH - directory name of the maildir directory. +# +MAILDIRPATH=Maildir diff --git a/imap/imapd.h b/imap/imapd.h new file mode 100644 index 0000000..d7966a1 --- /dev/null +++ b/imap/imapd.h @@ -0,0 +1,31 @@ +#ifndef imapd_h +#define imapd_h + +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +#define HIERCH '.' /* Hierarchy separator char */ +#define HIERCHS "." /* Hierarchy separator char */ + +#define NEWMSG_FLAG '*' /* Prefixed to mimeinfo to indicate new msg */ + + +#define is_sharedsubdir(dir) \ + (strncmp((dir), SHAREDSUBDIR "/", \ + sizeof (SHAREDSUBDIR "/")-1) == 0) + +#define SUBSCRIBEFILE "courierimapsubscribed" + +extern void check_rights(const char *mailbox, + char *rights_buf); + +#define CHECK_RIGHTSM(mailbox, varname, rights) \ + char varname[sizeof(rights)]; \ + strcpy(varname, rights); \ + check_rights(mailbox, varname); + + +#endif diff --git a/imap/imapd.sgml b/imap/imapd.sgml new file mode 100644 index 0000000..e842c20 --- /dev/null +++ b/imap/imapd.sgml @@ -0,0 +1,495 @@ +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<!-- Copyright 1998 - 2007 Double Precision, Inc. See COPYING for --> +<!-- distribution information. --> +<refentry> + <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info> + + <refmeta> + <refentrytitle>imapd</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo>Double Precision, Inc.</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>imapd</refname> + <refpurpose>The <application moreinfo="none">Courier</application> IMAP server</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis sepchar=" "> + <command moreinfo="none">@libexecdir@/couriertcpd</command> + <arg choice="req" rep="norepeat">couriertcpd options</arg> + <arg choice="req" rep="norepeat">@prefix@/sbin/imaplogin</arg> + <arg choice="opt" rep="repeat"><replaceable>modules</replaceable></arg> + <arg choice="req" rep="norepeat">@prefix@/bin/imapd</arg> + <arg choice="req" rep="norepeat">./Maildir</arg> + </cmdsynopsis> + + <cmdsynopsis sepchar=" "> + <command moreinfo="none">@prefix@/bin/imapd</command> + <arg choice="req" rep="norepeat">./Maildir</arg> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>DESCRIPTION</title> + + <para> +<command moreinfo="none">imapd</command> is the <application moreinfo="none">Courier</application> +IMAP server that provides IMAP access to +Maildir mailboxes. +Normally you don't have to worry about it, as <command moreinfo="none">imapd</command> +runs automatically after receiving a network connection, accompanied by +the appropriate userid and password.</para> + + <para> +<command moreinfo="none">couriertcpd</command> opens network ports that receive incoming +IMAP connections. +After an incoming network connections is established, +<command moreinfo="none">couriertcpd</command> +runs the command specified by its first argument, which is +<command moreinfo="none">imaplogin</command> passing the remaining arguments to +<command moreinfo="none">imaplogin</command>. +<command moreinfo="none">imaplogin</command> reads the IMAP login userid and password, +then runs the modules specified by its remaining options, which +are <application moreinfo="none">Courier</application> +server authentication modules described in the +<ulink url="authlib.html"><citerefentry><refentrytitle>authlib</refentrytitle><manvolnum>7</manvolnum></citerefentry></ulink> +manual page.</para> + + <para> +The last daisy-chained command is +<command moreinfo="none">imapd</command>, which is the actual IMAP server, +which is started from the logged-in account's home directory. +The sole argument to <command moreinfo="none">imapd</command> is the pathname +to the default IMAP mailbox, which is usually +<filename moreinfo="none">./Maildir</filename>. +Some authentication modules are capable of specifying a different +filename, by setting the <envar>MAILDIR</envar> environment variable. +</para> + + <para> +<command moreinfo="none">imapd</command> may also be invoked from the shell prompt, in which +case it issues a <literal moreinfo="none">PREAUTH</literal> response, then changes the +current directory to either its +argument, or the contents of the <envar>MAILDIR</envar> environment +variable, then attempts to talk IMAP on standard input and output.</para> + + <para> +<command moreinfo="none">imapd</command> implements IMAP4REV1, as defined by +<ulink url="http://www.rfc-editor.org/rfc/rfc2060.txt">RFC 2060</ulink>.</para> + + </refsect1> + + <refsect1> + <title>FILES AND ENVIRONMENT VARIABLES</title> + + <variablelist> + <varlistentry> + <term><envar>AUTH*</envar></term> + <listitem> + <simpara> +<command moreinfo="none">imapd</command> examines several environment variables whose +names start with AUTH - these environment variables are set by +<command moreinfo="none">imaplogin</command> and the authentication modules. +Their absence tells +<command moreinfo="none">imapd</command> that it's running from the command line. +</simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term><envar>MAILDIR</envar></term> + <listitem> + <simpara> +<envar>MAILDIR</envar> - if defined, +<command moreinfo="none">imapd</command> changes its directory to the +one specified by this environment variable. +Otherwise <command moreinfo="none">imapd</command> changes +its directory to the one specified on the command line.</simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term><filename moreinfo="none">`<command moreinfo="none">pwd</command>`/.</filename></term> + <listitem> + <simpara> +The current directory is assumed to be the main INBOX +Maildir.</simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term><filename moreinfo="none">`<command moreinfo="none">pwd</command>`/.<replaceable>folder</replaceable></filename></term> + <listitem> + <simpara> +Maildir folders, each one containing their own +tmp, new, cur, etc...</simpara> + </listitem> + </varlistentry> + </variablelist> + + <para> +Other environment variables are initialized from the +<filename moreinfo="none">@sysconfdir@/imapd</filename> and +<filename moreinfo="none">@sysconfdir@/imapd-ssl</filename> configuration files. +These files are loaded into the environment by the system startup script +that runs <command moreinfo="none">couriertcpd</command>.</para> + + <refsect2> + <title>Realtime concurrent folder status updates</title> + + <para> +Setting the <literal moreinfo="none">IMAP_ENHANCEDIDLE</literal> to +<literal moreinfo="none">1</literal> in +<filename moreinfo="none">@sysconfdir@/imapd</filename> enables realtime concurrent folder +status updates. +When relatime folder status updates are enabled all IMAP mail clients +that have the same folder open will be +immediately notified of any changes to the folder's contents.</para> + + <para> +The <application moreinfo="none">Courier</application> IMAP server always allows +more than one mail client to have the +same folder opened. +However, when two or more clients have the same folder opened, the mail +clients may not necessarily know when another client added or removed +messages from the folder. +The base IMAP protocol specification requires IMAP mail clients to explicitly +check for any changes to the folder's contents. +No provisions exists to notify the mail client immediately when the folder's +contents are modified by another mail client.</para> + + <para> +The <literal moreinfo="none">IDLE</literal> extension to the base IMAP protocol provides +a delivery mechanism for notifying mail clients of changes to the mail +folder's contents. Although at this time it's not known to which extent +the <literal moreinfo="none">IDLE</literal> extension is supported by IMAP mail clients, +the <application moreinfo="none">Courier</application> IMAP server fully implements +the <literal moreinfo="none">IDLE</literal> +extension provided that the following requirements are met: +</para> + + <variablelist> + <varlistentry> + <term><application moreinfo="none">Gamin</application> or <application moreinfo="none">FAM</application></term> + <listitem> + <para> +Either <ulink url="http://www.gnome.org/~veillard/gamin/">Gamin</ulink> or +<ulink url="http://oss.sgi.com/projects/fam/">FAM</ulink> +must be properly installed and +configured prior to installing +the <application moreinfo="none">Courier</application> IMAP server.</para> + + <para> +<application moreinfo="none">Gamin</application>/<application moreinfo="none">FAM</application> +is an application library that +provides an interface to the operating system's kernel +that applications can use to be notified when specific files +or directories are changed, and the +<application moreinfo="none">Courier</application> IMAP server leverages this API to +implement realtime concurrent folder status updates. +According to the most recently available documentation, +<application moreinfo="none">Gamin</application> is a Linux-specific library, and +<application moreinfo="none">FAM</application> +builds and runs on Linux and IRIX. +<application moreinfo="none">FAM</application> +should also build on other platforms, but without a supported kernel monitor +FAM will fall back to a polling mode. +At press time, +<application moreinfo="none">FAM</application>'s +web site reports that +<application moreinfo="none">FAM</application> +succesfully builds (in polling mode) on FreeBSD and Solaris.</para> + + <para> +<application moreinfo="none">FAM</application> (but not <application moreinfo="none">Gamin</application>) +also works with NFS filesystems. +On NFS clients <command moreinfo="none">fam</command> transparently forwards file monitoring +requests to a peer <command moreinfo="none">fam</command> process on the NFS server.</para> + + <para> +Installation and configuration of +<application moreinfo="none">Gamin</application> or +<application moreinfo="none">FAM</application> is beyond +the scope of this document. This documentation presumes that Gamin or FAM is +succesfully installed. Use the resources and tools on +<application moreinfo="none">Gamin</application>'s or +<application moreinfo="none">FAM</application>'s web site +for assistance with setting them up. +Systems that use GNOME or KDE desktops already have +<application moreinfo="none">FAM</application> or +<application moreinfo="none">Gamin</application> +installed, as +<application moreinfo="none">FAM</application> or <application moreinfo="none">Gamin</application> +is used by the current versions of both desktops.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><literal moreinfo="none">IDLE</literal> IMAP capability</term> + <listitem> + <para> +<literal moreinfo="none">IDLE</literal> +must be listed in the +<envar>IMAP_CAPABILITY</envar> +setting in the <filename moreinfo="none">@sysconfdir@/imapd</filename> +configuration file.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><envar>IMAP_USELOCKS</envar></term> + <listitem> + <para> +This setting in <filename moreinfo="none">@sysconfdir@/imapd</filename> +must be enabled. +This setting uses dot-lock files to synchronize updates to folder indexes +between multiple IMAP clients that have the same folder opened.</para> + + <para> +This setting is safe to use with NFS, as it does not use actual file locking +calls, and does not require the services of the problematic NFS lock +daemon.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term>An IMAP mail client that fully supports the +<literal moreinfo="none">IDLE</literal> +protocol extension.</term> + <listitem> +<para> +Of course, an IMAP client that supports the <literal moreinfo="none">IDLE</literal> +protocol extension is required. +At press time the status and extent of <literal moreinfo="none">IDLE</literal> support +in most IMAP mail clients is not known.</para> + </listitem> + </varlistentry> + + <varlistentry> + <term><envar>IMAP_ENHANCEDIDLE</envar></term> + <listitem> + <para> +This setting in <filename moreinfo="none">@sysconfdir@/imapd</filename> +actually enables concurrent realtime folder status updates using the +<literal moreinfo="none">IDLE</literal> extension. +Note that it is possible to enable the <literal moreinfo="none">IDLE</literal> extension +even if +<application moreinfo="none">FAM</application> or +<application moreinfo="none">Gamin</application> +is not available, or without +enabling either the <envar>IMAP_USELOCKS</envar> and/or +<envar>IMAP_ENHANCEDIDLE</envar> settings. +The resulting consequences are described are as follows:</para> + + <orderedlist inheritnum="ignore" continuation="restarts"> + <listitem> + <para> +Without <envar>IMAP_USERLOCKS</envar> there exists a small possibility +that multiple mail clients will receive a slightly inconsistent folder index +if both clients try to update the contents of the folder at the same time. +Usually, the worst case result is that some clients will eventually end up +downloading the same message twice from the server, and caching it incorrectly +in the local cache (if the IMAP client caches message contents). +Clearing the local message cache will quickly eliminate any residual +confusion that results from this situation.</para> + </listitem> + <listitem> + <para> +Without <application moreinfo="none">FAM</application> or <application moreinfo="none">Gamin</application>, and +<envar>IMAP_ENHANCEDIDLE</envar> set, the +<application moreinfo="none">Courier</application> IMAP server will +manually check for changes to the folder's contents every 60 seconds, +in IDLE mode (instead of in real time). +</para> + </listitem> + </orderedlist> + </listitem> + </varlistentry> + </variablelist> + </refsect2> + + <refsect2> + <title>Verifying realtime concurrent folder status updates</title> + <para> +Use the following procedure to verify that realtime concurrent folder status +updates are properly working. +It is helpful to be familiar with the IMAP protocol. +If that's not the case, just be extra careful in entering the IMAP protocol +commands. +The following instructions describe the procedure for connecting to the +IMAP server, and manually issuing IMAP protocol commands, as if they +originate from an IMAP client. +The following instructions use "<literal moreinfo="none">C:</literal>" to indicate IMAP +client commands that must be entered, and "<literal moreinfo="none">S:</literal>" to +indicate the expected replies from the server.</para> + + <note> + <para> +The actual replies from the server may differ slightly, due to the actual +server configuration, and other minor factors. +The following examples have long lines wrapped for readability. + +Slight observed differences from the expected replies are normal, but they +should still be substantively the same.</para> +</note> + + <orderedlist inheritnum="ignore" continuation="restarts"> + <listitem> + <para> +Prepare a test account with a couple of messages. +Open two or three terminal windows. +In each window, connect to the IMAP server, and enter IDLE mode: +</para> + <programlisting format="linespecific"> +S:* OK Courier-IMAP ready. Copyright 1998-2002 Double Precision, Inc. + See COPYING for distribution information. +C:a login <replaceable>userid</replaceable> <replaceable>password</replaceable> +S:a OK LOGIN Ok. +C:a SELECT INBOX +S:* FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent) + * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] + Limited + * 2 EXISTS + * 0 RECENT + * OK [UIDVALIDITY 939609418] Ok + a OK [READ-WRITE] Ok +C:a IDLE +S:+ entering ENHANCED idle mode +</programlisting> + <note> +<para> +The default <application moreinfo="none">Courier</application> IMAP server +configuration permits a maximum of four +connections from the same IP address. +It may be necessary to adjust this setting in +<filename moreinfo="none">@sysconfdir@/imapd</filename> +for the duration of this test.</para> + </note> + + </listitem> + + <listitem> +<para> +The last message from the server must be "entering ENHANCED idle mode". +Otherwise, it means that some of the necessary prerequisites have not been +met. +Verify that <application moreinfo="none">FAM</application> +or <application moreinfo="none">Gamin</application> was set up prior to installing +The <application moreinfo="none">Courier</application> IMAP server +(use <citerefentry><refentrytitle>ldd</refentrytitle><manvolnum>1</manvolnum></citerefentry> +to verify that the <command moreinfo="none">imapd</command> executable is linked with +the <filename moreinfo="none">libfam</filename> library), and verify the settings in the +<filename moreinfo="none">@sysconfdir@/imapd</filename>.</para> + </listitem> + + <listitem> +<para> +Open another terminal window, connect to the server, and modify the flags +of one of the messages:</para> + + <programlisting format="linespecific"> +S:* OK Courier-IMAP ready. Copyright 1998-2002 Double Precision, Inc. + See COPYING for distribution information. +C:a login <replaceable>userid</replaceable> <replaceable>password</replaceable> +S:a OK LOGIN Ok. +C:a SELECT INBOX +S:* FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent) + * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] + Limited + * 2 EXISTS + * 0 RECENT + * OK [UIDVALIDITY 939609418] Ok + a OK [READ-WRITE] Ok +C:STORE 1 +FLAGS (\Deleted) +* 1 FETCH (FLAGS (\Deleted)) +a OK STORE completed. +</programlisting> + </listitem> + + <listitem> + <para> +The last command sets the <literal moreinfo="none">\Deleted</literal> flag on the first +message in the folder. +Immediately after entering the last command, +"<literal moreinfo="none">* 1 FETCH (FLAGS (\Deleted))</literal>" should also appear +in all other terminal windows. +On systems where <application moreinfo="none">FAM</application> uses the fall-back polling +mode this response may appear after a brief delay of a few seconds. +The delay should never exceed 15-20 seconds. +</para> + </listitem> + + <listitem> +<para> +Verify that all terminal windows reliably receive folder status updates in +real time by alternatively entering the commands +"<literal moreinfo="none">a STORE 1 -FLAGS (\Deleted)</literal>" +and +"<literal moreinfo="none">a STORE 1 +FLAGS (\Deleted)</literal>", +to toggle the deleted flag on the first message. +Observe that the message is received by all terminal windows quickly, +and reliably.</para> + </listitem> + + <listitem> +<para> +With the <literal moreinfo="none">\Deleted</literal> flag set on the first message, +enter the <command moreinfo="none">EXPUNGE</command> command, which removes the deleted +message from the folder:</para> + +<programlisting format="linespecific"> +C:a EXPUNGE +S:* 1 EXPUNGE + * 2 EXISTS + * 0 RECENT +S:a OK EXPUNGE completed +</programlisting> + + <para> +The lines that begin with the "*" character should also appear in all other +terminal windows (depending on the initial folder state one of the terminal +windows may have a different <literal moreinfo="none">RECENT</literal> message, which is +fine). +</para> + </listitem> + <listitem> +<para> +Use a mail client to create and send a test message to the test account. +As soon as the mail server delivers the message, the following +messages should appear in every terminal window: +</para> + +<programlisting format="linespecific"> +* 3 EXISTS +* 0 RECENT +* 3 FETCH (FLAGS ()) +</programlisting> + +<para> +The numbers in these messages may be different, depending upon the +initial contents of the test mail folder. +One of the terminal windows should have a different <literal moreinfo="none">RECENT</literal> +count, +and one of the terminal windows should include a +<literal moreinfo="none">\Recent</literal> flag in the untagged +<literal moreinfo="none">FLAGS</literal> message. +These difference are acceptable; the important thing is to make sure that +all terminal windows have the same <literal moreinfo="none">EXISTS</literal> message. +</para> + </listitem> + </orderedlist> + </refsect2> + </refsect1> + <refsect1> + <title>SEE ALSO</title> + + <para> +<ulink url="authlib.html"><citerefentry><refentrytitle>authlib</refentrytitle><manvolnum>7</manvolnum></citerefentry></ulink>, + +<ulink url="userdb.html"><citerefentry><refentrytitle>userdb</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink></para> + + </refsect1> + +</refentry> diff --git a/imap/imaplogin.c b/imap/imaplogin.c new file mode 100644 index 0000000..c3dfa7f --- /dev/null +++ b/imap/imaplogin.c @@ -0,0 +1,694 @@ +/* +** Copyright 1998 - 2011 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <time.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/time.h> + +#include "imaptoken.h" +#include "imapwrite.h" +#include "proxy.h" + +#include <courierauth.h> +#include <courierauthdebug.h> +#include "tcpd/spipe.h" +#include "numlib/numlib.h" +#include "tcpd/tlsclient.h" + + +FILE *debugfile=0; +extern void initcapability(); +extern void mainloop(); +extern void imapcapability(); +extern int have_starttls(); +extern int tlsrequired(); +extern int authenticate(const char *, char *, int); +unsigned long header_count=0, body_count=0; /* Dummies */ + +extern unsigned long bytes_received_count; /* counter for received bytes (imaptoken.c) */ +extern unsigned long bytes_sent_count; /* counter for sent bytes (imapwrite.c) */ + +int main_argc; +char **main_argv; +extern time_t start_time; + +static const char *imapd; +static const char *defaultmaildir; + +void rfc2045_error(const char *p) +{ + if (write(2, p, strlen(p)) < 0) + _exit(1); + _exit(0); +} + +extern void cmdfail(const char *, const char *); +extern void cmdsuccess(const char *, const char *); + +static int starttls(const char *tag) +{ + char *argvec[4]; + + char localfd_buf[NUMBUFSIZE+40]; + char buf2[NUMBUFSIZE]; + struct couriertls_info cinfo; + int pipefd[2]; + + if (libmail_streampipe(pipefd)) + { + cmdfail(tag, "libmail_streampipe() failed.\r\n"); + return (-1); + } + + couriertls_init(&cinfo); + fcntl(pipefd[0], F_SETFD, FD_CLOEXEC); + + strcat(strcpy(localfd_buf, "-localfd="), + libmail_str_size_t(pipefd[1], buf2)); + + argvec[0]=localfd_buf; + argvec[1]="-tcpd"; + argvec[2]="-server"; + argvec[3]=NULL; + + cmdsuccess(tag, "Begin SSL/TLS negotiation now.\r\n"); + writeflush(); + + if (couriertls_start(argvec, &cinfo)) + { + close(pipefd[0]); + close(pipefd[1]); + cmdfail(tag, "STARTTLS failed: "); + writes(cinfo.errmsg); + writes("\r\n"); + couriertls_destroy(&cinfo); + return (-1); + } + + couriertls_export_subject_environment(&cinfo); + couriertls_destroy(&cinfo); + + close(pipefd[1]); + close(0); + close(1); + if (dup(pipefd[0]) != 0 || dup(pipefd[0]) != 1) + { + perror("dup"); + exit(1); + } + close(pipefd[0]); + + /* We use select() with a timeout, so use non-blocking filedescs */ + + if (fcntl(0, F_SETFL, O_NONBLOCK) || + fcntl(1, F_SETFL, O_NONBLOCK)) + { + perror("fcntl"); + exit(1); + } + return (0); +} + +struct imapproxyinfo { + const char *tag; + const char *uid; + const char *pwd; +}; + +static int login_imap(int, const char *, void *); + +int login_callback(struct authinfo *ainfo, void *dummy) +{ + int rc; + const char *tag=(const char *)dummy; + char *p; + + p=getenv("IMAP_PROXY"); + + if (p && atoi(p)) + { + if (ainfo->options == NULL || + (p=auth_getoption(ainfo->options, + "mailhost")) == NULL) + { + fprintf(stderr, "WARN: proxy enabled, but no proxy" + " host defined for %s\n", + ainfo->address); + + /* Fallthru to account login */ + + } + else if (ainfo->clearpasswd == NULL) + { + free(p); + fprintf(stderr, "WARN: proxy enabled, but no password" + " for %s\n", ainfo->address); + return -1; + } + else + { + struct proxyinfo pi; + struct imapproxyinfo ipi; + struct servent *se; + int fd; + + se=getservbyname("imap", NULL); + + pi.host=p; + pi.port=se ? ntohs(se->s_port):143; + + ipi.uid=ainfo->address; + ipi.pwd=ainfo->clearpasswd; + ipi.tag=tag; + + pi.connected_func=login_imap; + pi.void_arg=&ipi; + + if ((fd=connect_proxy(&pi)) < 0) + { + free(p); + return -1; + } + free(p); + if (fd > 0) + { + alarm(0); + proxyloop(fd); + exit(0); + } + + /* FALLTHRU */ + } + } + + rc=auth_callback_default(ainfo); + + if (rc == 0) + { + p=malloc(sizeof("OPTIONS=") + strlen(ainfo->options ? + ainfo->options:"")); + + if (p) + { + strcat(strcpy(p, "OPTIONS="), + ainfo->options ? ainfo->options:""); + putenv(p); + + p=malloc(sizeof("IMAPLOGINTAG=")+strlen(tag)); + if (p) + { + strcat(strcpy(p, "IMAPLOGINTAG="), tag); + putenv(p); + + p=malloc(sizeof("AUTHENTICATED=")+ + strlen(ainfo->address)); + if (p) + { + strcat(strcpy(p, "AUTHENTICATED="), + ainfo->address); + putenv(p); + alarm(0); + execl(imapd, imapd, + ainfo->maildir ? + ainfo->maildir:defaultmaildir, + NULL); + fprintf(stderr, "ERR: exec(%s) failed!!\n", + imapd); + } + } + } + } + + return(rc); +} + +int do_imap_command(const char *tag) +{ + struct imaptoken *curtoken=nexttoken(); + char authservice[40]; + +#if SMAP + if (strcmp(tag, "\\SMAP1") == 0) + { + const char *p=getenv("SMAP_CAPABILITY"); + + if (p && *p) + putenv("PROTOCOL=SMAP1"); + else + return -1; + } +#endif + + courier_authdebug_login( 1, "command=%s", curtoken->tokenbuf ); + + if (strcmp(curtoken->tokenbuf, "LOGOUT") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + writes("* BYE Courier-IMAP server shutting down\r\n"); + cmdsuccess(tag, "LOGOUT completed\r\n"); + writeflush(); + fprintf(stderr, "INFO: LOGOUT, ip=[%s], rcvd=%lu, sent=%lu\n", + getenv("TCPREMOTEIP"), bytes_received_count, bytes_sent_count); + exit(0); + } + if (strcmp(curtoken->tokenbuf, "NOOP") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + cmdsuccess(tag, "NOOP completed\r\n"); + return (0); + } + if (strcmp(curtoken->tokenbuf, "CAPABILITY") == 0) + { + if (nexttoken()->tokentype != IT_EOL) return (-1); + + writes("* CAPABILITY "); + imapcapability(); + writes("\r\n"); + cmdsuccess(tag, "CAPABILITY completed\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "STARTTLS") == 0) + { + if (!have_starttls()) return (-1); + if (starttls(tag)) return (-2); + putenv("IMAP_STARTTLS=NO"); + putenv("IMAP_TLS_REQUIRED=0"); + putenv("IMAP_TLS=1"); + + return (0); + } + + if (strcmp(curtoken->tokenbuf, "LOGIN") == 0) + { + struct imaptoken *tok=nexttoken_nouc(); + char *userid; + char *passwd; + const char *p; + int rc; + + if (have_starttls() && tlsrequired()) /* Not yet */ + { + cmdfail(tag, "STARTTLS required\r\n"); + return (0); + } + + switch (tok->tokentype) { + case IT_ATOM: + case IT_NUMBER: + case IT_QUOTED_STRING: + break; + default: + return (-1); + } + + userid=strdup(tok->tokenbuf); + if (!userid) + write_error_exit(0); + tok=nexttoken_nouc_okbracket(); + switch (tok->tokentype) { + case IT_ATOM: + case IT_NUMBER: + case IT_QUOTED_STRING: + break; + default: + free(userid); + return (-1); + } + + passwd=my_strdup(tok->tokenbuf); + + if (nexttoken()->tokentype != IT_EOL) + { + free(userid); + free(passwd); + return (-1); + } + + strcat(strcpy(authservice, "AUTHSERVICE"), + getenv("TCPLOCALPORT")); + + p=getenv(authservice); + + if (!p || !*p) + p="imap"; + + rc=auth_login(p, userid, passwd, login_callback, (void *)tag); + courier_safe_printf("INFO: LOGIN FAILED, user=%s, ip=[%s]", + userid, getenv("TCPREMOTEIP")); + free(userid); + free(passwd); + if (rc > 0) + { + perror("ERR: authentication error"); + writes("* BYE Temporary problem, please try again later\r\n"); + writeflush(); + exit(1); + } + sleep(5); + cmdfail(tag, "Login failed.\r\n"); + return (0); + } + + if (strcmp(curtoken->tokenbuf, "AUTHENTICATE") == 0) + { + char method[32]; + int rc; + + if (have_starttls() && tlsrequired()) /* Not yet */ + { + cmdfail(tag, "STARTTLS required\r\n"); + return (0); + } + rc=authenticate(tag, method, sizeof(method)); + courier_safe_printf("INFO: LOGIN FAILED, method=%s, ip=[%s]", + method, getenv("TCPREMOTEIP")); + if (rc > 0) + { + perror("ERR: authentication error"); + writes("* BYE Temporary problem, please try again later\r\n"); + writeflush(); + exit(1); + } + sleep(5); + cmdfail(tag, "Login failed.\r\n"); + writeflush(); + return (-2); + } + + return (-1); +} + +extern void ignorepunct(); + +int main(int argc, char **argv) +{ + const char *ip; + +#ifdef HAVE_SETVBUF_IOLBF + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); +#endif + + if (argc != 3) + { + printf("* BYE imaplogin expected exactly two arguments.\r\n"); + fflush(stdout); + exit(1); + } + + alarm(60); + imapd=argv[1]; + defaultmaildir=argv[2]; + initcapability(); + + ip=getenv("TCPREMOTEIP"); + if (!ip) + putenv("TCPREMOTEIP=127.0.0.1"); + ip=getenv("TCPREMOTEIP"); + + if (!getenv("TCPLOCALPORT")) + putenv("TCPLOCALPORT=143"); + + time(&start_time); + +#if IMAP_CLIENT_BUGS + + ignorepunct(); + +#endif + + courier_authdebug_login_init(); + + /* We use select() with a timeout, so use non-blocking filedescs */ + + if (fcntl(0, F_SETFL, O_NONBLOCK) || + fcntl(1, F_SETFL, O_NONBLOCK)) + { + perror("fcntl"); + exit(1); + } + + writes("* OK [CAPABILITY "); + imapcapability(); + writes("] Courier-IMAP ready. " + "Copyright 1998-2011 Double Precision, Inc. " + "See COPYING for distribution information.\r\n"); + fprintf(stderr, "DEBUG: Connection, ip=[%s]\n", ip); + writeflush(); + main_argc=argc; + main_argv=argv; + + putenv("PROTOCOL=IMAP"); + + mainloop(); + return (0); +} + +void bye() +{ + exit(0); +} + +static void imap_write_str(const char *c, + void (*cb_func)(const char *, + size_t, + void *), + void *cb_arg) +{ + if (c == NULL) + { + (*cb_func)("NIL", 3, cb_arg); + } + + (*cb_func)("\"", 1, cb_arg); + + while (*c) + { + size_t n; + + for (n=0; c[n]; n++) + if (c[n] == '"' || c[n] == '\\') + break; + + if (n) + { + (*cb_func)(c, n, cb_arg); + + c += n; + } + + if (*c) + { + (*cb_func)("\\", 1, cb_arg); + (*cb_func)(c, 1, cb_arg); + ++c; + } + } + (*cb_func)("\"", 1, cb_arg); +} + +static void imap_login_cmd(struct imapproxyinfo *ipi, + void (*cb_func)(const char *, + size_t, + void *), + void *cb_arg) +{ + (*cb_func)(ipi->tag, strlen(ipi->tag), cb_arg); + (*cb_func)(" LOGIN ", 7, cb_arg); + imap_write_str(ipi->uid, cb_func, cb_arg); + (*cb_func)(" ", 1, cb_arg); + imap_write_str(ipi->pwd, cb_func, cb_arg); + (*cb_func)("\r\n", 2, cb_arg); +} + +static void imap_capability_cmd(struct imapproxyinfo *ipi, + void (*cb_func)(const char *, + size_t, + void *), + void *cb_arg) +{ + (*cb_func)(ipi->tag, strlen(ipi->tag), cb_arg); + (*cb_func)(" CAPABILITY\r\n", 13, cb_arg); +} + +static void cb_cnt(const char *c, size_t l, + void *arg) +{ + *(size_t *)arg += l; +} + +static void cb_cpy(const char *c, size_t l, + void *arg) +{ + char **p=(char **)arg; + + memcpy(*p, c, l); + *p += l; +} + +static char *get_imap_cmd(struct imapproxyinfo *ipi, + void (*cmd)(struct imapproxyinfo *ipi, + void (*cb_func)(const char *, + size_t, + void *), + void *cb_arg)) + +{ + size_t cnt=1; + char *buf; + char *p; + + (*cmd)(ipi, &cb_cnt, &cnt); + + buf=malloc(cnt); + + if (!buf) + { + fprintf(stderr, "CRIT: Out of memory!\n"); + return NULL; + } + + p=buf; + (*cmd)(ipi, &cb_cpy, &p); + *p=0; + return buf; +} + +static int login_imap(int fd, const char *hostname, void *void_arg) +{ + struct imapproxyinfo *ipi=(struct imapproxyinfo *)void_arg; + struct proxybuf pb; + char linebuf[256]; + const char *p; + char *cmd; + + DPRINTF("Proxy connected to %s", hostname); + + memset(&pb, 0, sizeof(pb)); + + if (proxy_readline(fd, &pb, linebuf, sizeof(linebuf), 1) < 0) + return -1; + + DPRINTF("%s: %s", hostname, linebuf); + + if ((p=strtok(linebuf, " \t")) == NULL || + strcmp(p, "*") || + (p=strtok(NULL, " \t")) == NULL || + strcasecmp(p, "OK")) + { + fprintf(stderr, "WARN: Did not receive greeting from %s\n", + hostname); + return -1; + } + + cmd=get_imap_cmd(ipi, imap_login_cmd); + + if (!cmd) + return -1; + + if (proxy_write(fd, hostname, cmd, strlen(cmd))) + { + free(cmd); + return -1; + } + free(cmd); + +#if SMAP + if (strcmp(ipi->tag, "\\SMAP1") == 0) + { + do + { + if (proxy_readline(fd, &pb, linebuf, sizeof(linebuf), + 0) < 0) + return -1; + + DPRINTF("%s: %s", hostname, linebuf); + + } while (linebuf[0] != '+' && linebuf[0] != '-'); + + + if (linebuf[0] != '+') + { + fprintf(stderr, "WARN: Login to %s failed\n", hostname); + return -1; + } + + if (fcntl(1, F_SETFL, 0) < 0 || + (printf("+OK connected to proxy server.\r\n"), + fflush(stdout)) < 0) + return -1; + return (0); + } +#endif + + + for (;;) + { + if (proxy_readline(fd, &pb, linebuf, sizeof(linebuf), 1) < 0) + return -1; + + DPRINTF("%s: %s", hostname, linebuf); + + if ((p=strtok(linebuf, " \t")) == NULL || + strcmp(p, ipi->tag) || + (p=strtok(NULL, " \t")) == NULL) + continue; + + if (strcasecmp(p, "OK")) + { + fprintf(stderr, "WARN: Login to %s failed\n", hostname); + return -1; + } + break; + } + + p=getenv("IMAP_PROXY_FOREIGN"); + + if (p && atoi(p)) + { + cmd=get_imap_cmd(ipi, imap_capability_cmd); + + if (!cmd) + return -1; + + if (proxy_write(fd, hostname, cmd, strlen(cmd))) + { + free(cmd); + return -1; + } + free(cmd); + } + else + { + if (fcntl(1, F_SETFL, 0) < 0 || + (printf("%s OK connected to proxy server.\r\n", + ipi->tag), fflush(stdout)) < 0) + return -1; + } + + return 0; +} + diff --git a/imap/imapscanclient.c b/imap/imapscanclient.c new file mode 100644 index 0000000..da79c70 --- /dev/null +++ b/imap/imapscanclient.c @@ -0,0 +1,1201 @@ +/* +** Copyright 1998 - 2011 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <time.h> +#include <errno.h> +#include "numlib/numlib.h" + +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif + +#include "liblock/config.h" +#include "liblock/liblock.h" +#include "maildir/config.h" +#include "maildir/maildircreate.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirwatch.h" +#include "liblock/mail.h" + +#include "imapscanclient.h" +#include "imaptoken.h" +#include "imapwrite.h" +#include "imapd.h" + +/* +** RFC 2060: "A good value to use for the unique identifier validity value is a +** 32-bit representation of the creation date/time of the mailbox." +** +** Well, Y2038k is on the horizon, time to push to reset button. +** +*/ + +#ifndef IMAP_EPOCH +#define IMAP_EPOCH 1000000000 +#endif + +static int do_imapscan_maildir2(struct imapscaninfo *, const char *, + int, int, struct uidplus_info *); +void imapscanfail(const char *p); + +#if SMAP +extern int smapflag; +#endif + +extern int keywords(); +extern void set_time(const char *tmpname, time_t timestamp); + +static void imapscan_readKeywords(const char *maildir, + struct imapscaninfo *scaninfo); + +void imapscan_init(struct imapscaninfo *i) +{ + memset(i, 0, sizeof(*i)); + + if ((i->keywordList=malloc(sizeof(*i->keywordList))) == NULL) + write_error_exit(0); + + libmail_kwhInit(i->keywordList); +} + +void imapscan_copy(struct imapscaninfo *a, + struct imapscaninfo *b) +{ + imapscan_free(a); + *a=*b; + imapscan_init(b); +} + +struct libmail_kwMessage *imapscan_createKeyword(struct imapscaninfo *a, + unsigned long n) +{ + if (n >= a->nmessages) + return NULL; + + if (a->msgs[n].keywordMsg == NULL) + { + struct libmail_kwMessage *m=libmail_kwmCreate(); + + if (!m) + write_error_exit(0); + + m->u.userNum=n; + a->msgs[n].keywordMsg=m; + } + + return a->msgs[n].keywordMsg; +} + +static int uselocks() +{ + const char *p; + + if ((p=getenv("IMAP_USELOCKS")) != 0 && *p != '1') + return 0; + + return 1; +} + +int imapmaildirlock(struct imapscaninfo *scaninfo, + const char *maildir, + int (*func)(void *), + void *void_arg) +{ + char *newname; + int tryAnyway; + int rc; + + if (!uselocks()) + return (*func)(void_arg); + + if (scaninfo->watcher == NULL && + (scaninfo->watcher=maildirwatch_alloc(maildir)) == NULL) + imapscanfail("maildirwatch"); + + if ((newname=maildir_lock(maildir, scaninfo->watcher, &tryAnyway)) + == NULL) + { + if (!tryAnyway) + return -1; + + imapscanfail("maildir_lock"); + } + + rc=(*func)(void_arg); + + if (newname) + { + unlink(newname); + free(newname); + newname=NULL; + } + return rc; +} + + +struct imapscan_info { + struct imapscaninfo *scaninfo; + const char *dir; + int leavenew; + int ro; + struct uidplus_info *uidplus; +}; + + +static int imapscan_maildir_cb(void *); + +int imapscan_maildir(struct imapscaninfo *scaninfo, + const char *dir, int leavenew, int ro, + struct uidplus_info *uidplus) +{ + struct imapscan_info ii; + + ii.scaninfo=scaninfo; + ii.dir=dir; + ii.leavenew=leavenew; + ii.ro=ro; + ii.uidplus=uidplus; + + return imapmaildirlock(scaninfo, dir, imapscan_maildir_cb, &ii); +} + +int imapscan_maildir_cb(void *void_arg) +{ + struct imapscan_info *ii=(struct imapscan_info *)void_arg; + int rc=do_imapscan_maildir2(ii->scaninfo, + ii->dir, + ii->leavenew, + ii->ro, + ii->uidplus); + + if (rc) + rc= -1; + return rc; +} + +/* This structure is a temporary home for the filenames */ + +struct tempinfo { + struct tempinfo *next; + char *filename; + unsigned long uid; + int found; + int isrecent; + } ; + +static char *imapscan_namedir(const char *dir, const char *new_or_cur) +{ +char *p=malloc(strlen(dir)+strlen(new_or_cur)+2); + + if (!p) write_error_exit(0); + strcat(strcat(strcpy(p, dir), "/"), new_or_cur); + return (p); +} + +static int fnamcmp(const char *a, const char *b) +{ + long ai, bi; + char ca, cb; + + ai = atol(a); + bi = atol(b); + if(ai - bi) + return ai - bi; + + do + { + ca= *a++; + cb= *b++; + + if (ca == ':') ca=0; + if (cb == ':') cb=0; + } while (ca && cb && ca == cb); + + + return ( (int)(unsigned char)ca - (int)(unsigned char)cb); +} + +static int sort_by_filename(struct tempinfo **a, struct tempinfo **b) +{ + return (fnamcmp( (*a)->filename, (*b)->filename)); +} + +static int sort_by_filename_status(struct tempinfo **a, struct tempinfo **b) +{ + if ( (*a)->found && (*b)->found ) + { + if ( (*a)->uid < (*b)->uid ) + return (-1); + if ( (*a)->uid > (*b)->uid ) + return (1); + return (0); /* What the fuck??? */ + } + if ( (*a)->found ) return (-1); + if ( (*b)->found ) return (1); + + return (fnamcmp( (*a)->filename, (*b)->filename)); +} + +/* Binary search on an array of tempinfos which is sorted by filenames */ + +static int search_by_filename(struct tempinfo **a, unsigned s, unsigned *i, + const char *filename) +{ +unsigned lo=0, hi=s, mid; +int rc; + + while (lo < hi) + { + mid=(hi+lo)/2; + rc=fnamcmp( a[mid]->filename, filename); + if (rc < 0) + { + lo=mid+1; + continue; + } + if (rc > 0) + { + hi=mid; + continue; + } + *i=mid; + return (0); + } + return (-1); +} + +void imapscanfail(const char *p) +{ + fprintf(stderr, "ERR: Failed to create cache file: %s (%s)\n", p, + getenv("AUTHENTICATED")); +#if HAVE_STRERROR + fprintf(stderr, "ERR: Error: %s\n", strerror(errno)); +#endif + +#if HAVE_FAM + if (errno == EIO) + { + fprintf(stderr, + "ERR: Check for proper operation and configuration\n" + "ERR: of the File Access Monitor daemon (famd).\n"); + } +#endif +} + +static char *readbuf; +static unsigned readbufsize=0; + +char *readline(unsigned i, FILE *fp) +{ +int c; + + for (;;) + { + if (i >= 10000) + --i; /* DOS check */ + + if (i >= readbufsize) + { + char *p= readbuf ? realloc(readbuf, readbufsize+256): + malloc(readbufsize+256); + + if (!p) write_error_exit(0); + readbuf=p; + readbufsize += 256; + } + + c=getc(fp); + if (c == EOF || c == '\n') + { + readbuf[i]=0; + return (c == EOF ? 0:readbuf); + } + readbuf[i++]=c; + } +} + +static int do_imapscan_maildir2(struct imapscaninfo *scaninfo, + const char *dir, int leavenew, int ro, + struct uidplus_info *uidplus) +{ +char *dbfilepath, *newdbfilepath; +struct tempinfo *tempinfo_list=0, **tempinfo_array=0, *tempinfoptr; +struct tempinfo *newtempinfo_list=0; +unsigned tempinfo_cnt=0, i; +FILE *fp; +char *p, *q; +unsigned long uidv, nextuid; +int version; +struct stat stat_buf; +DIR *dirp; +struct dirent *de; +unsigned long left_unseen=0; +int dowritecache=0; + + if (is_sharedsubdir(dir)) + maildir_shared_sync(dir); + + + /* Step 0 - purge the tmp directory */ + + maildir_purgetmp(dir); + + dbfilepath=malloc(strlen(dir)+sizeof("/" IMAPDB)); + if (!dbfilepath) write_error_exit(0); + strcat(strcpy(dbfilepath, dir), "/" IMAPDB); + + /* + ** We may need to rebuild the UID cache file. Create the new cache + ** file in the tmp subdirectory. + */ + + { + char uniqbuf[80]; + static unsigned tmpuniqcnt=0; + struct maildir_tmpcreate_info createInfo; + int fd; + + maildir_tmpcreate_init(&createInfo); + + createInfo.maildir=dir; + createInfo.hostname=getenv("HOSTNAME"); + sprintf(uniqbuf, "imapuid_%u", tmpuniqcnt++); + createInfo.uniq=uniqbuf; + createInfo.doordie=1; + + if ((fd=maildir_tmpcreate_fd(&createInfo)) < 0) + { + write_error_exit(0); + } + close(fd); + + newdbfilepath=createInfo.tmpname; + createInfo.tmpname=NULL; + maildir_tmpcreate_free(&createInfo); + } + + /* Step 1 - read the cache file */ + + if ((fp=fopen(dbfilepath, "r")) != 0 && + (p=readline(0, fp)) != 0 && + sscanf(p, "%d %lu %lu", &version, &uidv, &nextuid) == 3 && + version == IMAPDBVERSION) + { + while ((p=readline(0, fp)) != 0) + { + char *q=strchr(p, ' '); + unsigned long uid; + struct tempinfo *newtmpl; + + if (!q) continue; + *q++=0; + if (sscanf(p, "%lu", &uid) != 1) continue; + if ((newtmpl=(struct tempinfo *) + malloc(sizeof(struct tempinfo))) == 0 + || (newtmpl->filename=strdup(q)) == 0) + { + unlink(newdbfilepath); + write_error_exit(0); + } + newtmpl->next=tempinfo_list; + tempinfo_list=newtmpl; + newtmpl->uid=uid; + newtmpl->found=0; + newtmpl->isrecent=0; + ++tempinfo_cnt; + } + fclose(fp); + fp=0; + } + else if(!ro) + { + + /* First time - create the cache file */ + + if (fp) fclose(fp); + nextuid=1; + if ((fp=fopen(newdbfilepath, "w")) == 0 || + fstat(fileno(fp), &stat_buf) != 0) + { + if (fp) fclose(fp); + imapscanfail(newdbfilepath); + + /* bk: ignore error */ + unlink(newdbfilepath); + unlink(dbfilepath); + fp = 0; + /* + free(dbfilepath); + unlink(newdbfilepath); + free(newdbfilepath); + return (-1); + */ + } + uidv=stat_buf.st_mtime - IMAP_EPOCH; + dowritecache=1; + } + else + { + nextuid=1; + uidv=time(0) - IMAP_EPOCH; + } + + while (uidplus) + { + struct tempinfo *newtmpl; + + if (uidplus->tmpkeywords) + if (rename(uidplus->tmpkeywords, + uidplus->newkeywords) < 0) + { + struct libmail_kwGeneric g; + + /* + ** Maybe courierimapkeywords needs to be + ** created. + */ + + libmail_kwgInit(&g); + libmail_kwgReadMaildir(&g, dir); + libmail_kwgDestroy(&g); + + rename(uidplus->tmpkeywords, + uidplus->newkeywords); + } + + maildir_movetmpnew(uidplus->tmpfilename, + uidplus->curfilename); + + if (uidplus->mtime) + set_time (uidplus->curfilename, uidplus->mtime); + + if ((newtmpl=(struct tempinfo *) + malloc(sizeof(struct tempinfo))) == 0 + || (newtmpl->filename=strdup(strrchr(uidplus->curfilename, + '/')+1)) == 0) + { + unlink(newdbfilepath); + write_error_exit(0); + } + + if ((p=strrchr(newtmpl->filename, MDIRSEP[0])) != 0) + *p=0; + + newtmpl->next=tempinfo_list; + tempinfo_list=newtmpl; + newtmpl->uid=nextuid; + uidplus->uid=nextuid; + nextuid++; + newtmpl->found=0; + newtmpl->isrecent=0; + ++tempinfo_cnt; + + uidplus=uidplus->next; + dowritecache=1; + } + + if (!fp && (fp=fopen(newdbfilepath, "w")) == 0) + { + imapscanfail(newdbfilepath); + + /* bk: ignore error */ + unlink(newdbfilepath); + unlink(dbfilepath); + /* + free(dbfilepath); + unlink(newdbfilepath); + free(newdbfilepath); + while (tempinfo_list) + { + tempinfoptr=tempinfo_list; + tempinfo_list=tempinfoptr->next; + free(tempinfoptr->filename); + free(tempinfoptr); + } + return (-1); + */ + } + + /* + ** Convert the link list of cached files to an array, then + ** sort it by filename. + */ + + if ((tempinfo_array=(struct tempinfo **)malloc( + (tempinfo_cnt+1)*sizeof(struct tempinfo *))) == 0) + { + unlink(newdbfilepath); + write_error_exit(0); + } + + for (i=0, tempinfoptr=tempinfo_list; tempinfoptr; + tempinfoptr=tempinfoptr->next, i++) + tempinfo_array[i]=tempinfoptr; + + if (tempinfo_cnt) + qsort(tempinfo_array, tempinfo_cnt, + sizeof(tempinfo_array[0]), + ( int (*)(const void *, const void *)) + &sort_by_filename); + + /* Step 2 - read maildir/cur. Search the array. Mark found files. */ + + p=imapscan_namedir(dir, "cur"); + dirp=opendir(p); + free(p); + while (dirp && (de=readdir(dirp)) != 0) + { + int rc; + struct tempinfo *newtmpl; + + if (de->d_name[0] == '.') continue; + + p=my_strdup(de->d_name); + + /* IMAPDB doesn't store the filename flags, so strip them */ + q=strrchr(p, MDIRSEP[0]); + if (q) *q=0; + rc=search_by_filename(tempinfo_array, tempinfo_cnt, &i, p); + if (q) *q=MDIRSEP[0]; + if (rc == 0) + { + tempinfo_array[i]->found=1; + free(tempinfo_array[i]->filename); + tempinfo_array[i]->filename=p; + /* Keep the full filename */ + continue; + } + + if ((newtmpl=(struct tempinfo *) + malloc(sizeof(struct tempinfo))) == 0) + { + unlink(newdbfilepath); + write_error_exit(0); + } + newtmpl->filename=p; + newtmpl->next=newtempinfo_list; + newtmpl->found=0; + newtmpl->isrecent=1; + newtempinfo_list=newtmpl; + dowritecache=1; + } + if (dirp) closedir(dirp); + + /* Step 3 - purge messages that no longer exist in the maildir */ + + free(tempinfo_array); + + for (tempinfo_array= &tempinfo_list; *tempinfo_array; ) + { + if ( (*tempinfo_array)->found ) + { + tempinfo_array= & (*tempinfo_array)->next; + continue; + } + tempinfoptr= *tempinfo_array; + *tempinfo_array=tempinfoptr->next; + free(tempinfoptr->filename); + free(tempinfoptr); + --tempinfo_cnt; + dowritecache=1; + } + + /* Step 4 - add messages in cur that are not in the cache file */ + + while (newtempinfo_list) + { + tempinfoptr=newtempinfo_list; + newtempinfo_list=tempinfoptr->next; + + tempinfoptr->next=tempinfo_list; + tempinfo_list=tempinfoptr; + ++tempinfo_cnt; + } + + /* Step 5 - read maildir/new. */ + + p=imapscan_namedir(dir, "new"); + + if (leavenew) + { + dirp=opendir(p); + while (dirp && (de=readdir(dirp)) != 0) + { + if (de->d_name[0] == '.') continue; + ++left_unseen; + } + if (dirp) closedir(dirp); + } + else + /* + ** Some filesystems keel over if we delete files while + ** reading the directory where the files are. + ** Accomodate them by processing 20 files at a time. + */ + { + char *new_buf[20]; + char *cur_buf[20]; + int keepgoing; + int n; + + do + { + n=0; + keepgoing=0; + + dirp=opendir(p); + while (dirp && (de=readdir(dirp)) != 0) + { + struct tempinfo *newtmpl; + char *newname, *curname; + char *z; + + if (de->d_name[0] == '.') continue; + + z=de->d_name; + + newname=imapscan_namedir(p, z); + curname=malloc(strlen(newname) + +sizeof(MDIRSEP "2,")); + if (!curname) + { + unlink(newdbfilepath); + write_error_exit(0); + } + strcpy(curname, newname); + z=strrchr(curname, '/'); + + memcpy(z-3, "cur", 3); + /* Mother of all hacks */ + if (strchr(z, MDIRSEP[0]) == 0) + strcat(z, MDIRSEP "2,"); + + new_buf[n]=newname; + cur_buf[n]=curname; + + if ((newtmpl=(struct tempinfo *) + malloc(sizeof(struct tempinfo))) == 0) + { + unlink(newdbfilepath); + write_error_exit(0); + } + newtmpl->filename=my_strdup(z+1); + newtmpl->next=tempinfo_list; + tempinfo_list=newtmpl; + ++tempinfo_cnt; + newtmpl->found=0; + newtmpl->isrecent=1; + dowritecache=1; + + if (++n >= sizeof(cur_buf)/ + sizeof(cur_buf[0])) + { + keepgoing=1; + break; + } + } + + if (dirp) closedir(dirp); + + while (n) + { + char *newname, *curname; + + --n; + + newname=new_buf[n]; + curname=cur_buf[n]; + + if (rename(newname, curname)) + { + fprintf(stderr, + "ERR: rename(%s,%s) failed:" + " %s\n", + newname, curname, + strerror(errno)); + keepgoing=0; + /* otherwise we could have infinite loop */ + } + + free(newname); + free(curname); + } + } while (keepgoing); + } + free(p); + + /* + ** Step 6: sort existing messages by UIDs, new messages will + ** sort after all messages with UIDs, and new messages are + ** sorted by filename, so that they end up roughly in the order + ** they were received. + */ + + if ((tempinfo_array=(struct tempinfo **)malloc( + (tempinfo_cnt+1)*sizeof(struct tempinfo *))) == 0) + { + unlink(newdbfilepath); + write_error_exit(0); + } + + for (i=0, tempinfoptr=tempinfo_list; tempinfoptr; + tempinfoptr=tempinfoptr->next, i++) + tempinfo_array[i]=tempinfoptr; + + if (tempinfo_cnt) + qsort(tempinfo_array, tempinfo_cnt, + sizeof(tempinfo_array[0]), + ( int (*)(const void *, const void *)) + &sort_by_filename_status); + + /* Assign new UIDs */ + + for (i=0; i<tempinfo_cnt; i++) + if ( !tempinfo_array[i]->found ) + { + tempinfo_array[i]->uid= nextuid++; + dowritecache=1; + } + + /* bk: ignore if failed to open file */ + if (!ro && dowritecache && fp) + { + int need_fclose; + + /* Step 7 - write out the new cache file */ + + version=IMAPDBVERSION; + fprintf(fp, "%d %lu %lu\n", version, uidv, nextuid); + + for (i=0; i<tempinfo_cnt; i++) + { + q=strrchr(tempinfo_array[i]->filename, MDIRSEP[0]); + if (q) *q=0; + fprintf(fp, "%lu %s\n", tempinfo_array[i]->uid, + tempinfo_array[i]->filename); + if (q) *q=MDIRSEP[0]; + } + + need_fclose=1; + if (fflush(fp) || ferror(fp) || ((need_fclose=0), fclose(fp))) + { + imapscanfail(dir); + if (need_fclose) + fclose(fp); + /* bk: ignore if failed */ + unlink(newdbfilepath); + unlink(dbfilepath); + /* + free(tempinfo_array); + free(dbfilepath); + unlink(newdbfilepath); + free(newdbfilepath); + while (tempinfo_list) + { + tempinfoptr=tempinfo_list; + tempinfo_list=tempinfoptr->next; + free(tempinfoptr->filename); + free(tempinfoptr); + } + return (-1); + */ + } + /* bk */ + else + + rename(newdbfilepath, dbfilepath); + } + else + { + if (fp) + fclose(fp); + unlink(newdbfilepath); + } + free(dbfilepath); + free(newdbfilepath); + + /* Step 8 - create the final scaninfo array */ + + scaninfo->msgs=0; + if (tempinfo_cnt && (scaninfo->msgs=(struct imapscanmessageinfo *) + malloc(tempinfo_cnt * sizeof(*scaninfo->msgs))) == 0) + write_error_exit(0); + scaninfo->nmessages=tempinfo_cnt; + scaninfo->uidv=uidv; + scaninfo->left_unseen=left_unseen; + scaninfo->nextuid=nextuid+left_unseen; + + for (i=0; i<tempinfo_cnt; i++) + { + scaninfo->msgs[i].uid=tempinfo_array[i]->uid; + scaninfo->msgs[i].filename=tempinfo_array[i]->filename; + scaninfo->msgs[i].keywordMsg=NULL; + scaninfo->msgs[i].copiedflag=0; + +#if SMAP + if (smapflag) + scaninfo->msgs[i].recentflag=0; + else +#endif + scaninfo->msgs[i].recentflag= + tempinfo_array[i]->isrecent; + scaninfo->msgs[i].changedflags=0; + + free(tempinfo_array[i]); + } + free(tempinfo_array); + + imapscan_readKeywords(dir, scaninfo); + + + return (0); +} + +static int try_maildir_open(const char *dir, struct imapscanmessageinfo *n) +{ +int fd; +char *filename=maildir_filename(dir, 0, n->filename); +char *p; + + if (!filename) + { + return (0); + } + + p=strrchr(filename, '/')+1; + + if (strcmp(p, n->filename)) + { + n->changedflags=1; + free(n->filename); + n->filename=malloc(strlen(p)+1); + if (!n->filename) write_error_exit(0); + strcpy(n->filename, p); + } + + fd=maildir_semisafeopen(filename, O_RDONLY, 0); + free(filename); + return (fd); +} + +int imapscan_openfile(const char *dir, struct imapscaninfo *i, unsigned j) +{ +struct imapscanmessageinfo *n; + + if (j >= i->nmessages) + { + errno=EINVAL; + return (-1); + } + + n=i->msgs+j; + + return (try_maildir_open(dir, n)); +} + +void imapscan_free(struct imapscaninfo *i) +{ + unsigned n; + + if (i->watcher) + { + maildirwatch_free(i->watcher); + i->watcher=NULL; + } + + if (i->msgs) + { + for (n=0; n<i->nmessages; n++) + { + if (i->msgs[n].filename) + free(i->msgs[n].filename); + + if (i->msgs[n].keywordMsg) + libmail_kwmDestroy(i->msgs[n].keywordMsg); + + } + free(i->msgs); + i->msgs=0; + } + + if (i->keywordList) + { + if (libmail_kwhCheck(i->keywordList) < 0) + write_error_exit("INTERNAL ERROR: Keyword hashtable " + "memory corruption."); + + free(i->keywordList); + i->keywordList=NULL; + } +} + +/* +** Keyword-related stuff See README.imapkeywords.html for more information. +*/ + +extern char *current_mailbox; + +int imapscan_updateKeywords(const char *filename, + struct libmail_kwMessage *newKeyword) +{ + char *tmpname, *newname; + int rc; + + if (maildir_kwSave(current_mailbox, filename, newKeyword, + &tmpname, &newname, 0)) + { + perror("maildir_kwSave"); + return -1; + } + + rc=rename(tmpname, newname); + + if (rc) + { + perror(tmpname); + unlink(tmpname); + } + free(tmpname); + free(newname); + return rc; +} + +static unsigned long hashFilename(const char *fn, struct imapscaninfo *info) +{ + unsigned long hashBucket=0; + + while (*fn && *fn != MDIRSEP[0]) + { + hashBucket=(hashBucket << 1) ^ (hashBucket & 0x8000 ? 0x1301:0) + ^ (unsigned char)*fn++; + } + hashBucket=hashBucket & 0xFFFF; + + return hashBucket % info->nmessages; /* Cannot get here if its zero */ +} + +struct imapscanReadKeywordInfo { + struct maildir_kwReadInfo ri; + + struct imapscaninfo *messages; + int hashedFilenames; +}; + +static struct libmail_kwMessage **findMessageByFilename(const char *filename, + int autocreate, + size_t *indexNum, + void *voidarg) +{ + struct imapscanReadKeywordInfo *info= + (struct imapscanReadKeywordInfo *)voidarg; + + size_t l; + struct imapscanmessageinfo *i; + + struct imapscaninfo *scaninfo=info->messages; + + if (!info->hashedFilenames) + { + unsigned long n; + + for (n=0; n<scaninfo->nmessages; n++) + scaninfo->msgs[n].firstBucket=NULL; + + for (n=0; n<scaninfo->nmessages; n++) + { + unsigned long bucket=hashFilename(scaninfo->msgs[n] + .filename, + scaninfo); + + scaninfo->msgs[n].nextBucket= + scaninfo->msgs[bucket].firstBucket; + + scaninfo->msgs[bucket].firstBucket=scaninfo->msgs+n; + } + info->hashedFilenames=1; + } + + l=strlen(filename); + + for (i= scaninfo->nmessages ? + scaninfo->msgs[hashFilename(filename, scaninfo)] + .firstBucket:NULL; i; i=i->nextBucket) + { + if (strncmp(i->filename, filename, l)) + continue; + + if (i->filename[l] == 0 || + i->filename[l] == MDIRSEP[0]) + break; + } + + if (!i) + return NULL; + + if (indexNum) + *indexNum= i-scaninfo->msgs; + + if (!i->keywordMsg && autocreate) + imapscan_createKeyword(info->messages, i-scaninfo->msgs); + + return &i->keywordMsg; +} + +static size_t getMessageCount(void *voidarg) +{ + struct imapscanReadKeywordInfo *info= + (struct imapscanReadKeywordInfo *)voidarg; + + return info->messages->nmessages; +} + +static const char *getMessageFilename(size_t n, void *voidarg) +{ + struct imapscanReadKeywordInfo *info= + (struct imapscanReadKeywordInfo *)voidarg; + + if (n >= info->messages->nmessages) + return NULL; + + return info->messages->msgs[n].filename; +} + +static void updateKeywords(size_t n, struct libmail_kwMessage *kw, + void *voidarg) +{ + struct imapscanReadKeywordInfo *info= + (struct imapscanReadKeywordInfo *)voidarg; + + if (n >= info->messages->nmessages) + return; + + if (info->messages->msgs[n].keywordMsg) + libmail_kwmDestroy(info->messages->msgs[n].keywordMsg); + + kw->u.userNum=n; + info->messages->msgs[n].keywordMsg=kw; +} + +static struct libmail_kwHashtable * getKeywordHashtable(void *voidarg) +{ + struct imapscanReadKeywordInfo *info= + (struct imapscanReadKeywordInfo *)voidarg; + + return info->messages->keywordList; +} + +static struct libmail_kwMessage **findMessageByIndex(size_t indexNum, + int autocreate, + void *voidarg) +{ + struct imapscanReadKeywordInfo *info= + (struct imapscanReadKeywordInfo *)voidarg; + struct imapscanmessageinfo *i; + + if (indexNum >= info->messages->nmessages) + return NULL; + + i= &info->messages->msgs[indexNum]; + + if (!i->keywordMsg && autocreate) + imapscan_createKeyword(info->messages, indexNum); + + return &i->keywordMsg; +} + +static void initri(struct imapscanReadKeywordInfo *rki) +{ + memset(rki, 0, sizeof(*rki)); + + rki->ri.findMessageByFilename= &findMessageByFilename; + rki->ri.getMessageCount= &getMessageCount; + rki->ri.findMessageByIndex= &findMessageByIndex; + rki->ri.getKeywordHashtable= &getKeywordHashtable; + rki->ri.getMessageFilename= &getMessageFilename; + rki->ri.updateKeywords= &updateKeywords; + rki->ri.voidarg= rki; +} + +void imapscan_readKeywords(const char *maildir, + struct imapscaninfo *scaninfo) +{ + struct imapscanReadKeywordInfo rki; + + initri(&rki); + + do + { + unsigned long i; + + for (i=0; i<scaninfo->nmessages; i++) + if (scaninfo->msgs[i].keywordMsg) + { + libmail_kwmDestroy(scaninfo->msgs[i] + .keywordMsg); + scaninfo->msgs[i].keywordMsg=NULL; + } + + rki.messages=scaninfo; + + if (maildir_kwRead(maildir, &rki.ri) < 0) + write_error_exit(0); + + } while (rki.ri.tryagain); +} + +int imapscan_restoreKeywordSnapshot(FILE *fp, struct imapscaninfo *scaninfo) +{ + struct imapscanReadKeywordInfo rki; + + initri(&rki); + + rki.messages=scaninfo; + return maildir_kwImport(fp, &rki.ri); +} + +int imapscan_saveKeywordSnapshot(FILE *fp, struct imapscaninfo *scaninfo) +{ + struct imapscanReadKeywordInfo rki; + + initri(&rki); + + rki.messages=scaninfo; + return maildir_kwExport(fp, &rki.ri); +} diff --git a/imap/imapscanclient.h b/imap/imapscanclient.h new file mode 100644 index 0000000..2b01180 --- /dev/null +++ b/imap/imapscanclient.h @@ -0,0 +1,94 @@ +#ifndef imapscanclient_h +#define imapscanclient_h + +#include "config.h" +#include "maildir/maildirkeywords.h" + +/* +** Copyright 1998 - 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +/* +** Stuff we want to know about an individual message in the maildir. +*/ + +struct imapscanmessageinfo { + unsigned long uid; /* See RFC 2060 */ + char *filename; + struct libmail_kwMessage *keywordMsg; /* If not NULL - keywords */ + char recentflag; + char changedflags; /* Set by imapscan_open */ + char copiedflag; /* This message was copied to another folder */ + + char storeflag; /* Used by imap_addRemoveKeywords() */ + + /* When reading keywords, hash messages by filename */ + + struct imapscanmessageinfo *firstBucket, *nextBucket; + + } ; + +/* +** Stuff we want to know about the maildir. +*/ + +struct imapscaninfo { + unsigned long nmessages; /* # of messages */ + unsigned long uidv; /* See RFC 2060 */ + unsigned long left_unseen; + unsigned long nextuid; + + struct libmail_kwHashtable *keywordList; /* All defined keywords */ + + struct imapscanmessageinfo *msgs; + struct maildirwatch *watcher; + } ; + +/* +** In imapscan_maildir, move the following msgs to cur. +*/ + +struct uidplus_info { + struct uidplus_info *next; + char *tmpfilename; + char *curfilename; + + char *tmpkeywords; + char *newkeywords; + + unsigned long uid; /* Initialized by imapscan_maildir2 */ + unsigned long old_uid; /* Initialized by do_copy() */ + + time_t mtime; +} ; + + +void imapscan_init(struct imapscaninfo *p); +void imapscan_copy(struct imapscaninfo *a, + struct imapscaninfo *b); + +int imapscan_maildir(struct imapscaninfo *, const char *, int, int, + struct uidplus_info *); +void imapscan_free(struct imapscaninfo *); + +int imapscan_openfile(const char *, struct imapscaninfo *, unsigned); + + +struct libmail_kwMessage *imapscan_createKeyword(struct imapscaninfo *, + unsigned long n); + +int imapscan_updateKeywords(const char *filename, + struct libmail_kwMessage *newKeywords); + +int imapscan_restoreKeywordSnapshot(FILE *, struct imapscaninfo *); +int imapscan_saveKeywordSnapshot(FILE *fp, struct imapscaninfo *); + +int imapmaildirlock(struct imapscaninfo *scaninfo, + const char *maildir, + int (*func)(void *), + void *void_arg); + +char *readline(unsigned, FILE *); + +#endif diff --git a/imap/imaptoken.c b/imap/imaptoken.c new file mode 100644 index 0000000..a2d3cd8 --- /dev/null +++ b/imap/imaptoken.c @@ -0,0 +1,562 @@ +/* +** Copyright 1998 - 2005 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imaptoken.h" +#include "imapwrite.h" +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/time.h> +#include "numlib/numlib.h" +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#ifndef BUFSIZ +#define BUFSIZ 8192 +#endif + + +static struct imaptoken curtoken; +static char readbuf[BUFSIZ]; + +char *imap_readptr=0; +size_t imap_readptrleft=0; +time_t start_time; + +static time_t readtimeout; + +extern FILE *debugfile; + +extern unsigned long header_count, body_count; + +unsigned long bytes_received_count = 0; /* counter for received bytes */ +unsigned long bytes_sent_count; /* counter for sent bytes (imapwrite.c) */ + +extern void bye(); + +void bye_msg(const char *type) +{ + const char *a=getenv("AUTHENTICATED"); + char buf[NUMBUFSIZE]; + const char *tls=getenv("IMAP_TLS"); + + libmail_str_time_t(time(NULL)-start_time, buf); + + if (tls && atoi(tls)) + tls=", starttls=1"; + else + tls=""; + + if (a && *a) + fprintf(stderr, "%s, user=%s, " + "ip=[%s], headers=%lu, body=%lu, rcvd=%lu, sent=%lu, time=%s%s\n", + type, + a, getenv("TCPREMOTEIP"), header_count, body_count, bytes_received_count, bytes_sent_count, + buf, tls); + else + fprintf(stderr, "DEBUG: Disconnected, ip=[%s], time=%s%s\n", + getenv("TCPREMOTEIP"), + buf, tls); +} + +void disconnected() +{ + bye_msg("INFO: DISCONNECTED"); + bye(); +} + +static void disconnected_timeout(void) +{ + writes("* BYE Disconnected for inactivity.\r\n"); + writeflush(); + bye_msg("INFO: TIMEOUT"); + bye(); +} + +int doidle(time_t idletimeout, int extraFd) +{ +fd_set fds; +struct timeval tv; +time_t t; + + time(&t); + if (t >= readtimeout) disconnected_timeout(); + if (imap_readptrleft > 0) return 1; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + if (extraFd > 0) + { + FD_SET(extraFd, &fds); + } + else + { + extraFd=0; + } + + tv.tv_sec=idletimeout; + tv.tv_usec=0; + + select(extraFd + 1, &fds, 0, 0, &tv); + return (FD_ISSET(0, &fds)); +} + +size_t doread(char *buf, size_t bufsiz) +{ +fd_set fds; +struct timeval tv; +time_t t; +int n = 0; + + time(&t); + if (t >= readtimeout) disconnected_timeout(); + + FD_ZERO(&fds); + FD_SET(0, &fds); + tv.tv_sec=readtimeout - t; + tv.tv_usec=0; + + if (select(1, &fds, 0, 0, &tv) <= 0) + { + disconnected_timeout(); + return (0); + } + if (!FD_ISSET(0, &fds) || (n=read(0, buf, bufsiz)) <= 0) + { + if ( n > 0 ) + bytes_received_count += n; /* count received bytes */ + disconnected(); + return (0); + } + if ( n > 0 ) + bytes_received_count += n; /* count received bytes */ + return (n); +} + +void readfill() +{ + imap_readptrleft=doread(readbuf, sizeof(readbuf)); + imap_readptr=readbuf; +} + +#define UNREAD(c) (*--imap_readptr=(c), ++imap_readptrleft) + +void unread(int c) +{ + UNREAD(c); +} + +void read_eol() +{ +int c; + + while ( (c=READ()) != '\n') + ; + curtoken.tokentype=IT_EOL; +} + +void read_timeout(time_t t) +{ +time_t tt; + + time(&tt); + readtimeout=tt+t; +} + +static void alloc_tokenbuf(unsigned l) +{ + if (l >= curtoken.tokenbuf_size) + { + char *p=curtoken.tokenbuf ? realloc(curtoken.tokenbuf, l + 256): + malloc(l + 256); + + if (!p) + write_error_exit("malloc"); + + curtoken.tokenbuf_size = l+256; + curtoken.tokenbuf=p; + } +} + +static char LPAREN_CHAR='('; +static char RPAREN_CHAR=')'; +static char LBRACKET_CHAR='['; +static char RBRACKET_CHAR=']'; + +void ignorepunct() +{ + LPAREN_CHAR=RPAREN_CHAR=LBRACKET_CHAR=RBRACKET_CHAR='\n'; +} + +#if SMAP + +void smap_readline(char *buffer, size_t bufsize) +{ + int c; + + while ((c=READ()) != '\n') + { + if (bufsize > 1) + { + *buffer++ = c; + --bufsize; + } + } + *buffer=0; +} + +#endif + +static struct imaptoken *do_readtoken(int touc) +{ +int c=0; +unsigned l; + +#define appendch(c) alloc_tokenbuf(l+1); curtoken.tokenbuf[l++]=(c); + + if (curtoken.tokentype == IT_ERROR) return (&curtoken); + + do + { + c=READ(); + } while (c == '\r' || c == ' ' || c == '\t'); + + if (c == '\n') + { + UNREAD(c); + curtoken.tokentype=IT_EOL; + return (&curtoken); + } + c=(unsigned char)c; + if (c == LPAREN_CHAR) + { + curtoken.tokentype=IT_LPAREN; + return (&curtoken); + } + + if (c == RPAREN_CHAR) + { + curtoken.tokentype=IT_RPAREN; + return (&curtoken); + } + + if (c == LBRACKET_CHAR) + { + curtoken.tokentype=IT_LBRACKET; + return (&curtoken); + } + + if (c == RBRACKET_CHAR) + { + curtoken.tokentype=IT_RBRACKET; + return (&curtoken); + } + + if (c == '"') + { + l=0; + while ((c=READ()) != '"') + { + if (c == '\\') + c=READ(); + if (c == '\r' || c == '\n') + { + UNREAD(c); + curtoken.tokentype=IT_ERROR; + return (&curtoken); + } + if (l < 8192) + { + appendch(c); + } + } + appendch(0); + curtoken.tokentype=IT_QUOTED_STRING; + return (&curtoken); + } + + if (c == '{') + { + curtoken.tokennum=0; + while ((c=READ()) != '}') + { + if (!isdigit((int)(unsigned char)c)) + { + UNREAD(c); + curtoken.tokentype=IT_ERROR; + return (&curtoken); + } + curtoken.tokennum = curtoken.tokennum*10 + (c-'0'); + } + c=READ(); + if (c == '\r') + { + c=READ(); + } + if (c != '\n') + { + curtoken.tokentype=IT_ERROR; + return (&curtoken); + } + curtoken.tokentype=IT_LITERAL_STRING_START; + return (&curtoken); + } + + l=0; + if (c == '\\') + { + appendch(c); /* Message flag */ + c=READ(); + } + else if (isdigit(c)) + { + curtoken.tokentype=IT_NUMBER; + curtoken.tokennum=0; + do + { + appendch(c); + curtoken.tokennum = curtoken.tokennum*10 + + (c-'0'); + c=READ(); + } while (isdigit( (int)(unsigned char)c)); + + /* Could be stuff like mime.spec, so continue reading. */ + } + + while (c != '\r' && c != '\n' + && !isspace((int)(unsigned char)c) + && c != '\\' && c != '"' && c != LPAREN_CHAR && c != RPAREN_CHAR + && c != '{' && c != '}' && c != LBRACKET_CHAR && c != RBRACKET_CHAR) + { + curtoken.tokentype=IT_ATOM; + if (l < IT_MAX_ATOM_SIZE) + { + if (touc) + c=toupper(c); + appendch(c); + } + else + { + write_error_exit("max atom size too small"); + } + c=READ(); + } + if (l == 0) + { + curtoken.tokentype=IT_ERROR; + return (&curtoken); + } + appendch(0); + UNREAD(c); + + if (strcmp(curtoken.tokenbuf, "NIL") == 0) + curtoken.tokentype=IT_NIL; + return (&curtoken); +} + +static struct imaptoken *readtoken(int touc) +{ +struct imaptoken *tok=do_readtoken(touc); + + if (tok->tokentype == IT_LITERAL_STRING_START) + { + unsigned long nbytes=curtoken.tokennum; + + if (nbytes > 8192) + { + writes("* NO [ALERT] IMAP command too long.\r\n"); + tok->tokentype=IT_ERROR; + } + else + { + unsigned long i; + + writes("+ OK\r\n"); + writeflush(); + alloc_tokenbuf(nbytes+1); + for (i=0; i<nbytes; i++) + tok->tokenbuf[i]= READ(); + tok->tokenbuf[i]=0; + tok->tokentype=IT_QUOTED_STRING; + } + } + + if (debugfile) + { + char *p=0; + + fprintf(debugfile, "READ: "); + switch (tok->tokentype) { + case IT_ATOM: + p=curtoken.tokenbuf; fprintf(debugfile, "ATOM"); break; + case IT_NUMBER: + p=curtoken.tokenbuf; fprintf(debugfile, "NUMBER"); break; + case IT_QUOTED_STRING: + p=curtoken.tokenbuf; fprintf(debugfile, "QUOTED_STRING"); break; + case IT_LPAREN: + fprintf(debugfile, "LPAREN"); break; + case IT_RPAREN: + fprintf(debugfile, "RPAREN"); break; + case IT_NIL: + fprintf(debugfile, "NIL"); break; + case IT_ERROR: + fprintf(debugfile, "ERROR"); break; + case IT_EOL: + fprintf(debugfile, "EOL"); break; + case IT_LBRACKET: + fprintf(debugfile, "LBRACKET"); break; + case IT_RBRACKET: + fprintf(debugfile, "RBRACKET"); break; + } + + if (p) + fprintf(debugfile, ": %s", p); + fprintf(debugfile, "\n"); + fflush(debugfile); + } + return (tok); +} + +struct imaptoken *nexttoken(void) +{ + return (readtoken(1)); +} + +struct imaptoken *nexttoken_nouc(void) +{ + return (readtoken(0)); +} + +/* RFC 2060 sucks */ + +struct imaptoken *nexttoken_okbracket(void) +{ + struct imaptoken *t; + + LBRACKET_CHAR=RBRACKET_CHAR='\n'; + + t=nexttoken(); + + LBRACKET_CHAR='['; + RBRACKET_CHAR=']'; + return (t); +} + +struct imaptoken *nexttoken_nouc_okbracket(void) +{ + struct imaptoken *t; + + LBRACKET_CHAR=RBRACKET_CHAR='\n'; + + t=nexttoken_nouc(); + + LBRACKET_CHAR='['; + RBRACKET_CHAR=']'; + return (t); +} + +struct imaptoken *currenttoken(void) +{ + return (&curtoken); +} + +struct imaptoken *nexttoken_noparseliteral(void) +{ + return (do_readtoken(0)); +} + +/* Read an IMAP literal string (or a portion of) */ + +void read_string(char **ptr, unsigned long *left, unsigned long cnt) +{ + if (imap_readptrleft == 0) + { + /* Keep reading until we fill the buffer or until we've + ** read the entire string. + */ + + read_timeout(SOCKET_TIMEOUT); + imap_readptr=readbuf; + while (imap_readptrleft < sizeof(readbuf) && imap_readptrleft < cnt) + imap_readptrleft += doread(readbuf+imap_readptrleft, + sizeof(readbuf)-imap_readptrleft); + } + + if (cnt < imap_readptrleft) /* Can satisfy fully from buffer */ + { + *ptr=imap_readptr; + *left=cnt; + imap_readptr += cnt; + imap_readptrleft -= cnt; + return; + } + + *ptr=imap_readptr; + *left=imap_readptrleft; + imap_readptrleft=0; + return; +} + +char *my_strdup(const char *s) +{ +char *q=strdup(s); + + if (!q) write_error_exit("malloc"); + return (q); +} + +int ismsgset(struct imaptoken *tok) + /* See if this token is a syntactically valid message set */ +{ + + if (tok->tokentype == IT_NUMBER) return (1); + if (tok->tokentype != IT_ATOM) return (0); + + return ismsgset_str(tok->tokenbuf); +} + +int ismsgset_str(const char *p) +{ + while (isdigit((int)(unsigned char)*p) || *p == '*') + { + if (*p == '0') return (0); + if (*p == '*') + ++p; + else + do + { + ++p; + } while (isdigit((int)(unsigned char)*p)); + + if (*p == ':') + { + ++p; + if (!isdigit((int)(unsigned char)*p) && + *p != '*') + return (0); + if (*p == '0') return (0); + if (*p == '*') + ++p; + else + do + { + ++p; + } while (isdigit((int)(unsigned char)*p)); + } + if (*p != ',') break; + ++p; + } + if (*p) return (0); + return (1); +} + diff --git a/imap/imaptoken.h b/imap/imaptoken.h new file mode 100644 index 0000000..6ca1b2d --- /dev/null +++ b/imap/imaptoken.h @@ -0,0 +1,88 @@ +#ifndef imaptoken_h +#define imaptoken_h + +/* +** Copyright 1998 - 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <time.h> + + +struct imaptoken { + short tokentype; + unsigned long tokennum; + char *tokenbuf; + size_t tokenbuf_size; + } ; + +#define IT_ATOM 0 +#define IT_NUMBER 1 +#define IT_QUOTED_STRING 2 +#define IT_LITERAL_STRING_START 3 +#define IT_LPAREN 4 +#define IT_RPAREN 5 +#define IT_NIL 6 +#define IT_ERROR 7 +#define IT_EOL 8 +#define IT_LBRACKET 9 +#define IT_RBRACKET 10 + +struct imaptoken *nexttoken(void); +struct imaptoken *currenttoken(void); +struct imaptoken *nexttoken_nouc(void); +struct imaptoken *nexttoken_noparseliteral(void); +struct imaptoken *nexttoken_okbracket(void); +struct imaptoken *nexttoken_nouc_okbracket(void); + +int ismsgset(struct imaptoken *); + /* See if this token is a syntactically valid message set */ +int ismsgset_str(const char *); + /* Ditto */ + +void read_timeout(time_t); +void read_eol(); +void unread(int); + +extern size_t doread(char *buf, size_t bufsiz); +extern char *imap_readptr; +extern size_t imap_readptrleft; +extern void readfill(); + +#define READ() ( imap_readptrleft ? \ + (--imap_readptrleft, (int)(unsigned char)*imap_readptr++): \ + (readfill(), --imap_readptrleft, (int)(unsigned char)*imap_readptr++)) + +void read_string(char **, unsigned long *, unsigned long); + +/* Flags */ + +struct imapflags { + char seen; + char answered; + char deleted; + char flagged; + char drafts; + char recent; + } ; + +struct imapkeywords { + struct imapflags flags; + + struct imapkeyword *first_keyword, *last_keyword; +}; + +/* ATOMS have the following maximum length */ + +#define IT_MAX_ATOM_SIZE 16384 + +char *my_strdup(const char *s); + + +/* SMAP */ + +void smap_readline(char *buffer, size_t bufsize); + +#endif diff --git a/imap/imapwrite.c b/imap/imapwrite.c new file mode 100644 index 0000000..6167fa8 --- /dev/null +++ b/imap/imapwrite.c @@ -0,0 +1,225 @@ +/* +** Copyright 1998 - 2006 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <sys/types.h> +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + +#include "imapwrite.h" + + +static char outbuf[BUFSIZ]; /* Ye olde output buffer */ +static size_t outbuf_cnt=0; /* How much stuff's in ye olde output buffer */ + +extern FILE *debugfile; + +extern unsigned long bytes_sent_count; /* counter for sent bytes (imapwrite.c) */ + +extern void disconnected(); + +void writeflush() +{ +const char *p=outbuf; +unsigned s=outbuf_cnt; +time_t t, tend; +fd_set fds; +struct timeval tv; +int n; + + if (s == 0) return; + time(&t); + tend=t+SOCKET_TIMEOUT; + if (debugfile) + { + fprintf(debugfile, "WRITE: "); + if (fwrite(p, 1, s, debugfile) == 1) + fprintf(debugfile, "\n"); + fflush(debugfile); + } + while (t < tend) + { + FD_ZERO(&fds); + FD_SET(1, &fds); + tv.tv_sec=tend-t; + tv.tv_usec=0; + /* BUG: if client closes connection BEFORE we flush it, select "stucks" + * until timeout. To workaround this, we should "write" first, and then + * if we get EPIPE connection is already closed. Othervise, try select + */ + if ((n=write(1, p, s)) <= 0) + { + if (errno == EPIPE || + select(2, 0, &fds, 0, &tv) <= 0 || + !FD_ISSET(1, &fds) || + (n=write(1, p, s)) <= 0) + { + disconnected(); + return; + } + } + bytes_sent_count += n; + p += n; + s -= n; + if (s == 0) break; + time(&t); + } + if (s) disconnected(); + outbuf_cnt=0; +} + +void writemem(const char *s, size_t l) +{ +size_t n; + + while (l) + { + n=sizeof(outbuf) - outbuf_cnt; + + if (n >= l) + { + memcpy(outbuf+outbuf_cnt, s, l); + outbuf_cnt += l; + break; + } + if (n == 0) + { + writeflush(); + continue; + } + if (n > l) n=l; + memcpy(outbuf+outbuf_cnt, s, n); + outbuf_cnt += n; + l -= n; + s += n; + } +} + +void writes(const char *s) +{ + writemem(s, strlen(s)); +} + +void writen(unsigned long n) +{ +char buf[40]; + + sprintf(buf, "%lu", n); + writemem(buf, strlen(buf)); +} + +void writeqs(const char *s) +{ +size_t i=strlen(s), j; + + while (i) + { + for (j=0; j<i; j++) + { + if ( s[j] == '"' || s[j] == '\\') + { + writemem(s, j); + writemem("\\", 1); + writemem(s+j, 1); + ++j; + s += j; + i -= j; + j=0; + break; + } +#if 0 + if (s[j] == '&') + { + writemem(s, j); + writemem("&-", 2); + ++j; + s += j; + i -= j; + j=0; + break; + } + + if (s[j] < ' ' || s[j] >= 0x7F) + { + char *q; + + writemem(s, j); + ++j; + s += j; + i -= j; + for (j=0; j<i; j++) + if (s[j] >= ' ' && s[j] < 0x7F) + break; + q=imap_utf7_encode(s, j); + if (!q) write_error_exit(0); + writemem("&", 1); + writes(q); + writemem("-", 1); + s += j; + i -= j; + j=0; + break; + } +#endif + } + writemem(s, j); + s += j; + i -= j; + } +} + +void write_error_exit(const char *funcname) +{ + outbuf_cnt=0; + writes("* BYE [ALERT] Fatal error: "); + if (funcname && *funcname) + { + writes(funcname); + writes(": "); + } + + if (errno) + { +#if HAVE_STRERROR + const char *p; + + p=strerror(errno); +#else + char p[40]; + + sprintf(p, "Error %d", (int)errno); +#endif + + + writes(p); + } + writes("\r\n"); + writeflush(); + + if (funcname && *funcname) + { + fprintf(stderr, "ERR: %s: %s\n", getenv("AUTHENTICATED"), + funcname); + fflush(stderr); + } + _exit(1); +} diff --git a/imap/imapwrite.h b/imap/imapwrite.h new file mode 100644 index 0000000..6fa677c --- /dev/null +++ b/imap/imapwrite.h @@ -0,0 +1,16 @@ +#ifndef imapwrite_h +#define imapwrite_h + +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +void writeflush(); +void writemem(const char *, size_t); +void writes(const char *); +void writeqs(const char *); +void writen(unsigned long n); +void write_error_exit(const char *); +#endif diff --git a/imap/kwtest.c b/imap/kwtest.c new file mode 100644 index 0000000..3813cad --- /dev/null +++ b/imap/kwtest.c @@ -0,0 +1,97 @@ +/* +** Copyright 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include "keywords.h" + +static struct libmail_kwHashtable h; + +int smapflag=0; + +static int count_flags(struct libmail_keywordEntry *dummy1, void *dummy) +{ + ++*(size_t *)dummy; + + return 0; +} + +static struct libmail_kwMessage *msgs[3]; +static const char * const flags[]={"apple", "banana", "pear", "grape"}; + + +static int dump() +{ + size_t cnt=0; + + if (libmail_kwEnumerate(&h, &count_flags, &cnt)) + return -1; + + printf("%d flags\n", (int)cnt); + + for (cnt=0; cnt<sizeof(msgs)/sizeof(msgs[0]); cnt++) + { + struct libmail_kwMessageEntry *e; + + printf("%d:", (int)cnt); + + for (e=msgs[cnt]->firstEntry; e; e=e->next) + printf(" %s", keywordName(e->libmail_keywordEntryPtr)); + printf("\n"); + } + return 0; + +} + +int main() +{ + size_t i; + + libmail_kwhInit(&h); + + for (i=0; i<sizeof(msgs)/sizeof(msgs[0]); i++) + { + if ((msgs[i]=libmail_kwmCreate()) == NULL) + { + perror("malloc"); + exit(1); + } + + msgs[i]->u.userNum=i; + } + + if (libmail_kwmSetName(&h, msgs[0], flags[0]) >= 0 && + libmail_kwmSetName(&h, msgs[1], flags[1]) >= 0 && + libmail_kwmSetName(&h, msgs[2], flags[2]) >= 0 && + libmail_kwmSetName(&h, msgs[0], flags[0]) >= 0 && + libmail_kwmSetName(&h, msgs[0], flags[1]) >= 0 && + libmail_kwmSetName(&h, msgs[1], flags[2]) >= 0 && + libmail_kwmSetName(&h, msgs[2], flags[3]) >= 0) + { + + if (dump() == 0) + { + libmail_kwmClearName(msgs[2], flags[3]); + libmail_kwmClearName(msgs[2], flags[3]); + libmail_kwmClearName(msgs[0], flags[1]); + + if (dump() == 0) + exit(0); + } + + } + + perror("ERROR"); + exit(1); + return 0; +} diff --git a/imap/kwtest.txt b/imap/kwtest.txt new file mode 100644 index 0000000..d8c8025 --- /dev/null +++ b/imap/kwtest.txt @@ -0,0 +1,8 @@ +4 flags +0: apple banana +1: banana pear +2: grape pear +3 flags +0: apple +1: banana pear +2: pear diff --git a/imap/mailboxlist.c b/imap/mailboxlist.c new file mode 100644 index 0000000..a204ebf --- /dev/null +++ b/imap/mailboxlist.c @@ -0,0 +1,1064 @@ +/* +** Copyright 1998 - 2007 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include "imaptoken.h" +#include "imapwrite.h" +#include "imapscanclient.h" + +#include "mysignal.h" +#include "imapd.h" +#include "fetchinfo.h" +#include "searchinfo.h" +#include "storeinfo.h" +#include "mailboxlist.h" + +#include "maildir/config.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildiraclt.h" +#include "maildir/maildirnewshared.h" +#include "maildir/maildirinfo.h" +#include "unicode/unicode.h" +#include "courierauth.h" + + +static const char hierchs[]={HIERCH, 0}; + +extern char *decode_valid_mailbox(const char *, int); +extern dev_t homedir_dev; +extern ino_t homedir_ino; +/* + LIST MAILBOXES +*/ + +static int do_mailbox_list(int do_lsub, char *qq, int isnullname, + int (*callback_func)(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg), + void *void_arg); + +static int shared_index_err_reported=0; + +const char *maildir_shared_index_file() +{ + static char *filenamep=NULL; + + if (filenamep == NULL) + { + const char *p=getenv("IMAP_SHAREDINDEXFILE"); + + if (p && *p) + { + const char *q=auth_getoptionenv("sharedgroup"); + + if (!q) q=""; + + filenamep=malloc(strlen(p)+strlen(q)+1); + + if (!filenamep) + write_error_exit(0); + + strcat(strcpy(filenamep, p), q); + } + } + + if (filenamep && !shared_index_err_reported) /* Bitch just once */ + { + struct stat stat_buf; + + shared_index_err_reported=1; + if (stat(filenamep, &stat_buf)) + { + fprintf(stderr, "ERR: "); + perror(filenamep); + } + } + + return filenamep; +} + +/* +** IMAP sucks. Here's why. +*/ + + +int mailbox_scan(const char *reference, const char *name, + int list_options, + int (*callback_func)(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg), + void *void_arg) +{ + char *pattern, *p; + int nullname= *name == 0; + int rc; + + pattern=malloc(strlen(reference)+strlen(name)+2); + + strcpy(pattern, reference); + + p=strrchr(pattern, HIERCH); + if (p && p[1] == 0) *p=0; /* Strip trailing . for now */ + if (*pattern) + { + struct maildir_info mi; + + if (maildir_info_imap_find(&mi, pattern, + getenv("AUTHENTICATED"))) + { + free(pattern); + return (0); /* Invalid reference */ + } + maildir_info_destroy(&mi); + } + + /* Combine reference and name. */ + if (*pattern && *name) + strcat(pattern, hierchs); + strcat(pattern, name); + + if (name && *name) + { + char *s=strrchr(pattern, HIERCH); + + if (s && s[1] == 0) *s=0; /* strip trailing . */ + + } + + /* Now, do the list */ + + rc=do_mailbox_list(list_options, pattern, nullname, + callback_func, void_arg); + free(pattern); + return (rc); +} + +static int match_mailbox(char *, char *, int flags); +static void match_mailbox_prep(char *); + +/* Check if a folder has any new messages */ + +static int hasnewmsgs2(const char *dir) +{ +DIR *dirp=opendir(dir); +struct dirent *de; + + while (dirp && (de=readdir(dirp)) != 0) + { + char *p; + + if (de->d_name[0] == '.') continue; + p=strrchr(de->d_name, MDIRSEP[0]); + if (p == 0 || strncmp(p, MDIRSEP "2,", 3) || + strchr(p, 'S') == 0) + { + closedir(dirp); + return (1); + } + } + if (dirp) closedir(dirp); + return (0); +} + +static int hasnewmsgs(const char *folder) +{ +char *dir=decode_valid_mailbox(folder, 0); +char *subdir; + + if (!dir) return (0); + + if (is_sharedsubdir(dir)) + maildir_shared_sync(dir); + + subdir=malloc(strlen(dir)+sizeof("/cur")); + if (!subdir) write_error_exit(0); + strcat(strcpy(subdir, dir), "/new"); + if (hasnewmsgs2(subdir)) + { + free(subdir); + free(dir); + return (1); + } + + strcat(strcpy(subdir, dir), "/cur"); + if (hasnewmsgs2(subdir)) + { + free(subdir); + free(dir); + return (1); + } + + free(subdir); + free(dir); + return (0); +} + +/* Each folder is listed with the \Noinferiors tag. Then, for every subfolder +** we've seen, we need to output a listing for all the higher-level hierarchies +** with a \Noselect tag. Therefore, we need to keep track of all the +** hierarchies we've seen so far. +*/ + +struct hierlist { + struct hierlist *next; + int flag; + char *hier; + } ; + +static int add_hier(struct hierlist **h, const char *s) +{ +struct hierlist *p; + + for (p= *h; p; p=p->next) + if (strcmp(p->hier, s) == 0) return (1); + /* Seen this one already */ + + if ((p=(struct hierlist *) + malloc( sizeof(struct hierlist)+1+strlen(s))) == 0) + /* HACK!!!! */ + write_error_exit(0); + p->flag=0; + p->hier=(char *)(p+1); + strcpy(p->hier, s); + p->next= *h; + *h=p; + return (0); +} + +static struct hierlist *search_hier(struct hierlist *h, const char *s) +{ +struct hierlist *p; + + for (p= h; p; p=p->next) + if (strcmp(p->hier, s) == 0) return (p); + return (0); +} + +static void hier_entry(char *folder, + struct hierlist **hierarchies); + +static int has_hier_entry(char *folder, + struct hierlist **hierarchies); + +static void folder_entry(char *folder, char *pattern, + int list_options, + struct hierlist **folders, + struct hierlist **hierarchies) +{ + size_t i; + size_t folder_l=strlen(folder); + + int need_add_hier; + int need_add_folders; + + match_mailbox_prep(folder); + + /* Optimize away folders we don't care about */ + + for (i=0; pattern[i]; i++) + { + if ((!(list_options & LIST_CHECK1FOLDER)) && + (pattern[i] == '%' || pattern[i] == '*')) + { + while (i) + { + if (pattern[i] == HIERCH) + break; + --i; + } + break; + } + } + + if (folder_l <= i) + { + if (memcmp(folder, pattern, folder_l)) + return; + + if (folder_l != i && pattern[folder_l] != HIERCH) + return; + } + else if (i) + { + if (memcmp(folder, pattern, i)) + return; + if (folder[i] != HIERCH) + return; + } + + need_add_folders=0; + + if (match_mailbox(folder, pattern, list_options) == 0) + need_add_folders=1; + + need_add_hier=0; + if (!has_hier_entry(folder, hierarchies)) + need_add_hier=1; + + if (!need_add_folders && !need_add_hier) + return; /* Nothing to do */ + + { + CHECK_RIGHTSM(folder, have_rights, ACL_LOOKUP); + + if (!have_rights[0]) + return; + } + + if (need_add_folders) + (void) add_hier(folders, folder); + + if (need_add_hier) + hier_entry(folder, hierarchies); +} + +static void hier_entry(char *folder, + struct hierlist **hierarchies) +{ + unsigned i; + + for (i=0; folder[i]; i++) + { + if (folder[i] != HIERCH) continue; + folder[i]=0; + (void)add_hier(hierarchies, folder); + folder[i]=HIERCH; + } +} + +static int has_hier_entry(char *folder, + struct hierlist **hierarchies) +{ + unsigned i; + + for (i=0; folder[i]; i++) + { + if (folder[i] != HIERCH) continue; + folder[i]=0; + if (!search_hier(*hierarchies, folder)) + { + folder[i]=HIERCH; + return (0); + } + folder[i]=HIERCH; + } + return (1); +} + +struct list_sharable_info { + char *pattern; + struct hierlist **folders, **hierarchies; + int flags; + int (*callback_func)(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg); + void *cb_arg; + } ; + +static void list_sharable(const char *n, + void *voidp) +{ +struct list_sharable_info *ip=(struct list_sharable_info *)voidp; +char *p=malloc(strlen(n)+sizeof("shared.")); + + if (!p) write_error_exit(0); + + strcat(strcpy(p, "shared."), n); + + folder_entry(p, ip->pattern, ip->flags, + ip->folders, ip->hierarchies); + + free(p); +} + +static void list_subscribed(char *hier, + int flags, + struct hierlist **folders, + struct hierlist **hierarchies) +{ +char buf[BUFSIZ]; +FILE *fp; + + fp=fopen(SUBSCRIBEFILE, "r"); + if (fp) + { + while (fgets(buf, sizeof(buf), fp) != 0) + { + char *q=strchr(buf, '\n'); + + if (q) *q=0; + + if (*hier == '#') + { + if (*buf != '#') + continue; + } + else + { + if (*buf == '#') + continue; + } + + folder_entry(buf, hier, flags, + folders, hierarchies); + } + fclose(fp); + } +} + +static void maildir_scan(const char *inbox_dir, + const char *inbox_name, + struct list_sharable_info *shared_info) +{ + DIR *dirp; + struct dirent *de; + + /* Scan maildir, looking for .subdirectories */ + + dirp=opendir(inbox_dir && inbox_dir ? inbox_dir:"."); + while (dirp && (de=readdir(dirp)) != 0) + { + char *p; + + if (de->d_name[0] != '.' || + strcmp(de->d_name, "..") == 0) + continue; + + if ((p=malloc(strlen(de->d_name)+strlen(inbox_name)+10)) == 0) + /* A bit too much, that's OK */ + write_error_exit(0); + + strcpy(p, inbox_name); + + if (strcmp(de->d_name, ".")) + strcat(p, de->d_name); + + folder_entry(p, shared_info->pattern, shared_info->flags, + shared_info->folders, + shared_info->hierarchies); + free(p); + } + + if (dirp) closedir(dirp); +} + +/* List the #shared hierarchy */ + +struct list_newshared_info { + const char *acc_pfix; + const char *skipped_pattern; + struct list_sharable_info *shared_info; + struct maildir_shindex_cache *parentCache; + int dorecurse; +}; + +static int list_newshared_cb(struct maildir_newshared_enum_cb *cb); +static int list_newshared_skipcb(struct maildir_newshared_enum_cb *cb); +static int list_newshared_skiplevel(struct maildir_newshared_enum_cb *cb); + +static int list_newshared_shortcut(const char *skipped_pattern, + struct list_sharable_info *shared_info, + const char *current_namespace, + struct maildir_shindex_cache *parentCache, + const char *indexfile, + const char *subhierarchy); + +static int list_newshared(const char *skipped_pattern, + struct list_sharable_info *shared_info) +{ + return list_newshared_shortcut(skipped_pattern, shared_info, + NEWSHARED, + NULL, NULL, NULL); +} + +static int list_newshared_shortcut(const char *skipped_pattern, + struct list_sharable_info *shared_info, + const char *acc_pfix, + struct maildir_shindex_cache *parentCache, + const char *indexfile, + const char *subhierarchy) +{ + struct list_newshared_info lni; + int rc; + struct maildir_shindex_cache *curcache=NULL; + + lni.acc_pfix=acc_pfix; + lni.skipped_pattern=skipped_pattern; + lni.shared_info=shared_info; + lni.dorecurse=1; + + /* Try for some common optimization, to avoid expanding the + ** entire #shared hierarchy, taking advantage of the cache list. + */ + + for (;;) + { + const char *p; + size_t i; + char *q; + int eof; + + if (strcmp(skipped_pattern, "%") == 0) + { + lni.dorecurse=0; + break; + } + + if (strncmp(skipped_pattern, "%" HIERCHS, + sizeof("%" HIERCHS)-1) == 0) + { + curcache=maildir_shared_cache_read(parentCache, + indexfile, + subhierarchy); + if (!curcache) + return 0; + + lni.acc_pfix=acc_pfix; + lni.skipped_pattern=skipped_pattern + + sizeof("%" HIERCHS)-1; + lni.parentCache=curcache; + + for (i=0; i<curcache->nrecords; i++) + { + if (i == 0) + { + curcache->indexfile.startingpos=0; + rc=maildir_newshared_nextAt(&curcache->indexfile, + &eof, + list_newshared_skiplevel, + &lni); + } + else + rc=maildir_newshared_next(&curcache->indexfile, + &eof, + list_newshared_skiplevel, + &lni); + + if (rc || eof) + { + fprintf(stderr, "ERR:maildir_newshared_next failed: %s\n", + strerror(errno)); + break; + } + } + return 0; + } + + for (p=skipped_pattern; *p; p++) + if (*p == HIERCH || + ((lni.shared_info->flags & LIST_CHECK1FOLDER) == 0 + && (*p == '*' || *p == '%'))) + break; + + if (*p && *p != HIERCH) + break; + + curcache=maildir_shared_cache_read(parentCache, indexfile, + subhierarchy); + if (!curcache) + return 0; + + for (i=0; i < curcache->nrecords; i++) + { + char *n=maildir_info_imapmunge(curcache->records[i] + .name); + + if (!n) + write_error_exit(0); + + if (strlen(n) == p-skipped_pattern && + strncmp(n, skipped_pattern, p-skipped_pattern) == 0) + { + free(n); + break; + } + free(n); + } + + if (i >= curcache->nrecords) /* not found */ + return 0; + + if (*p) + ++p; + + + q=malloc(strlen(acc_pfix)+(p-skipped_pattern)+1); + if (!q) + { + write_error_exit(0); + } + strcpy(q, acc_pfix); + strncat(q, skipped_pattern, p-skipped_pattern); + + lni.acc_pfix=q; + lni.skipped_pattern=p; + lni.parentCache=curcache; + + curcache->indexfile.startingpos=curcache->records[i].offset; + + rc=maildir_newshared_nextAt(&curcache->indexfile, &eof, + list_newshared_skipcb, &lni); + free(q); + return rc; + + } + + if (!indexfile) + indexfile=maildir_shared_index_file(); + + rc=maildir_newshared_enum(indexfile, list_newshared_cb, &lni); + + return rc; +} + +static int list_newshared_cb(struct maildir_newshared_enum_cb *cb) +{ + const char *name=cb->name; + const char *homedir=cb->homedir; + const char *maildir=cb->maildir; + struct list_newshared_info *lni= + (struct list_newshared_info *)cb->cb_arg; + char *n=maildir_info_imapmunge(name); + int rc; + + if (!n) + write_error_exit(0); + + if (homedir == NULL) + { + struct list_newshared_info new_lni= *lni; + char *new_pfix=malloc(strlen(lni->acc_pfix)+ + strlen(n)+2); + if (!new_pfix) + write_error_exit(0); + + strcat(strcpy(new_pfix, lni->acc_pfix), n); + + free(n); + n=new_pfix; + new_lni.acc_pfix=n; + add_hier(lni->shared_info->hierarchies, n); + hier_entry(n, lni->shared_info->hierarchies); + strcat(n, hierchs); + rc=lni->dorecurse ? + maildir_newshared_enum(maildir, list_newshared_cb, + &new_lni):0; + } + else + { + char *new_pfix; + struct stat stat_buf; + + new_pfix=maildir_location(homedir, maildir); + + if (stat(new_pfix, &stat_buf) < 0 || + /* maildir inaccessible, perhaps another server? */ + + (stat_buf.st_dev == homedir_dev && + stat_buf.st_ino == homedir_ino)) + /* Exclude ourselves from the shared list */ + { + free(new_pfix); + free(n); + return 0; + } + free(new_pfix); + + new_pfix=malloc(strlen(lni->acc_pfix)+ + strlen(n)+1); + if (!new_pfix) + write_error_exit(0); + + strcat(strcpy(new_pfix, lni->acc_pfix), n); + + free(n); + n=new_pfix; + + new_pfix=malloc(strlen(homedir)+strlen(maildir)+2); + + if (!new_pfix) + write_error_exit(0); + + if (*maildir == '/') + strcpy(new_pfix, maildir); + else + strcat(strcat(strcpy(new_pfix, homedir), "/"), + maildir); + + /* if (lni->dorecurse) */ + + maildir_scan(new_pfix, n, lni->shared_info); +#if 0 + else + { + folder_entry(n, lni->shared_info->pattern, + lni->shared_info->flags, + lni->shared_info->folders, + lni->shared_info->hierarchies); + } +#endif + + free(new_pfix); + rc=0; + } + free(n); + return rc; +} + +static int list_newshared_skiplevel(struct maildir_newshared_enum_cb *cb) +{ + struct list_newshared_info *lni= + (struct list_newshared_info *)cb->cb_arg; + char *n=maildir_info_imapmunge(cb->name); + + char *p=malloc(strlen(lni->acc_pfix)+strlen(n)+sizeof(HIERCHS)); + int rc; + const char *save_skip; + + if (!n || !p) + write_error_exit(0); + + strcat(strcat(strcpy(p, lni->acc_pfix), n), HIERCHS); + free(n); + + save_skip=lni->acc_pfix; + lni->acc_pfix=p; + + rc=list_newshared_skipcb(cb); + lni->acc_pfix=save_skip; + free(p); + return rc; +} + +static int list_newshared_skipcb(struct maildir_newshared_enum_cb *cb) +{ + struct list_newshared_info *lni= + (struct list_newshared_info *)cb->cb_arg; + char *dir; + char *inbox_name; + + if (cb->homedir == NULL) + return list_newshared_shortcut(lni->skipped_pattern, + lni->shared_info, + lni->acc_pfix, + lni->parentCache, + cb->maildir, + cb->name); + + inbox_name=my_strdup(lni->acc_pfix); + + dir=strrchr(inbox_name, HIERCH); + if (dir && dir[1] == 0) + *dir=0; /* Strip trailing hier separator */ + + dir=malloc(strlen(cb->homedir)+strlen(cb->maildir)+2); + + if (!dir) + { + free(inbox_name); + write_error_exit(0); + } + + if (*cb->maildir == '/') + strcpy(dir, cb->maildir); + else + strcat(strcat(strcpy(dir, cb->homedir), "/"), cb->maildir); + + maildir_scan(dir, inbox_name, lni->shared_info); + free(dir); + free(inbox_name); + return 0; +} + +static int do_mailbox_list(int list_options, char *pattern, int isnullname, + int (*callback_func)(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg), + void *void_arg) +{ +int found_hier=MAILBOX_NOSELECT; +int is_interesting; +int i,j,bad_pattern; +struct hierlist *hierarchies, *folders, *hp; +struct list_sharable_info shared_info; + +const char *obsolete; +int check_all_folders=0; +char hiersepbuf[8]; +int callback_rc=0; + + obsolete=getenv("IMAP_CHECK_ALL_FOLDERS"); + if (obsolete && atoi(obsolete)) + check_all_folders=1; + + obsolete=getenv("IMAP_OBSOLETE_CLIENT"); + + if (obsolete && atoi(obsolete) == 0) + obsolete=0; + + /* Allow up to ten wildcards */ + + for (i=j=0; pattern[i]; i++) + if (pattern[i] == '*' || pattern[i] == '%') ++j; + bad_pattern= j > 10; + + if (list_options & LIST_CHECK1FOLDER) + bad_pattern=0; + + if (bad_pattern) + { + errno=EINVAL; + return -1; + } + + hierarchies=0; + folders=0; + + match_mailbox_prep(pattern); + + shared_info.pattern=pattern; + shared_info.folders= &folders; + shared_info.hierarchies= &hierarchies; + shared_info.flags=list_options; + shared_info.callback_func=callback_func; + shared_info.cb_arg=void_arg; + + if (!(list_options & LIST_SUBSCRIBED)) + { + if (strncmp(pattern, NEWSHARED, + sizeof(NEWSHARED)-1) == 0) + { + list_newshared(pattern + + sizeof(NEWSHARED)-1, + &shared_info); + } + else + { + maildir_scan(".", INBOX, &shared_info); + + /* List sharable maildirs */ + + maildir_list_sharable( ".", &list_sharable, + &shared_info ); + } + } + else + { + list_subscribed(pattern, list_options, &folders, &hierarchies); + + /* List shared folders */ + + maildir_list_shared( ".", &list_sharable, + &shared_info ); + } + + while ((hp=folders) != 0) + { + struct hierlist *d; + int mb_flags; + + folders=hp->next; + + is_interesting= -1; + + if (strcmp(hp->hier, INBOX) == 0 || check_all_folders) + is_interesting=hasnewmsgs(hp->hier); + + strcat(strcat(strcpy(hiersepbuf, "\""), hierchs), "\""); + + mb_flags=0; + + if (is_interesting == 0) + { + mb_flags|=MAILBOX_UNMARKED; + } + if (is_interesting > 0) + { + mb_flags|=MAILBOX_MARKED; + } + + if ((d=search_hier(hierarchies, hp->hier)) == 0) + { + mb_flags |= + obsolete ? MAILBOX_NOINFERIORS:MAILBOX_NOCHILDREN; + } + else + { + d->flag=1; + if (!obsolete) + mb_flags |= MAILBOX_CHILDREN; + } + + if (isnullname) + found_hier=mb_flags; + else + if (callback_rc == 0) + callback_rc=(*callback_func) + (hiersepbuf, hp->hier, + mb_flags | list_options, void_arg); + free(hp); + } + + while ((hp=hierarchies) != 0) + { + hierarchies=hp->next; + + match_mailbox_prep(hp->hier); + + if (match_mailbox(hp->hier, pattern, list_options) == 0 + && hp->flag == 0) + { + int mb_flags=MAILBOX_NOSELECT; + + if (!obsolete) + mb_flags |= MAILBOX_CHILDREN; + + if (isnullname) + found_hier=mb_flags; + else + { + strcat(strcat(strcpy(hiersepbuf, "\""), + hierchs), "\""); + + if (callback_rc == 0) + callback_rc=(*callback_func) + (hiersepbuf, + hp->hier, + mb_flags | list_options, + void_arg); + } + } + free(hp); + } + + if (isnullname) + { + const char *namesp=""; + + if (strncmp(pattern, NEWSHARED, sizeof(NEWSHARED)-1) == 0) + namesp=NEWSHARED; + + strcat(strcat(strcpy(hiersepbuf, "\""), hierchs), "\""); + + if (callback_rc == 0) + callback_rc=(*callback_func) + (hiersepbuf, namesp, found_hier | list_options, + void_arg); + } + return callback_rc; +} + +static int match_recursive(char *, char *, int); + +static void match_mailbox_prep(char *name) +{ + size_t i; + + /* First component, INBOX, is case insensitive */ + + if ( +#if HAVE_STRNCASECMP + strncasecmp(name, INBOX, sizeof(INBOX)-1) == 0 +#else + strnicmp(name, INBOX, sizeof(INBOX)-1) == 0 +#endif + ) + for (i=0; name[i] && name[i] != HIERCH; i++) + name[i]=toupper( (int)(unsigned char)name[i] ); + + /* ... except that "shared" should be lowercase ... */ + + if (memcmp(name, "SHARED", 6) == 0) + memcpy(name, "shared", 6); +} + +static int match_mailbox(char *name, char *pattern, int list_options) +{ + if (list_options & LIST_CHECK1FOLDER) + return strcmp(name, pattern); + + return (match_recursive(name, pattern, HIERCH)); +} + +static int match_recursive(char *name, char *pattern, int hierch) +{ + for (;;) + { + if (*pattern == '*') + { + do + { + if (match_recursive(name, pattern+1, + hierch) == 0) + return (0); + } while (*name++); + return (-1); + } + if (*pattern == '%') + { + do + { + if (match_recursive(name, pattern+1, hierch) + == 0) return (0); + if (*name == hierch) break; + } while (*name++); + return (-1); + } + if (*name == 0 && *pattern == 0) break; + if (*name == 0 || *pattern == 0) return (-1); + if (*name != *pattern) return (-1); + ++name; + ++pattern; + } + return (0); +} diff --git a/imap/mailboxlist.h b/imap/mailboxlist.h new file mode 100644 index 0000000..4725422 --- /dev/null +++ b/imap/mailboxlist.h @@ -0,0 +1,33 @@ +#ifndef mailboxlist_h +#define mailboxlist_h + + +#include "config.h" + +/* +** Copyright 1998 - 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#define MAILBOX_MARKED 0x0001 +#define MAILBOX_UNMARKED 0x0002 +#define MAILBOX_NOCHILDREN 0x0004 +#define MAILBOX_NOINFERIORS 0x0008 +#define MAILBOX_CHILDREN 0x0010 +#define MAILBOX_NOSELECT 0x0020 + +#define LIST_SUBSCRIBED 0x0100 +#define LIST_ACL 0x0200 +#define LIST_MYRIGHTS 0x0400 +#define LIST_POSTADDRESS 0x0800 +#define LIST_CHECK1FOLDER 0x1000 + +int mailbox_scan(const char *ref, const char *name, + int list_options, + int (*callback_func)(const char *hiersep, + const char *mailbox, + int flags, + void *void_arg), + void *void_arg); + +#endif diff --git a/imap/mainloop.c b/imap/mainloop.c new file mode 100644 index 0000000..b58ae6f --- /dev/null +++ b/imap/mainloop.c @@ -0,0 +1,151 @@ +/* +** Copyright 1998 - 2006 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <signal.h> +#include <unistd.h> +#include "imaptoken.h" +#include "imapwrite.h" +#include "numlib/numlib.h" + + +extern int do_imap_command(const char *); + +extern unsigned long header_count, body_count; +extern unsigned long bytes_received_count, bytes_sent_count; +extern time_t start_time; + +static RETSIGTYPE sigexit(int n) +{ + static char byemsg[]="* BYE Courier-IMAP server shut down by signal.\r\n"; + const char *a=getenv("AUTHENTICATED"); + char buf[NUMBUFSIZE]; + char msg[1024]; + int l = 0; + const char *tls=getenv("IMAP_TLS"); + const char *ip=getenv("TCPREMOTEIP"); + + if (write(1, byemsg, sizeof(byemsg)-1) < 0) + exit(1); + + bytes_sent_count += sizeof(byemsg); + + libmail_str_time_t(time(NULL)-start_time, buf); + + if (tls && atoi(tls)) + tls=", starttls=1"; + else + tls=""; + + if (a && *a) + l = snprintf(msg, sizeof(msg) - 1, "NOTICE: Disconnected during shutdown by signal, user=%s, " + "ip=[%s], headers=%lu, body=%lu, rcvd=%lu, sent=%lu, time=%s%s\n", + a, ip, header_count, body_count, bytes_received_count, bytes_sent_count, + buf, tls); + else + l = snprintf(msg, sizeof(msg) - 1, "NOTICE: Disconnected during shutdown by signal, ip=[%s], time=%s%s\n", + getenv("TCPREMOTEIP"), + buf, tls); + + if (l > 0 && write(2, msg, l)) + ; /* Suppress gcc warning */ + + exit(0); +#if RETSIGTYPE != void + return (0); +#endif +} + + +void cmdfail(const char *tag, const char *msg) +{ +#if SMAP + const char *p=getenv("PROTOCOL"); + + if (p && strcmp(p, "SMAP1") == 0) + writes("-ERR "); + else +#endif + { + writes(tag); + writes(" NO "); + } + writes(msg); +} + +void cmdsuccess(const char *tag, const char *msg) +{ +#if SMAP + const char *p=getenv("PROTOCOL"); + + if (p && strcmp(p, "SMAP1") == 0) + writes("+OK "); + else +#endif + { + writes(tag); + writes(" OK "); + } + writes(msg); +} + +void mainloop(void) +{ + int noerril = 0; + + signal(SIGTERM, sigexit); + signal(SIGHUP, sigexit); + signal(SIGINT, sigexit); + + for (;;) + { + char tag[IT_MAX_ATOM_SIZE+1]; + struct imaptoken *curtoken; + + read_timeout(30 * 60); + curtoken=nexttoken_nouc(); + tag[0]=0; + if (curtoken->tokentype == IT_ATOM || + curtoken->tokentype == IT_NUMBER) + { + int rc; + + if (strlen(tag)+strlen(curtoken->tokenbuf) > IT_MAX_ATOM_SIZE) + write_error_exit("max atom size too small"); + + strncat(tag, curtoken->tokenbuf, IT_MAX_ATOM_SIZE); + rc=do_imap_command(tag); + + if (rc == 0) + { + noerril = 0; + writeflush(); + read_eol(); + continue; + } + if (rc == -2) + continue; + } + + noerril++; + if (noerril > 9) + { + errno = 0; + write_error_exit("TOO MANY CONSECUTIVE PROTOCOL VIOLATIONS"); + } + read_eol(); + cmdfail(tag[0] ? tag:"*", + "Error in IMAP command received by server.\r\n"); + writeflush(); + } +} diff --git a/imap/mkimapdcert.in b/imap/mkimapdcert.in new file mode 100644 index 0000000..4156975 --- /dev/null +++ b/imap/mkimapdcert.in @@ -0,0 +1,63 @@ +#! @SHELL@ +# +# +# Copyright 2000-2007 Double Precision, Inc. See COPYING for +# distribution information. +# +# This is a short script to quickly generate a self-signed X.509 key for +# IMAP over SSL. Normally this script would get called by an automatic +# package installation routine. + +if test "@ssllib@" = "openssl" +then + test -x @OPENSSL@ || exit 0 +else + test -x @CERTTOOL@ || exit 0 +fi + +prefix="@prefix@" + +if test -f @certsdir@/imapd.pem +then + echo "@certsdir@/imapd.pem already exists." + exit 1 +fi + +umask 077 + +cleanup() { + rm -f @certsdir@/imapd.pem + rm -f @certsdir@/imapd.rand + rm -f @certsdir@/imapd.key + rm -f @certsdir@/imapd.cert + exit 1 +} + +cd @certsdir@ + +if test "@ssllib@" = "openssl" +then + cp /dev/null @certsdir@/imapd.pem + chmod 600 @certsdir@/imapd.pem + chown @mailuser@ @certsdir@/imapd.pem + + dd if=@RANDOMV@ of=@certsdir@/imapd.rand count=1 2>/dev/null + @OPENSSL@ req -new -x509 -days 365 -nodes \ + -config @sysconfdir@/imapd.cnf -out @certsdir@/imapd.pem -keyout @certsdir@/imapd.pem || cleanup + @OPENSSL@ gendh -rand @certsdir@/imapd.rand 512 >>@certsdir@/imapd.pem || cleanup + @OPENSSL@ x509 -subject -dates -fingerprint -noout -in @certsdir@/imapd.pem || cleanup + rm -f @certsdir@/imapd.rand +else + cp /dev/null @certsdir@/imapd.key + chmod 600 @certsdir@/imapd.key + cp /dev/null @certsdir@/imapd.cert + chmod 600 @certsdir@/imapd.cert + cp /dev/null @certsdir@/imapd.pem + chmod 600 @certsdir@/imapd.pem + + @CERTTOOL@ --generate-privkey --outfile imapd.key + @CERTTOOL@ --generate-self-signed --load-privkey imapd.key --outfile imapd.cert --template @sysconfdir@/imapd.cnf + @CERTTOOL@ --generate-dh-params >>imapd.cert + cat imapd.key imapd.cert >imapd.pem + rm -f imapd.key imapd.cert +fi diff --git a/imap/mkimapdcert.sgml b/imap/mkimapdcert.sgml new file mode 100644 index 0000000..6b1c2f8 --- /dev/null +++ b/imap/mkimapdcert.sgml @@ -0,0 +1,83 @@ +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<!-- Copyright 2000 - 2007 Double Precision, Inc. See COPYING for --> +<!-- distribution information. --> +<refentry> + <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info> + + <refmeta> + <refentrytitle>mkimapdcert</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo>Double Precision, Inc.</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>mkimapdcert</refname> + <refpurpose>create a test SSL certificate for IMAP over SSL</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis sepchar=" "> + <command moreinfo="none">@sbindir@/mkimapdcert</command> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>DESCRIPTION</title> + + <para> +IMAP over SSL requires a valid, signed, X.509 certificate. The default +location for the certificate file is +<filename moreinfo="none">@certsdir@/imapd.pem</filename>. +<command moreinfo="none">mkimapdcert</command> generates a self-signed X.509 certificate, +mainly for +testing. +For production use the X.509 certificate must be signed by a +recognized certificate authority, in order for mail clients to accept the +certificate.</para> + + <para> +<filename moreinfo="none">@certsdir@/imapd.pem</filename> must be owned by the +@mailuser@ user and +have no group or world permissions. +The <command moreinfo="none">mkimapdcert</command> command will +enforce this. To prevent an unfortunate accident, +<command moreinfo="none">mkimapdcert</command> +will not work if <command moreinfo="none">@certsdir@/imapd.pem</command> already exists.</para> + + <para> +<command moreinfo="none">mkimapdcert</command> requires +<application moreinfo="none">OpenSSL</application> to be installed.</para> + + </refsect1> + + <refsect1> + <title>FILES</title> + + <variablelist> + <varlistentry> + <term>@certsdir@/imapd.pem</term> + <listitem> + <simpara> +X.509 certificate. +</simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term>@sysconfdir@/imapd.cnf</term> + <listitem> + <simpara> +Parameters used by OpenSSL to +create the X.509 certificate. +</simpara> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>SEE ALSO</title> + + <para> +<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink></para> + </refsect1> +</refentry> diff --git a/imap/mkpop3dcert.in b/imap/mkpop3dcert.in new file mode 100644 index 0000000..9a4c530 --- /dev/null +++ b/imap/mkpop3dcert.in @@ -0,0 +1,63 @@ +#! @SHELL@ +# +# +# Copyright 2000-2007 Double Precision, Inc. See COPYING for +# distribution information. +# +# This is a short script to quickly generate a self-signed X.509 key for +# POP3 over SSL. Normally this script would get called by an automatic +# package installation routine. + +if test "@ssllib@" = "openssl" +then + test -x @OPENSSL@ || exit 0 +else + test -x @CERTTOOL@ || exit 0 +fi + +prefix="@prefix@" + +if test -f @certsdir@/pop3d.pem +then + echo "@certsdir@/pop3d.pem already exists." + exit 1 +fi + +umask 077 + +cleanup() { + rm -f @certsdir@/pop3d.pem + rm -f @certsdir@/pop3d.rand + rm -f @certsdir@/pop3d.key + rm -f @certsdir@/pop3d.cert + exit 1 +} + +cd @certsdir@ + +if test "@ssllib@" = "openssl" +then + cp /dev/null @certsdir@/pop3d.pem + chmod 600 @certsdir@/pop3d.pem + chown @mailuser@ @certsdir@/pop3d.pem + + dd if=@RANDOMV@ of=@certsdir@/pop3d.rand count=1 2>/dev/null + @OPENSSL@ req -new -x509 -days 365 -nodes \ + -config @sysconfdir@/pop3d.cnf -out @certsdir@/pop3d.pem -keyout @certsdir@/pop3d.pem || cleanup + @OPENSSL@ gendh -rand @certsdir@/pop3d.rand 512 >>@certsdir@/pop3d.pem || cleanup + @OPENSSL@ x509 -subject -dates -fingerprint -noout -in @certsdir@/pop3d.pem || cleanup + rm -f @certsdir@/pop3d.rand +else + cp /dev/null @certsdir@/pop3d.key + chmod 600 @certsdir@/pop3d.key + cp /dev/null @certsdir@/pop3d.cert + chmod 600 @certsdir@/pop3d.cert + cp /dev/null @certsdir@/pop3d.pem + chmod 600 @certsdir@/pop3d.pem + + @CERTTOOL@ --generate-privkey --outfile pop3d.key + @CERTTOOL@ --generate-self-signed --load-privkey pop3d.key --outfile pop3d.cert --template @sysconfdir@/pop3d.cnf + @CERTTOOL@ --generate-dh-params >>pop3d.cert + cat pop3d.key pop3d.cert >pop3d.pem + rm -f pop3d.key pop3d.cert +fi diff --git a/imap/mkpop3dcert.sgml b/imap/mkpop3dcert.sgml new file mode 100644 index 0000000..abd4258 --- /dev/null +++ b/imap/mkpop3dcert.sgml @@ -0,0 +1,83 @@ +<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"> +<!-- Copyright 2000 - 2007 Double Precision, Inc. See COPYING for --> +<!-- distribution information. --> +<refentry> + <info><author><firstname>Sam</firstname><surname>Varshavchik</surname><contrib>Author</contrib></author><productname>Courier Mail Server</productname></info> + + <refmeta> + <refentrytitle>mkpop3dcert</refentrytitle> + <manvolnum>8</manvolnum> + <refmiscinfo>Double Precision, Inc.</refmiscinfo> + </refmeta> + + <refnamediv> + <refname>mkpop3dcert</refname> + <refpurpose>create a test SSL certificate for POP3 over SSL</refpurpose> + </refnamediv> + + <refsynopsisdiv> + <cmdsynopsis sepchar=" "> + <command moreinfo="none">@sbindir@/mkpop3dcert</command> + </cmdsynopsis> + </refsynopsisdiv> + + <refsect1> + <title>DESCRIPTION</title> + + <para> +POP3 over SSL requires a valid, signed, X.509 certificate. The default +location for the certificate file is +<filename moreinfo="none">@datadir@/pop3d.pem</filename>. +<command moreinfo="none">mkpop3dcert</command> generates a self-signed X.509 certificate, +mainly for +testing. +For production use the X.509 certificate must be signed by a +recognized certificate authority, in order for mail clients to accept the +certificate.</para> + + <para> +<filename moreinfo="none">@datadir@/pop3d.pem</filename> must be owned by the +@mailuser@ user and +have no group or world permissions. +The <command moreinfo="none">mkpop3dcert</command> command will +enforce this. To prevent an unfortunate accident, +<command moreinfo="none">mkpop3dcert</command> +will not work if <command moreinfo="none">@datadir@/pop3d.pem</command> already exists.</para> + + <para> +<command moreinfo="none">mkpop3dcert</command> requires +<application moreinfo="none">OpenSSL</application> to be installed.</para> + + </refsect1> + + <refsect1> + <title>FILES</title> + + <variablelist> + <varlistentry> + <term>@datadir@/pop3d.pem</term> + <listitem> + <simpara> +X.509 certificate. +</simpara> + </listitem> + </varlistentry> + + <varlistentry> + <term>@sysconfdir@/pop3d.cnf</term> + <listitem> + <simpara> +Parameters used by OpenSSL to +create the X.509 certificate. +</simpara> + </listitem> + </varlistentry> + </variablelist> + </refsect1> + <refsect1> + <title>SEE ALSO</title> + + <para> +<ulink url="courier.html"><citerefentry><refentrytitle>courier</refentrytitle><manvolnum>8</manvolnum></citerefentry></ulink></para> + </refsect1> +</refentry> diff --git a/imap/msgbodystructure.c b/imap/msgbodystructure.c new file mode 100644 index 0000000..760f967 --- /dev/null +++ b/imap/msgbodystructure.c @@ -0,0 +1,242 @@ +/* +** Copyright 1998 - 2001 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include "imaptoken.h" +#include "imapwrite.h" +#include "rfc822/rfc822.h" +#include "rfc2045/rfc2045.h" +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + + +extern void msgenvelope(void (*)(const char *, size_t), + FILE *, struct rfc2045 *); + +extern void msgappends(void (*)(const char *, size_t), const char *, size_t); + +static void do_param_list(void (*writefunc)(const char *, size_t), + struct rfc2045attr *a) +{ +int flag; +char *p; + + flag=0; + p="("; + for (; a; a=a->next) + { + (*writefunc)(p, strlen(p)); + (*writefunc)("\"", 1); + if (a->name) + msgappends(writefunc, a->name, strlen(a->name)); + (*writefunc)("\" \"", 3); + if (a->value) + { +#if IMAP_CLIENT_BUGS + + /* NETSCAPE */ + + char *u, *v, *w; + + u=strdup(a->value); + if (!u) write_error_exit(0); + strcpy(u, a->value); + for (v=w=u; *v; v++) + if (*v != '\\') *w++ = *v; + *w=0; + msgappends(writefunc, u, strlen(u)); + free(u); + +#else + msgappends(writefunc, a->value, strlen(a->value)); +#endif + } + (*writefunc)("\"", 1); + flag=1; + p=" "; + } + if (flag) + (*writefunc)(")", 1); + else + (*writefunc)("NIL", 3); +} + +static void contentstr( void (*writefunc)(const char *, size_t), const char *s) +{ + if (!s || !*s) + { + (*writefunc)("NIL", 3); + return; + } + + (*writefunc)("\"", 1); + msgappends(writefunc, s, strlen(s)); + (*writefunc)("\"", 1); +} + + +static void do_disposition( + void (*writefunc)(const char *, size_t), const char *disposition_s, + struct rfc2045attr *disposition_a) +{ + if ( (disposition_s == 0 || *disposition_s == 0) && + disposition_a == 0) + { + (*writefunc)("NIL", 3); + return; + } + (*writefunc)("(", 1); + + if (disposition_s && *disposition_s) + { + (*writefunc)("\"", 1); + msgappends(writefunc, disposition_s, + strlen(disposition_s)); + (*writefunc)("\"", 1); + } + else + (*writefunc)("\"\"", 2); + + (*writefunc)(" ", 1); + do_param_list(writefunc, disposition_a); + (*writefunc)(")", 1); +} + +void msgbodystructure( void (*writefunc)(const char *, size_t), int dox, + FILE *fp, struct rfc2045 *mimep) +{ +const char *content_type_s; +const char *content_transfer_encoding_s; +const char *charset_s; +off_t start_pos, end_pos, start_body; +off_t nlines, nbodylines; +const char *disposition_s; + +char *p, *q; + + rfc2045_mimeinfo(mimep, &content_type_s, &content_transfer_encoding_s, + &charset_s); + rfc2045_mimepos(mimep, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + disposition_s=mimep->content_disposition; + + (*writefunc)("(", 1); + + if (mimep->firstpart && mimep->firstpart->isdummy && + mimep->firstpart->next) + /* MULTIPART */ + { + struct rfc2045 *childp; + + for (childp=mimep->firstpart; (childp=childp->next) != 0; ) + msgbodystructure(writefunc, dox, fp, childp); + + (*writefunc)(" \"", 2); + p=strchr(content_type_s, '/'); + if (p) + msgappends(writefunc, p+1, strlen(p+1)); + (*writefunc)("\"", 1); + + if (dox) + { + (*writefunc)(" ", 1); + do_param_list(writefunc, mimep->content_type_attr); + + (*writefunc)(" ", 1); + do_disposition(writefunc, disposition_s, + mimep->content_disposition_attr); + + (*writefunc)(" ", 1); + contentstr(writefunc, rfc2045_content_language(mimep)); + } + } + else + { + char *mybuf; + char buf[40]; + const char *cp; + + mybuf=my_strdup(content_type_s); + q=strtok(mybuf, " /"); + (*writefunc)("\"", 1); + if (q) + msgappends(writefunc, q, strlen(q)); + (*writefunc)("\" \"", 3); + if (q) q=strtok(0, " /"); + if (q) + msgappends(writefunc, q, strlen(q)); + free(mybuf); + (*writefunc)("\" ", 2); + + do_param_list(writefunc, mimep->content_type_attr); + + (*writefunc)(" ", 1); + cp=rfc2045_content_id(mimep); + if (!cp || !*cp) + contentstr(writefunc, cp); + else + { + (*writefunc)("\"<", 2); + msgappends(writefunc, cp, strlen(cp)); + (*writefunc)(">\"", 2); + } + (*writefunc)(" ", 1); + contentstr(writefunc, rfc2045_content_description(mimep)); + + (*writefunc)(" \"", 2); + msgappends(writefunc, content_transfer_encoding_s, + strlen(content_transfer_encoding_s)); + (*writefunc)("\" ", 2); + + sprintf(buf, "%lu", (unsigned long) + (end_pos-start_body+nbodylines)); + /* nbodylines added for CRs */ + (*writefunc)(buf, strlen(buf)); + + if ( + (content_type_s[0] == 't' || content_type_s[0] == 'T') && + (content_type_s[1] == 'e' || content_type_s[1] == 'E') && + (content_type_s[2] == 'x' || content_type_s[2] == 'X') && + (content_type_s[3] == 't' || content_type_s[3] == 'T') && + (content_type_s[4] == '/' || + content_type_s[4] == 0)) + { + (*writefunc)(" ", 1); + sprintf(buf, "%lu", (unsigned long)nbodylines); + (*writefunc)(buf, strlen(buf)); + } + + if (mimep->firstpart && !mimep->firstpart->isdummy) + /* message/rfc822 */ + { + (*writefunc)(" ", 1); + msgenvelope(writefunc, fp, mimep->firstpart); + (*writefunc)(" ", 1); + msgbodystructure(writefunc, dox, fp, mimep->firstpart); + (*writefunc)(" ", 1); + sprintf(buf, "%lu", (unsigned long)nbodylines); + (*writefunc)(buf, strlen(buf)); + } + + if (dox) + { + (*writefunc)(" ", 1); + contentstr(writefunc, rfc2045_content_md5(mimep)); + + (*writefunc)(" ", 1); + do_disposition(writefunc, disposition_s, + mimep->content_disposition_attr); + + (*writefunc)(" NIL", 4); + /* TODO Content-Language: */ + } + } + (*writefunc)(")", 1); +} diff --git a/imap/msgenvelope.c b/imap/msgenvelope.c new file mode 100644 index 0000000..60564df --- /dev/null +++ b/imap/msgenvelope.c @@ -0,0 +1,310 @@ +/* +** Copyright 1998 - 2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include "rfc822/rfc822.h" +#include "rfc822/rfc2047.h" +#include "rfc2045/rfc2045.h" +#include "imapwrite.h" +#include <stdio.h> +#include <ctype.h> +#include <stdlib.h> +#include <string.h> + + +#define MAX_HEADER_SIZE 8192 + +static char *read_header(FILE *fp, off_t *headerpos, off_t *endhp) +{ +size_t i=0; +int c; +static char headerbuf[MAX_HEADER_SIZE]; + + while (*headerpos < *endhp) + { + while ((c=getc(fp)) != '\n' && c != EOF) + { + if (c == '\r') + c=' '; /* Otherwise Outlook croaks */ + + if (i < sizeof(headerbuf)-1) + headerbuf[i++]=c; + if ( ++*headerpos >= *endhp) break; + } + if (c == EOF || *headerpos >= *endhp) + { + *headerpos=*endhp; + break; + } + if (c == '\n' && ++*headerpos >= *endhp) break; + c=getc(fp); + if (c != EOF) ungetc(c, fp); + if (c == EOF) + { + *headerpos=*endhp; + break; + } + if (c != ' ' && c != '\t') break; + } + headerbuf[i]=0; + if (*headerpos == *endhp && i == 0) return (0); + return (headerbuf); +} + +void msgappends(void (*writefunc)(const char *, size_t), + const char *s, size_t l) +{ + size_t i,j; + char *q=0; + + for (i=0; i<l; i++) + if (s[i] & 0x80) /* Illegal 8-bit header content */ + { + char *p=malloc(l+1); + + if (!p) + write_error_exit(0); + if (l) + memcpy(p, s, l); + p[l]=0; + + /* Assume UTF-8, if not, well, GIGO */ + q=rfc2047_encode_str(p, "utf-8", + rfc2047_qp_allow_any); + free(p); + if (!q) + write_error_exit(0); + s=q; + l=strlen(s); + } + + for (i=j=0; i<l; i++) + { + if (s[i] == '"' || s[i] == '\\') + { + (*writefunc)(s+j, i-j); + (*writefunc)("\\", 1); + j=i; + } + } + (*writefunc)(s+j, i-j); + if (q) + free(q); +} + +static void doenvs(void (*writefunc)(const char *, size_t), char *s) +{ +size_t i,j; +char *t=s; + + while ( s && *s && isspace((int)(unsigned char)*s)) + ++s; + + for (i=j=0; s && s[i]; i++) + if ( !isspace((int)(unsigned char)s[i])) + j=i+1; + + if (j == 0) + (*writefunc)("NIL", 3); + else + { + (*writefunc)("\"", 1); + msgappends(writefunc, s, j); + (*writefunc)("\"", 1); + } + + if (t) free(t); +} + +static void doenva(void (*writefunc)(const char *, size_t), char *s) +{ +struct rfc822t *t; +struct rfc822a *a; +int i; +char *q, *r; + + if (!s) + { + (*writefunc)("NIL", 3); + return; + } + + t=rfc822t_alloc_new(s, 0, 0); + if (!t) + { + perror("malloc"); + exit(0); + } + a=rfc822a_alloc(t); + if (!a) + { + perror("malloc"); + exit(1); + } + + if (a->naddrs == 0) + { + rfc822a_free(a); + rfc822t_free(t); + free(s); + (*writefunc)("NIL", 3); + return; + } + + (*writefunc)("(", 1); + for (i=0; i<a->naddrs; i++) + { + (*writefunc)("(", 1); + + q=rfc822_display_name_tobuf(a, i, NULL); + + if (!q) + { + perror("malloc"); + exit(1); + } + if (a->addrs[i].tokens == 0) + { + if (strcmp(q, ";") == 0) + { + (*writefunc)("NIL NIL NIL NIL)", 16); + free(q); + continue; + } + r=strrchr(q, ':'); + if (r && r[1] == 0) *r=0; + + (*writefunc)("NIL NIL \"", 9); + msgappends(writefunc, q, strlen(q)); + (*writefunc)("\" NIL)", 6); + free(q); + continue; + } + + if (a->addrs[i].name == 0) + *q=0; + /* rfc822_display_name_tobuf() defaults to addr, ignore. */ + + doenvs(writefunc, q); + (*writefunc)(" NIL \"", 6); /* TODO @domain list */ + q=rfc822_gettok(a->addrs[i].tokens); + if (!q) + { + perror("malloc"); + exit(1); + } + r=strrchr(q, '@'); + if (r) *r++=0; + msgappends(writefunc, q, strlen(q)); + (*writefunc)("\" \"", 3); + if (r) + msgappends(writefunc, r, strlen(r)); + (*writefunc)("\")", 2); + free(q); + } + (*writefunc)(")", 1); + rfc822a_free(a); + rfc822t_free(t); + free(s); +} + +void msgenvelope(void (*writefunc)(const char *, size_t), + FILE *fp, struct rfc2045 *mimep) +{ +char *date=0, *subject=0; +char *from=0, *sender=0, *replyto=0, *to=0, *cc=0, *bcc=0; +char *inreplyto=0, *msgid=0; + +off_t start_pos, end_pos, start_body; +off_t nlines, nbodylines; + +char *p, *q, *r; + + rfc2045_mimepos(mimep, &start_pos, &end_pos, &start_body, + &nlines, &nbodylines); + + if (fseek(fp, start_pos, SEEK_SET) < 0) + { + perror("fseek"); + exit(0); + } + + while ((p=read_header(fp, &start_pos, &start_body)) != 0) + { + char **hdrp=0; + size_t oldl, newl; + size_t c; + + if ((q=strchr(p, ':')) != 0) + *q++=0; + for (r=p; *r; r++) + *r=tolower((int)(unsigned char)*r); + if (strcmp(p, "date") == 0) hdrp= &date; + if (strcmp(p, "subject") == 0) hdrp= &subject; + if (strcmp(p, "from") == 0) hdrp= &from; + if (strcmp(p, "sender") == 0) hdrp= &sender; + if (strcmp(p, "reply-to") == 0) hdrp= &replyto; + if (strcmp(p, "to") == 0) hdrp= &to; + if (strcmp(p, "cc") == 0) hdrp= &cc; + if (strcmp(p, "bcc") == 0) hdrp= &bcc; + if (strcmp(p, "in-reply-to") == 0) hdrp= &inreplyto; + if (strcmp(p, "message-id") == 0) hdrp= &msgid; + if (!hdrp) continue; + if (!q) q=""; + oldl= *hdrp ? strlen(*hdrp):0; + newl= strlen(q); + c=oldl+newl+1; + if (c > 8192) c=8192; + r= (char *)(*hdrp ? realloc(*hdrp, c+1):malloc(c+1)); + if (!r) + { + perror("malloc"); + exit(1); + } + if (oldl && oldl < c) + r[oldl++]=','; + newl=c-oldl; + if (newl) memcpy(r+oldl, q, newl); + r[oldl+newl]=0; + *hdrp= r; + } + +#if 1 + if (!replyto) + replyto=strdup(from ? from:""); + if (!sender) + sender=strdup(from ? from:""); + + if (!replyto || !sender) + { + perror("malloc"); + exit(1); + } +#endif + + (*writefunc)("(", 1); + doenvs(writefunc, date); + (*writefunc)(" ", 1); + doenvs(writefunc, subject); + (*writefunc)(" ", 1); + doenva(writefunc, from); + (*writefunc)(" ", 1); + doenva(writefunc, sender); + (*writefunc)(" ", 1); + doenva(writefunc, replyto); + (*writefunc)(" ", 1); + doenva(writefunc, to); + (*writefunc)(" ", 1); + doenva(writefunc, cc); + (*writefunc)(" ", 1); + doenva(writefunc, bcc); + (*writefunc)(" ", 1); + doenvs(writefunc, inreplyto); + (*writefunc)(" ", 1); + doenvs(writefunc, msgid); + (*writefunc)(")", 1); +} diff --git a/imap/mysignal.c b/imap/mysignal.c new file mode 100644 index 0000000..3dca78d --- /dev/null +++ b/imap/mysignal.c @@ -0,0 +1,37 @@ +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <signal.h> + + +static int n; + +static RETSIGTYPE trap(int signum) +{ + n=signum; +#if RETSIGTYPE != void + return (0); +#endif +} + +void trap_signals() +{ + n=0; + signal(SIGTERM, trap); + signal(SIGINT, trap); + signal(SIGHUP, trap); +} + +int release_signals() +{ + signal(SIGTERM, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGHUP, SIG_DFL); + return (n); +} diff --git a/imap/mysignal.h b/imap/mysignal.h new file mode 100644 index 0000000..5cdf16b --- /dev/null +++ b/imap/mysignal.h @@ -0,0 +1,12 @@ +#ifndef mysignal_h +#define mysignal_h + +/* +** Copyright 1998 - 1999 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +void trap_signals(); +int release_signals(); + +#endif diff --git a/imap/outbox.c b/imap/outbox.c new file mode 100644 index 0000000..babafce --- /dev/null +++ b/imap/outbox.c @@ -0,0 +1,257 @@ +/* +** Copyright 2002-2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <signal.h> +#include <ctype.h> +#include <fcntl.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + +#include "outbox.h" +#include "imapwrite.h" + +#include <sys/types.h> +#include <sys/stat.h> +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#ifndef WEXITSTATUS +#define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +#define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + +/* +** Is this the magic outbox? +*/ + +int is_outbox(const char *mailbox) +{ + const char *p=getenv("OUTBOX"); + + if (strncmp(mailbox, "./", 2) == 0) + mailbox += 2; + + if (p == NULL || *p == 0 || strcmp(p, mailbox)) + return (0); + + return (1); +} + +const char *defaultSendFrom() +{ + const char *from; + + from=getenv("AUTHADDR"); + if (!from || !*from) + from=getenv("AUTHENTICATED"); + if (from && !*from) + from=NULL; + + return from; +} + +/* +** After a message is copied to a folder, mail it, if it's an OUTBOX. +*/ + +static void errlogger(char *buffer) +{ + fprintf(stderr, "ERR: error sending a message, user=%s: %s\n", + getenv("AUTHENTICATED"), buffer); +} + +int check_outbox(const char *message, const char *mailbox) +{ + char *argv[10]; + const char *from; + + if (!is_outbox(mailbox)) + return (0); + + from=defaultSendFrom(); + + argv[1]="-oi"; + argv[2]="-t"; + argv[3]=NULL; + if (from) + { + argv[3]="-f"; + argv[4]=(char *)from; + argv[5]=NULL; + } + + return (imapd_sendmsg(message, argv, errlogger)); +} + +int imapd_sendmsg(const char *message, char **argv, void (*err_func)(char *)) +{ + char buffer[512]; + int i; + int ch; + const char *from=defaultSendFrom(); + const char *hdrfrom; + + const char *prog; + + int pipefd[2]; + FILE *pipefp, *pipesendmail; + pid_t pid, pid2; + int waitstat; + + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + if (pipe(pipefd) < 0) + write_error_exit("pipe"); + + prog=getenv("SENDMAIL"); + if (!prog || !*prog) + prog="sendmail"; + + + pid=fork(); + + if (pid < 0) + write_error_exit("fork"); + + if (pid > 0) /* Parent reads err message, checks exit status */ + { + i=0; + close(pipefd[1]); + pipefp=fdopen(pipefd[0], "r"); + if (pipefp == NULL) + write_error_exit("fdopen"); + while ((ch=getc(pipefp)) != EOF) + { + if ((unsigned char)ch < ' ') + ch='/'; + if (i < sizeof(buffer)-1) + buffer[i++]=ch; + } + fclose(pipefp); + close(pipefd[0]); + buffer[i]=0; + + while ((pid2=wait(&waitstat)) != pid) + if (pid2 < 0 && errno == ECHILD) + break; + + if (pid2 < 0 || !WIFEXITED(waitstat) || WEXITSTATUS(waitstat)) + { + if (buffer[0] == '\0') + { + buffer[0]=0; + strncat(buffer, prog, 128); + +#ifdef WIFSIGNALED +#ifdef WTERMSIG + if (WIFSIGNALED(waitstat)) + sprintf(buffer+strlen(buffer), + " terminated with signal %d", + (int)WTERMSIG(waitstat)); + else +#endif +#endif + strcat(buffer, " failed without logging an error. This shouldn't happen."); + } + + (*err_func)(buffer); + return (-1); + } + return (0); + } + + close(pipefd[0]); + close(1); + close(2); + if (dup(pipefd[1]) != 1 || dup(pipefd[1]) != 2) + { + perror("dup(pipefd) failed"); + exit(1); + } + close(pipefd[1]); + + pipefp=fopen(message, "r"); + if (pipefp == NULL) + { + perror(message); + exit(1); + } + + /* Child forks again, second child process runs sendmail */ + + if (pipe(pipefd) < 0) + { + perror("pipe"); + exit(1); + } + + pid=fork(); + + if (pid < 0) + { + perror("fork"); + exit(1); + } + + if (pid == 0) + { + fclose(pipefp); + close(pipefd[1]); + close(0); + errno=EINVAL; + if (dup(pipefd[0]) != 0) + { + perror("dup"); + exit(1); + } + + argv[0]=(char *)prog; + + execvp(prog, argv); + perror(prog); + exit(1); + } + + close(pipefd[0]); + + pipesendmail=fdopen(pipefd[1], "w"); + if (!pipesendmail) + { + perror("fdopen"); + kill(pid, SIGTERM); + close(pipefd[1]); + exit(1); + } + + if ((hdrfrom=getenv("HEADERFROM")) != NULL && *hdrfrom && from) + fprintf(pipesendmail, "%s: %s\n", hdrfrom, from); + while ((ch=getc(pipefp)) != EOF) + putc(ch, pipesendmail); + fclose(pipefp); + fclose(pipesendmail); + + while ((pid2=wait(&waitstat)) != pid) + if (pid2 < 0 && errno == ECHILD) + break; + + if (pid2 < 0 || !WIFEXITED(waitstat) || WEXITSTATUS(waitstat)) + { + fprintf(stderr, "Message send FAILED.\n"); + exit(1); + } + exit(0); + return (0); +} diff --git a/imap/outbox.h b/imap/outbox.h new file mode 100644 index 0000000..a63a2c9 --- /dev/null +++ b/imap/outbox.h @@ -0,0 +1,15 @@ +#ifndef outbox_h +#define outbox_h + +/* +** Copyright 2002 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +int check_outbox(const char *message, const char *mailbox); +int is_outbox(const char *mailbox); +int imapd_sendmsg(const char *message, char **argv, void (*err_func)(char *)); +const char *defaultSendFrom(); + +#endif diff --git a/imap/pop3d-ssl.dist.in b/imap/pop3d-ssl.dist.in new file mode 100644 index 0000000..e306226 --- /dev/null +++ b/imap/pop3d-ssl.dist.in @@ -0,0 +1,263 @@ +##VERSION: $Id:$ +# +# pop3d-ssl created from pop3d-ssl.dist by sysconftool +# +# Do not alter lines that begin with ##, they are used when upgrading +# this configuration. +# +# Copyright 2000-2008 Double Precision, Inc. See COPYING for +# distribution information. +# +# This configuration file sets various options for the Courier-IMAP server +# when used to handle SSL POP3 connections. +# +# SSL and non-SSL connections are handled by a dedicated instance of the +# couriertcpd daemon. If you are accepting both SSL and non-SSL POP3 +# connections, you will start two instances of couriertcpd, one on the +# POP3 port 110, and another one on the POP3-SSL port 995. +# +# Download OpenSSL from http://www.openssl.org/ +# +##NAME: SSLPORT:0 +# +# Options in the pop3d-ssl configuration file AUGMENT the options in the +# pop3d configuration file. First the pop3d configuration file is read, +# then the pop3d-ssl configuration file, so we do not have to redefine +# anything. +# +# However, some things do have to be redefined. The port number is +# specified by SSLPORT, instead of PORT. The default port is port 995. +# +# Multiple port numbers can be separated by commas. When multiple port +# numbers are used it is possibly to select a specific IP address for a +# given port as "ip.port". For example, "127.0.0.1.900,192.168.0.1.900" +# accepts connections on port 900 on IP addresses 127.0.0.1 and 192.168.0.1 +# The SSLADDRESS setting is a default for ports that do not have +# a specified IP address. + +SSLPORT=995 + +##NAME: SSLADDRESS:0 +# +# Address to listen on, can be set to a single IP address. +# +# SSLADDRESS=127.0.0.1 + +SSLADDRESS=0 + +##NAME: SSLPIDFILE:0 +# + +SSLPIDFILE=@piddir@/pop3d-ssl.pid + +##NAME: SSLLOGGEROPTS:0 +# +# courierlogger(1) options. +# + +SSLLOGGEROPTS="-name=pop3d-ssl" + +##NAME: POP3DSSLSTART:0 +# +# Whether or not to start POP3 over SSL on spop3 port: + +POP3DSSLSTART=NO + +##NAME: POP3_STARTTLS:0 +# +# Whether or not to implement the POP3 STLS extension: + +POP3_STARTTLS=YES + +##NAME: POP3_TLS_REQUIRED:1 +# +# Set POP3_TLS_REQUIRED to 1 if you REQUIRE STARTTLS for everyone. +# (this option advertises the LOGINDISABLED POP3 capability, until STARTTLS +# is issued). + +POP3_TLS_REQUIRED=0 + +##NAME: COURIERTLS:0 +# +# The following variables configure POP3 over SSL. If OpenSSL or GnuTLS +# is available during configuration, the couriertls helper gets compiled, and +# upon installation a dummy TLS_CERTFILE gets generated. +# +# WARNING: Peer certificate verification has NOT yet been tested. Proceed +# at your own risk. Only the basic SSL/TLS functionality is known to be +# working. Keep this in mind as you play with the following variables. + +COURIERTLS=@bindir@/couriertls + +##NAME: TLS_PRIORITY:0 +# +# Set TLS protocol priority settings (GnuTLS only) +# +# DEFAULT: NORMAL:-CTYPE-OPENPGP +# +# TLS_PRIORITY="NORMAL:-CTYPE-OPENPGP" + +##NAME: TLS_PROTOCOL:0 +# +# TLS_PROTOCOL sets the protocol version. The possible versions are: +# +# OpenSSL: +# +# SSL3 - SSLv3 +# SSL23 - either SSLv2 or SSLv3 (also TLS1, it seems) +# TLS1 - TLS1 +# +# Note that this setting, with OpenSSL, is modified by the TLS_CIPHER_LIST +# setting, below. +# +# SSL23 + +##NAME: TLS_STARTTLS_PROTOCOL:0 +# +# TLS_STARTTLS_PROTOCOL is used instead of TLS_PROTOCOL for the POP3 STARTTLS +# extension, as opposed to POP3 over SSL on port 995. +# +# It takes the same values for OpenSSL/GnuTLS as TLS_PROTOCOL + +TLS_STARTTLS_PROTOCOL=TLS1 + +##NAME: TLS_CIPHER_LIST:0 +# +# TLS_CIPHER_LIST optionally sets the list of ciphers to be used by the +# OpenSSL library. In most situations you can leave TLS_CIPHER_LIST +# undefined +# +# OpenSSL: +# +# TLS_CIPHER_LIST="SSLv3:TLSv1:HIGH:!LOW:!MEDIUM:!EXP:!NULL:!aNULL@STRENGTH" +# +# + + +##NAME: TLS_MIN_DH_BITS:0 +# +# TLS_MIN_DH_BITS=n +# +# GnuTLS only: +# +# Set the minimum number of acceptable bits for a DH key exchange. +# +# GnuTLS's compiled-in default is 727 bits (as of GnuTLS 1.6.3). Some server +# have been encountered that offer 512 bit keys. You may have to set +# TLS_MIN_DH_BITS=512 here, if necessary. + +##NAME: TLS_TIMEOUT:0 +# TLS_TIMEOUT is currently not implemented, and reserved for future use. +# This is supposed to be an inactivity timeout, but its not yet implemented. +# + +##NAME: TLS_DHCERTFILE:0 +# +# TLS_DHCERTFILE - PEM file that stores a Diffie-Hellman -based certificate. +# When OpenSSL is compiled to use Diffie-Hellman ciphers instead of RSA +# you must generate a DH pair that will be used. In most situations the +# DH pair is to be treated as confidential, and the file specified by +# TLS_DHCERTFILE must not be world-readable. +# +# TLS_DHCERTFILE= + +##NAME: TLS_CERTFILE:0 +# +# TLS_CERTFILE - certificate to use. TLS_CERTFILE is required for SSL/TLS +# servers, and is optional for SSL/TLS clients. TLS_CERTFILE is usually +# treated as confidential, and must not be world-readable. Set TLS_CERTFILE +# instead of TLS_DHCERTFILE if this is a garden-variety certificate +# +# VIRTUAL HOSTS (servers only): +# +# Due to technical limitations in the original SSL/TLS protocol, a dedicated +# IP address is required for each virtual host certificate. If you have +# multiple certificates, install each certificate file as +# $TLS_CERTFILE.aaa.bbb.ccc.ddd, where "aaa.bbb.ccc.ddd" is the IP address +# for the certificate's domain name. So, if TLS_CERTFILE is set to +# /etc/certificate.pem, then you'll need to install the actual certificate +# files as /etc/certificate.pem.192.168.0.2, /etc/certificate.pem.192.168.0.3 +# and so on, for each IP address. +# +# GnuTLS only (servers only): +# +# GnuTLS implements a new TLS extension that eliminates the need to have a +# dedicated IP address for each SSL/TLS domain name. Install each certificate +# as $TLS_CERTFILE.domain, so if TLS_CERTFILE is set to /etc/certificate.pem, +# then you'll need to install the actual certificate files as +# /etc/certificate.pem.host1.example.com, /etc/certificate.pem.host2.example.com +# and so on. +# +# Note that this TLS extension also requires a corresponding support in the +# client. Older SSL/TLS clients may not support this feature. +# +# This is an experimental feature. + +TLS_CERTFILE=@certsdir@/pop3d.pem + +##NAME: TLS_TRUSTCERTS:0 +# +# TLS_TRUSTCERTS=pathname - load trusted certificates from pathname. +# pathname can be a file or a directory. If a file, the file should +# contain a list of trusted certificates, in PEM format. If a +# directory, the directory should contain the trusted certificates, +# in PEM format, one per file and hashed using OpenSSL's c_rehash +# script. TLS_TRUSTCERTS is used by SSL/TLS clients (by specifying +# the -domain option) and by SSL/TLS servers (TLS_VERIFYPEER is set +# to PEER or REQUIREPEER). +# + +TLS_TRUSTCERTS=@cacerts@ + +##NAME: TLS_VERIFYPEER:0 +# +# TLS_VERIFYPEER - how to verify client certificates. The possible values of +# this setting are: +# +# NONE - do not verify anything +# +# PEER - verify the client certificate, if one's presented +# +# REQUIREPEER - require a client certificate, fail if one's not presented +# +# +TLS_VERIFYPEER=NONE + +##NAME: TLS_EXTERNAL:0 +# +# To enable SSL certificate-based authentication: +# +# 1) TLS_TRUSTCERTS must be set to a pathname that holds your certificate +# authority's SSL certificate +# +# 2) TLS_VERIFYPEER=PEER or TLS_VERIFYPEER=REQUIREPEER (the later settings +# requires all SSL clients to present a certificate, and rejects +# SSL/TLS connections without a valid cert). +# +# 3) Set TLS_EXTERNAL, below, to the subject field that holds the login ID. +# Example: +# +# TLS_EXTERNAL=emailaddress +# +# The above example retrieves the login ID from the "emailaddress" subject +# field. The certificate's emailaddress subject must match exactly the login +# ID in the courier-authlib database. + +##NAME: TLS_CACHE:0 +# +# A TLS/SSL session cache may slightly improve response for long-running +# POP3 clients. TLS_CACHEFILE will be automatically created, TLS_CACHESIZE +# bytes long, and used as a cache buffer. +# +# This is an experimental feature and should be disabled if it causes +# problems with SSL clients. Disable SSL caching by commenting out the +# following settings: + +TLS_CACHEFILE=@localstatedir@/couriersslcache +TLS_CACHESIZE=524288 + +##NAME: MAILDIRPATH:0 +# +# MAILDIRPATH - directory name of the maildir directory. +# +MAILDIRPATH=Maildir diff --git a/imap/pop3d.authpam b/imap/pop3d.authpam new file mode 100644 index 0000000..bc61899 --- /dev/null +++ b/imap/pop3d.authpam @@ -0,0 +1,16 @@ +#%PAM-1.0 +# +# +# Copyright 1998 - 1999 Double Precision, Inc. See COPYING for +# distribution information. +# +# To use the authpam authentication module with courierpop3d, you must +# configure your PAM library to authenticate the "pop3" service. +# See your system documentation for information on how to configure your +# PAM services. In most cases, all you need to do is to install this file +# as /etc/pam.d/pop3, but check your system documentation to make sure. + +auth required /lib/security/pam_pwdb.so shadow nullok +account required /lib/security/pam_pwdb.so +session required /lib/security/pam_pwdb.so + diff --git a/imap/pop3d.cnf.gnutls b/imap/pop3d.cnf.gnutls new file mode 100644 index 0000000..a8a4466 --- /dev/null +++ b/imap/pop3d.cnf.gnutls @@ -0,0 +1,9 @@ +organization = "Courier Mail Server" +unit = "Automatically-generated POP3 SSL key" +locality = "New York" +state = "NY" +country = US +cn = "localhost" +serial = 001 +expiration_days = 365 +email = "postmaster@example.com" diff --git a/imap/pop3d.cnf.openssl.in b/imap/pop3d.cnf.openssl.in new file mode 100644 index 0000000..971965e --- /dev/null +++ b/imap/pop3d.cnf.openssl.in @@ -0,0 +1,23 @@ + +RANDFILE = @certsdir@/pop3d.rand + +[ req ] +default_bits = 1024 +encrypt_key = yes +distinguished_name = req_dn +x509_extensions = cert_type +prompt = no +default_md = sha1 + +[ req_dn ] +C=US +ST=NY +L=New York +O=Courier Mail Server +OU=Automatically-generated POP3 SSL key +CN=localhost +emailAddress=postmaster@example.com + + +[ cert_type ] +nsCertType = server diff --git a/imap/pop3d.dist.in b/imap/pop3d.dist.in new file mode 100644 index 0000000..b3ab118 --- /dev/null +++ b/imap/pop3d.dist.in @@ -0,0 +1,163 @@ +##VERSION: $Id:$ +# +# pop3d created from pop3d.dist by sysconftool +# +# Do not alter lines that begin with ##, they are used when upgrading +# this configuration. +# +# Copyright 1998 - 2011 Double Precision, Inc. See COPYING for +# distribution information. +# +# Courier POP3 daemon configuration +# +##NAME: PIDFILE:0 +# + +PIDFILE=@piddir@/pop3d.pid + +##NAME: MAXDAEMONS:0 +# +# Maximum number of POP3 servers started +# + +MAXDAEMONS=40 + +##NAME: MAXPERIP:4 +# +# Maximum number of connections to accept from the same IP address + +MAXPERIP=4 + +##NAME: POP3AUTH:1 +# +# To advertise the SASL capability, per RFC 2449, uncomment the POP3AUTH +# variable: +# +# POP3AUTH="LOGIN" +# +# If you have configured the CRAM-MD5, CRAM-SHA1 or CRAM-SHA256, set POP3AUTH +# to something like this: +# +# POP3AUTH="LOGIN CRAM-MD5 CRAM-SHA1" + +POP3AUTH="" + +##NAME: POP3AUTH_ORIG:1 +# +# For use by webadmin + +POP3AUTH_ORIG="PLAIN LOGIN CRAM-MD5 CRAM-SHA1 CRAM-SHA256" + +##NAME: POP3AUTH_TLS:1 +# +# To also advertise SASL PLAIN if SSL is enabled, uncomment the +# POP3AUTH_TLS environment variable: +# +# POP3AUTH_TLS="LOGIN PLAIN" + +POP3AUTH_TLS="" + +##NAME: POP3AUTH_TLS_ORIG:0 +# +# For use by webadmin + +POP3AUTH_TLS_ORIG="LOGIN PLAIN" + +##NAME: POP3_PROXY:0 +# +# Enable proxying. See README.proxy + +POP3_PROXY=0 + +##NAME: PROXY_HOSTNAME:0 +# +# Override value from gethostname() when checking if a proxy connection is +# required. + +# PROXY_HOSTNAME= + +##NAME: PORT:1 +# +# Port to listen on for connections. The default is port 110. +# +# Multiple port numbers can be separated by commas. When multiple port +# numbers are used it is possibly to select a specific IP address for a +# given port as "ip.port". For example, "127.0.0.1.900,192.68.0.1.900" +# accepts connections on port 900 on IP addresses 127.0.0.1 and 192.68.0.1 +# The ADDRESS setting is a default for ports that do not have a specified +# IP address. + +PORT=110 + +##NAME: ADDRESS:0 +# +# IP address to listen on. 0 means all IP addresses. + +ADDRESS=0 + +##NAME: AUTHSERVICE:0 +# +# It's possible to authenticate using a different 'service' parameter +# depending on the connection's port. This only works with authentication +# modules that use the 'service' parameter, such as PAM. Example: +# +# AUTHSERVICE110=pop3 +# AUTHSERVICE995=pop3s + +##NAME: TCPDOPTS:0 +# +# Other couriertcpd(1) options. The following defaults should be fine. +# + +TCPDOPTS="-nodnslookup -noidentlookup" + +##NAME: LOGGEROPTS:0 +# +# courierlogger(1) options. +# + +LOGGEROPTS="-name=pop3d" + +##NAME: DEFDOMAIN:0 +# +# Optional default domain. If the username does not contain the +# first character of DEFDOMAIN, then it is appended to the username. +# If DEFDOMAIN and DOMAINSEP are both set, then DEFDOMAIN is appended +# only if the username does not contain any character from DOMAINSEP. +# You can set different default domains based on the the interface IP +# address using the -access and -accesslocal options of couriertcpd(1). + +#DEFDOMAIN="@example.com" + +##NAME: POP3DSTART:0 +# +# POP3DSTART is not referenced anywhere in the standard Courier programs +# or scripts. Rather, this is a convenient flag to be read by your system +# startup script in /etc/rc.d, like this: +# +# . @sysconfdir@/pop3d +# case x$POP3DSTART in +# x[yY]*) +# @libexecdir@/pop3d.rc start +# ;; +# esac +# +# The default setting is going to be NO, until Courier is shipped by default +# with enough platforms so that people get annoyed with having to flip it to +# YES every time. + +POP3DSTART=NO + +##NAME: POP3_LOG_DELETIONS:0 +# +# +# Set POP3_LOG_DELETIONS to log all message deletions to syslog. +# +# POP3_LOG_DELETIONS=1 + + +##NAME: MAILDIRPATH:0 +# +# MAILDIRPATH - directory name of the maildir directory. +# +MAILDIRPATH=Maildir diff --git a/imap/pop3dcapa.c b/imap/pop3dcapa.c new file mode 100644 index 0000000..5d0eef4 --- /dev/null +++ b/imap/pop3dcapa.c @@ -0,0 +1,87 @@ +/* +** Copyright 1998 - 2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#undef PACKAGE +#undef VERSION +#include "config.h" +#endif +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <ctype.h> +#include <fcntl.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif + + +extern const char *externalauth(); + +int have_starttls() +{ + const char *p; + + if ((p=getenv("POP3_STARTTLS")) == 0) return (0); + if (*p != 'y' && *p != 'Y') return (0); + + p=getenv("COURIERTLS"); + if (!p || !*p) return (0); + if (access(p, X_OK)) return (0); + return (1); +} + + +int tls_required() +{ + const char *p=getenv("POP3_TLS_REQUIRED"); + + if (p && atoi(p)) return (1); + return (0); +} + +const char *pop3_externalauth() +{ + const char *external=NULL; + const char *p; + + if ((p=getenv("POP3_TLS")) != 0 && atoi(p)) + external=externalauth(); + + return external; +} + +void pop3dcapa() +{ + const char *p; + const char *external=pop3_externalauth(); + + printf("+OK Here's what I can do:\r\n"); + + if ((p=getenv("POP3_TLS")) != 0 && atoi(p) && + (p=getenv("POP3AUTH_TLS")) != 0 && *p) + ; + else + p=getenv("POP3AUTH"); + + if ((p && *p) || external) + { + if (!p) + p=""; + + if (!external) + external=""; + + printf("SASL %s%s%s\r\n", p, *p && *external ? " ":"", + *external ? "EXTERNAL":""); + } + + if (have_starttls()) + printf("STLS\r\n"); + + printf("TOP\r\nUSER\r\nLOGIN-DELAY 10\r\nPIPELINING\r\nUIDL\r\nIMPLEMENTATION Courier Mail Server\r\n.\r\n"); + fflush(stdout); +} diff --git a/imap/pop3dserver.c b/imap/pop3dserver.c new file mode 100644 index 0000000..13aced9 --- /dev/null +++ b/imap/pop3dserver.c @@ -0,0 +1,1120 @@ +/* +** Copyright 1998 - 2011 Double Precision, Inc. +** See COPYING for distribution information. +** +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <sys/types.h> +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif +#include <sys/types.h> +#if HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <signal.h> +#include <errno.h> +#include "numlib/numlib.h" +#include "maildir/config.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirquota.h" +#include "maildir/maildirgetquota.h" +#include "maildir/maildircreate.h" +#include "maildir/loginexec.h" +#include "courierauth.h" + +#define POP3DLIST "courierpop3dsizelist" + +extern void pop3dcapa(); +static void acctout(const char *disc); + +static const char *authaddr, *remoteip, *remoteport; + +struct msglist { + struct msglist *next; + char *filename; + int isdeleted; + int isnew; + off_t size; + + struct { + unsigned long uidv; + unsigned long n; + } uid; + } ; + +static struct msglist *msglist_l; +static struct msglist **msglist_a; +static unsigned msglist_cnt; + +static struct stat enomem_stat; +static int enomem_1msg; + +/* +** When a disk error occurs while saving an updated courierpop3dsizelist +** file, proceed to recover as follows: +** +** If there's at least one existing message that's found in the old +** courierpop3dsizelist, then ignore all new messages that were not found +** in the old courierpop3dsizelist, and a new uid/uidv was assigned to them. +** Therefore, the client doesn't see any new messages. Hopefully, after +** at least one existing message gets deleted, there'll be room to create +** a new courierpop3dsizelist file next time, and record new messages. +** +** If none of the messages in the maildir could be found in the +** courierpop3dsizelist, take the first message in the maildir only, and +** use a UIDL that's derived from the message's dev_t/ino_t. The client +** will see that message only. After deleting it, hopefully a new +** courierpop3dsizelist file could be written out next time. +*/ + + +static unsigned long top_count=0; +static unsigned long retr_count=0; + +static unsigned long bytes_sent_count=0; +static unsigned long bytes_received_count=0; + +static unsigned long uidv=0; +static int convert_v0=0; + +static time_t start_time; + +/* +** The RFC is pretty strict in stating that octet size must count the CR +** in the CRLF endofline. +*/ + +static void calcsize(struct msglist *m) +{ +FILE *f=fopen(m->filename, "r"); +int c, lastc; + + m->size=0; + if (f == 0) + { + perror("calcsize fopen"); + return; + } + lastc='\n'; + while ((c=getc(f)) >= 0) + { + if (c == '\n') ++m->size; + ++m->size; + lastc=c; + } + if (lastc != '\n') m->size += 2; + + if (ferror(f)) + { + perror("calcsize ferror"); + return; + } + fclose(f); +} + +static FILE * +openpop3dlist() +{ + int tries; + FILE *fp; + + tries = 0; + do { + fp = fopen(POP3DLIST, "r"); + if (fp != NULL) + return (fp); + if (errno != ESTALE) { + if (errno != ENOENT) + perror("failed to open " POP3DLIST " file"); + return (NULL); + } + ++tries; + } while (tries < 3); /* somewhat arbitrary */ + fprintf(stderr, "failed to open pop3dlist file after retries\n"); + return NULL; +} + +/* +** Read courierpop3dsizelist +*/ + +static int cmpmsgs(const void *a, const void *b); + +static struct msglist **readpop3dlist(unsigned long *uid) +{ + struct msglist **a; + struct msglist *list=NULL; + size_t mcnt=0; + + char linebuf[1024]; + + FILE *fp=openpop3dlist(); + + size_t i; + int vernum=0; + + uidv=time(NULL); + + convert_v0=0; + + if (fp == NULL || + fgets(linebuf, sizeof(linebuf)-1, fp) == NULL || + linebuf[0] != '/' || sscanf(linebuf+1, "%d %lu %lu", &vernum, + uid, &uidv) + < 2 || (vernum != 1 && vernum != 2)) + { + if (fp == NULL) + convert_v0=1; + + if (vernum == 0 && fp && fseek(fp, 0L, SEEK_SET) >= 0) + { + /* Old version 0 format courierpop3dsizelist file */ + } + else + { + if (fp) + fclose(fp); + fp=NULL; + } + } + + if (fp) + { + struct msglist *m; + + char *p, *q; + + size_t n=0; + int ch; + + while ((ch=getc(fp)) != EOF) + { + unsigned long sz; + + if (ch != '\n') + { + if (n < sizeof(linebuf)-3) + linebuf[n++]=ch; + continue; + } + linebuf[n]=0; + n=0; + + if (vernum == 0) + strcat(linebuf, " 0"); + /* Convert version 0 to version 1 format - PRESTO! */ + + if ((p=strrchr(linebuf, ' ')) == NULL) + continue; + *p=0; + if ((q=strrchr(linebuf, ' ')) == NULL) + continue; + *p=' '; + p=q; + *p++=0; + + if (linebuf[0] == 0) + continue; + + if ((m=(struct msglist *)malloc(sizeof(struct + msglist))) == 0) + { + perror("malloc"); + exit(1); + } + + if ((m->filename=strdup(linebuf)) == NULL) + { + perror("malloc"); + exit(1); + } + + switch (sscanf(p, "%lu %lu:%lu", &sz, + &m->uid.n, &m->uid.uidv)) { + case 2: + m->uid.uidv=0; + /* FALLTHROUGH */ + case 3: + m->size=sz; + m->next=list; + list=m; + ++mcnt; + break; + default: + free(m->filename); + free(m); + } + } + fclose(fp); + } + if ((a=(struct msglist **)malloc((mcnt+1) + *sizeof(struct msglist *))) == 0) + { + perror("malloc"); + exit(1); + } + + for (i=0; list; list=list->next) + a[i++]=list; + + a[i]=NULL; + qsort(a, i, sizeof(*a), cmpmsgs); + + return a; +} + +static int savepop3dlist(struct msglist **a, size_t cnt, + unsigned long uid) +{ + FILE *fp; + size_t i; + + struct maildir_tmpcreate_info createInfo; + + maildir_tmpcreate_init(&createInfo); + + createInfo.uniq="pop3"; + createInfo.doordie=1; + + if ((fp=maildir_tmpcreate_fp(&createInfo)) == NULL) + { + maildir_tmpcreate_free(&createInfo); + return -1; + } + + fprintf(fp, "/2 %lu %lu\n", uid, uidv); + + for (i=0; i<cnt; i++) + { + char *p=a[i]->filename; + char *q; + + if ((q=strrchr(p, '/')) != NULL) + p=q+1; + + fprintf(fp, "%s %lu %lu:%lu\n", p, (unsigned long)a[i]->size, + a[i]->uid.n, a[i]->uid.uidv); + } + + if (fflush(fp) || ferror(fp)) + { + fclose(fp); + unlink(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + return -1; + } + + if (fclose(fp) || + rename(createInfo.tmpname, POP3DLIST) < 0) + { + unlink(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + return -1; + } + + maildir_tmpcreate_free(&createInfo); + return 0; +} + +/* Scan cur, and pick up all messages contained therein */ + +static int scancur() +{ +DIR *dirp; +struct dirent *de; +struct msglist *m; + + if ((dirp=opendir("cur")) == 0) + { + perror("scancur opendir(\"cur\")"); + return 1; + } + + while ((de=readdir(dirp)) != 0) + { + if ( de->d_name[0] == '.' ) continue; + + if ((m=(struct msglist *)malloc(sizeof(struct msglist))) == 0) + { + perror("malloc"); + exit(1); + } + if ((m->filename=(char *)malloc(strlen(de->d_name)+5)) == 0) + { + free( (char *)m); + perror("malloc"); + exit(1); + } + strcat(strcpy(m->filename, "cur/"), de->d_name); + m->isdeleted=0; + m->next=msglist_l; + msglist_l=m; + msglist_cnt++; + } + closedir(dirp); + return 0; +} + +/* +** When sorting messages, sort on the arrival date - the first part of the +** name of the file in the maildir is the timestamp. +*/ + +static int cmpmsgs(const void *a, const void *b) +{ + const char *aname=(*(struct msglist **)a)->filename; + const char *bname=(*(struct msglist **)b)->filename; + const char *ap=strrchr(aname, '/'); + const char *bp=strrchr(bname, '/'); + long na, nb; + + if (ap) + ++ap; + else + ap=aname; + + if (bp) + ++bp; + else + bp=bname; + + na=atol(ap); + nb=atol(bp); + + if (na < nb) return (-1); + if (na > nb) return (1); + + while (*ap || *bp) + { + if (*ap == MDIRSEP[0] && *bp == MDIRSEP[0]) + break; + + if (*ap < *bp) + return -1; + if (*ap > *bp) + return 1; + ++ap; + ++bp; + } + + return 0; +} + +static void sortmsgs() +{ + size_t i, n; + struct msglist *m; + struct msglist **prev_list; + int savesizes=0; + + unsigned long nextuid; + + if (msglist_cnt == 0) return; + + if ((msglist_a=(struct msglist **)malloc( + msglist_cnt*sizeof(struct msglist *))) == 0) + { + perror("malloc"); + msglist_cnt=0; + return; + } + + for (i=0, m=msglist_l; m; m=m->next, i++) + { + m->isnew=0; + msglist_a[i]=m; + } + qsort(msglist_a, msglist_cnt, sizeof(*msglist_a), cmpmsgs); + + nextuid=1; + + prev_list=readpop3dlist(&nextuid); + + n=0; + + for (i=0; i<msglist_cnt; i++) + { + while (prev_list[n] && + cmpmsgs(&prev_list[n], &msglist_a[i]) < 0) + { + ++n; + savesizes=1; + } + + if (prev_list[n] && + cmpmsgs(&prev_list[n], &msglist_a[i]) == 0) + { + msglist_a[i]->size=prev_list[n]->size; + msglist_a[i]->uid=prev_list[n]->uid; + n++; + } + else + { + msglist_a[i]->uid.n=nextuid++; + msglist_a[i]->uid.uidv=uidv; + msglist_a[i]->isnew=1; + if (convert_v0) + msglist_a[i]->uid.n=0; + + calcsize(msglist_a[i]); + savesizes=1; + } + } + + if (prev_list[n]) + savesizes=1; + + for (i=0; prev_list[i]; i++) + { + free(prev_list[i]->filename); + free(prev_list[i]); + } + + free(prev_list); + + if (savesizes && savepop3dlist(msglist_a, msglist_cnt, nextuid) < 0) + { + fprintf(stderr, "ERR: Error while saving courierpop3dsizelist" + ", user=%s\n", authaddr); + + for (i=n=0; i<msglist_cnt; i++) + { + if (msglist_a[i]->isnew) + continue; + + msglist_a[n]=msglist_a[i]; + ++n; + } + + if (n == 0 && n < msglist_cnt && + stat(msglist_a[0]->filename, &enomem_stat) == 0) + { + enomem_1msg=1; + ++n; + } + msglist_cnt=n; + + } +} + +static void mkupper(char *p) +{ + while (*p) + { + *p=toupper(*p); + p++; + } +} + +static void cleanup() +{ + unsigned i; + const char *cp=getenv("POP3_LOG_DELETIONS"); + int log_deletions= cp && *cp != '0'; + + int64_t deleted_bytes=0; + int64_t deleted_messages=0; + + for (i=0; i<msglist_cnt; i++) + if (msglist_a[i]->isdeleted) + { + unsigned long un=0; + + const char *filename=msglist_a[i]->filename; + + if (maildirquota_countfile(filename)) + { + if (maildir_parsequota(filename, &un)) + { + struct stat stat_buf; + + if (stat(filename, &stat_buf) == 0) + un=stat_buf.st_size; + } + } + + if (log_deletions) + fprintf(stderr, "INFO: DELETED, user=%s, ip=[%s], filename=%s\n", + getenv("AUTHENTICATED"), + getenv("TCPREMOTEIP"), + msglist_a[i]->filename); + + if (unlink(msglist_a[i]->filename)) + un=0; + + if (un) + { + deleted_bytes -= un; + deleted_messages -= 1; + } + } + + if (deleted_messages < 0) + maildir_quota_deleted(".", deleted_bytes, deleted_messages); + + return; +} + +#define printed(c) do {int cnt=(c); if (cnt > 0) \ + bytes_sent_count += cnt; \ + } while(0) + +#define printchar(ch) do { putchar((ch)); printed(1); } while(0); + +/* POP3 STAT */ + +static void do_stat() +{ +off_t n=0; +unsigned i, j; +char buf[NUMBUFSIZE]; + + j=0; + for (i=0; i<msglist_cnt; i++) + { + if (msglist_a[i]->isdeleted) continue; + n += msglist_a[i]->size; + ++j; + } + + printed(printf("+OK %u %s\r\n", j, libmail_str_off_t(n, buf))); + fflush(stdout); +} + +static unsigned getmsgnum(const char *p) +{ +unsigned i; + + if (!p || (i=atoi(p)) > msglist_cnt || i == 0 || + msglist_a[i-1]->isdeleted) + { + printed(printf("-ERR Invalid message number.\r\n")); + fflush(stdout); + return (0); + } + return (i); +} + +/* POP3 LIST */ + +static void do_list(const char *msgnum) +{ +unsigned i; +char buf[NUMBUFSIZE]; + + if (msgnum) + { + if ((i=getmsgnum(msgnum)) != 0) + { + printed(printf("+OK %u %s\r\n", i, + libmail_str_off_t(msglist_a[i-1]->size, + buf))); + fflush(stdout); + } + return; + } + + printed(printf("+OK POP3 clients that break here, they violate STD53.\r\n")); + for (i=0; i<msglist_cnt; i++) + { + if (msglist_a[i]->isdeleted) continue; + printed(printf("%u %s\r\n", i+1, libmail_str_off_t(msglist_a[i]->size, buf))); + } + printed(printf(".\r\n")); + fflush(stdout); +} + +/* RETR and TOP POP3 commands */ + +static void do_retr(unsigned i, unsigned *lptr) +{ +FILE *f=fopen(msglist_a[i]->filename, "r"); +char *p; +int c, lastc='\n'; +int inheader=1; +char buf[NUMBUFSIZE]; +unsigned long *cntr; + + if (!f) + { + printed(printf("-ERR Can't open the message file - it's gone!\r\n")); + fflush(stdout); + return; + } + printed(printf( (lptr ? "+OK headers follow.\r\n":"+OK %s octets follow.\r\n"), + libmail_str_off_t(msglist_a[i]->size, buf))); + + cntr= &retr_count; + if (lptr) + cntr= &top_count; + + for (lastc=0; (c=getc(f)) >= 0; lastc=c) + { + if (lastc == '\n') + { + if (lptr) + { + if (inheader) + { + if (c == '\n') inheader=0; + } + else if ( (*lptr)-- == 0) break; + } + + if (c == '.') + printchar('.'); + } + if (c == '\n') printchar('\r'); + printchar(c); + ++*cntr; + } + if (ferror(f)) { + /* Oops! All we can do is drop the connection */ + fprintf(stderr, "ERR: I/O error while reading message file %s: %s\n", + msglist_a[i]->filename, strerror(errno)); + acctout("INFO: I/O error disconnect"); + exit(1); + } + if (lastc != '\n') printed(printf("\r\n")); + printed(printf(".\r\n")); + fflush(stdout); + fclose(f); + if (lptr) return; + + if ((p=strchr(msglist_a[i]->filename, MDIRSEP[0])) != 0 && + (p[1] != '2' || p[2] != ',' || strchr(p, 'S') != 0)) + return; + + if ((p=malloc(strlen(msglist_a[i]->filename)+5)) == 0) + return; + + strcpy(p, msglist_a[i]->filename); + if (strchr(p, MDIRSEP[0]) == 0) strcat(p, MDIRSEP "2,"); + strcat(p, "S"); + + if (lptr /* Don't mark as seen for TOP */ + || rename(msglist_a[i]->filename, p)) + { + free(p); + return; + } + free(msglist_a[i]->filename); + msglist_a[i]->filename=p; +} + +/* +** The UIDL of the message is really just its filename, up to the first MDIRSEP character +*/ + +static void print_uidl(unsigned i) +{ + const char *p; + + if (enomem_1msg) + /* Error recovery - out of disk space, see comments + ** at the beginning of this file. + */ + { + char dev_buf[NUMBUFSIZE]; + char ino_buf[NUMBUFSIZE]; + char mtime_buf[NUMBUFSIZE]; + + printed(printf("ENOMEM-%s-%s-%s\r\n", + libmail_strh_time_t(enomem_stat.st_mtime, mtime_buf), + libmail_strh_dev_t(enomem_stat.st_dev, dev_buf), + libmail_strh_ino_t(enomem_stat.st_ino, ino_buf)) + ); + return; + } + + if (msglist_a[i]->uid.n != 0) + { + /* VERSION 1 and VERSION 2 UIDL */ + + printed(printf((msglist_a[i]->uid.uidv ? + "UID%lu-%lu\r\n":"UID%lu\r\n"), + msglist_a[i]->uid.n, msglist_a[i]->uid.uidv)); + return; + } + + /* VERSION 0 UIDL */ + + p=strchr(msglist_a[i]->filename, '/')+1; + + while (*p && *p != MDIRSEP[0]) + { + if (*p < 0x21 || *p > 0x7E || *p == '\'' || *p == '"' || + *p == '+') + printed(printf("+%02X", (int)(unsigned char)*p)); + else + printchar(*p); + ++p; + } + printed(printf("\r\n")); +} + +static void do_uidl(const char *msgnum) +{ +unsigned i; + + if (msgnum) + { + if ((i=getmsgnum(msgnum)) != 0) + { + printed(printf("+OK %u ", i)); + print_uidl(i-1); + fflush(stdout); + } + return; + } + printed(printf("+OK\r\n")); + for (i=0; i<msglist_cnt; i++) + { + if (msglist_a[i]->isdeleted) continue; + printed(printf("%u ", i+1)); + print_uidl(i); + } + printed(printf(".\r\n")); + fflush(stdout); +} + +static void acctout(const char *disc) +{ + static const char msg2[]=", user="; + static const char msg3[]=", ip=["; + static const char msgport[]="], port=["; + static const char msg4[]="], top="; + static const char msg5[]=", retr="; + static const char msg6[]=", time="; + static const char msg7[]=", stls=1"; + static const char msgAR[]=", rcvd="; + static const char msgAS[]=", sent="; + + char num1[NUMBUFSIZE]; + char num2[NUMBUFSIZE]; + char num3[NUMBUFSIZE]; + char numAR[NUMBUFSIZE]; + char numAS[NUMBUFSIZE]; + + char *p; + const char *q; + + libmail_str_size_t(top_count, num1); + libmail_str_size_t(retr_count, num2); + libmail_str_time_t(time(NULL)-start_time, num3); + libmail_str_size_t(bytes_received_count, numAR); + libmail_str_size_t(bytes_sent_count, numAS); + + p=malloc(strlen(authaddr)+strlen(remoteip)+strlen(remoteport)+strlen(disc)+ + strlen(num1)+strlen(num2)+strlen(num3)+ + strlen(numAR)+strlen(numAS)+200); /* Should be enough */ + + strcpy(p, disc); + strcat(p, msg2); + strcat(p, authaddr); + strcat(p, msg3); + strcat(p, remoteip); + strcat(p, msgport); + strcat(p, remoteport); + strcat(p, msg4); + strcat(p, num1); + strcat(p, msg5); + strcat(p, num2); + strcat(p, msgAR); + strcat(p, numAR); + strcat(p, msgAS); + strcat(p, numAS); + strcat(p, msg6); + strcat(p, num3); + + if ((q=getenv("POP3_TLS")) && atoi(q)) + strcat(p, msg7); + + strcat(p, "\n"); + if (write(2, p, strlen(p)) < 0) + ; /* make gcc shut up */ + free(p); +} + +static RETSIGTYPE bye(int signum) +{ + acctout("INFO: TIMEOUT"); + exit(0); +#if RETSIGTYPE != void + return (0); +#endif +} + +static void loop() +{ +char buf[BUFSIZ]; +char *p; +int c; + + signal(SIGALRM, bye); + while (alarm(300), fgets(buf, sizeof(buf), stdin)) + { + bytes_received_count += strlen(buf); + + alarm(0); + if ((p=strchr(buf, '\n')) != 0) + *p=0; + else while ((c=getc(stdin)) >= 0 && c != '\n') + ; + p=strtok(buf, " \t\r"); + if (!p) p=""; + + mkupper(p); + if (strcmp(p, "QUIT") == 0) + { + printed(printf("+OK Bye-bye.\r\n")); + fflush(stdout); + cleanup(); + acctout("INFO: LOGOUT"); + return; + } + + if (strcmp(p, "STAT") == 0) + { + do_stat(); + continue; + } + + if (strcmp(p, "LIST") == 0) + { + do_list(strtok(NULL, " \t\r")); + continue; + } + + if (strcmp(p, "RETR") == 0) + { + unsigned i; + + if ((i=getmsgnum(strtok(NULL, " \t\r"))) == 0) + continue; + + do_retr(i-1, 0); + continue; + } + + if (strcmp(p, "CAPA") == 0) + { + pop3dcapa(); + continue; + } + + if (strcmp(p, "DELE") == 0) + { + unsigned i; + + if ((i=getmsgnum(strtok(NULL, " \t\r"))) == 0) + continue; + + msglist_a[i-1]->isdeleted=1; + printed(printf("+OK Deleted.\r\n")); + fflush(stdout); + continue; + } + + if (strcmp(p, "NOOP") == 0) + { + printed(printf("+OK Yup.\r\n")); + fflush(stdout); + continue; + } + + if (strcmp(p, "RSET") == 0) + { + unsigned i; + + for (i=0; i<msglist_cnt; i++) + msglist_a[i]->isdeleted=0; + printed(printf("+OK Resurrected.\r\n")); + fflush(stdout); + continue; + } + + if (strcmp(p, "TOP") == 0) + { + unsigned i, j; + const char *q; + + if ((i=getmsgnum(strtok(NULL, " \t\r"))) == 0) + continue; + + q=strtok(NULL, " \t\r"); + + if (!q) goto error; + + j=atoi(q); + do_retr(i-1, &j); + continue; + } + + if (strcmp(p, "UIDL") == 0) + { + do_uidl(strtok(NULL, " \t\r")); + continue; + } + +error: + printed(printf("-ERR Invalid command.\r\n")); + fflush(stdout); + } + acctout("INFO: DISCONNECTED"); +} + +/* Like every good Maildir reader, we purge the tmp subdirectory */ + +static void purgetmp() +{ +DIR *p=opendir("tmp"); +time_t t; +struct dirent *de; +struct stat stat_buf; +char *n; + + if (!p) return; + time (&t); + t -= 48L * 60L * 60L; + + while ((de=readdir(p)) != 0) + { + if (de->d_name[0] == '.') continue; + n=malloc(strlen(de->d_name)+5); + if (!n) continue; + strcat(strcpy(n, "tmp/"), de->d_name); + if (stat(n, &stat_buf) == 0 && stat_buf.st_mtime < t) + unlink(n); + free(n); + } + closedir(p); +} + + +#include <unistd.h> + +int main(int argc, char **argv) +{ +char *p; + +#ifdef HAVE_SETVBUF_IOLBF + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); +#endif + time(&start_time); + + if ((authaddr=getenv("AUTHADDR")) == NULL || + *authaddr == 0) + { + authaddr=getenv("AUTHENTICATED"); + if (authaddr == NULL || *authaddr == 0) + authaddr="nobody"; + } + + if ((remoteip=getenv("TCPREMOTEIP")) == NULL) + remoteip="127.0.0.1"; + + if ((remoteport=getenv("TCPREMOTEPORT")) == NULL) + remoteport="0"; + + { + struct stat buf; + + if ( stat(".", &buf) < 0 || buf.st_mode & S_ISVTX) + { + fprintf(stderr, "INFO: LOCKED, user=%s, ip=[%s], port=[%s]\n", + authaddr, remoteip, remoteport); + printed(printf("-ERR Your account is temporarily unavailable (+t bit set on home directory).\r\n")); + exit(0); + } + } + + if (argc > 1) + p=argv[1]; + else + p=getenv("MAILDIR"); + + if (!p) + p="./Maildir"; + + if (chdir(p)) + { + fprintf(stderr, "chdir %s: %s\n", p, strerror(errno)); + printed(printf("-ERR chdir %s failed\n", p)); + fflush(stdout); + exit(1); + } + + maildir_loginexec(); + + if (auth_getoptionenvint("disablepop3")) + { + printed(printf("-ERR POP3 access disabled for this account.\r\n")); + fflush(stdout); + exit(1); + } + + if ( auth_getoptionenvint("disableinsecurepop3") + && ((p=getenv("POP3_TLS")) == NULL || !atoi(p))) + { + printed(printf("-ERR POP3 access disabled via insecure connection.\r\n")); + fflush(stdout); + exit(1); + } + + fprintf(stderr, "INFO: LOGIN, user=%s, ip=[%s], port=[%s]\n", + authaddr, + remoteip, + remoteport); + fflush(stderr); + + msglist_cnt=0; + msglist_l=0; + msglist_a=0; + purgetmp(); + maildir_getnew(".", INBOX, NULL, NULL); + if (scancur()) + { + printed(printf("-ERR Maildir invalid (no 'cur' directory)\r\n")); + return (0); + } + sortmsgs(); + printed(printf("+OK logged in.\r\n")); + fflush(stdout); + loop(); + return (0); +} diff --git a/imap/pop3login.c b/imap/pop3login.c new file mode 100644 index 0000000..2c6e599 --- /dev/null +++ b/imap/pop3login.c @@ -0,0 +1,529 @@ +/* +** Copyright 1998 - 2008 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#undef PACKAGE +#undef VERSION +#include "config.h" +#endif +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <ctype.h> +#include <fcntl.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <netinet/in.h> +#include <netdb.h> +#include "waitlib/waitlib.h" +#include "proxy.h" + +#include <courierauth.h> +#include <courierauthdebug.h> +#include <courierauthsasl.h> +#include "tcpd/spipe.h" +#include "numlib/numlib.h" +#include "tcpd/tlsclient.h" + + +extern void pop3dcapa(); +extern int have_starttls(); +extern int tls_required(); +extern const char *pop3_externalauth(); + +static const char *pop3d; +static const char *defaultmaildir; + +static int starttls() +{ + char *argvec[4]; + + char localfd_buf[NUMBUFSIZE+40]; + char buf2[NUMBUFSIZE]; + struct couriertls_info cinfo; + int pipefd[2]; + + if (libmail_streampipe(pipefd)) + { + printf("-ERR libmail_streampipe() failed.\r\n"); + return (-1); + } + + couriertls_init(&cinfo); + fcntl(pipefd[0], F_SETFD, FD_CLOEXEC); + + strcat(strcpy(localfd_buf, "-localfd="), + libmail_str_size_t(pipefd[1], buf2)); + + argvec[0]=localfd_buf; + argvec[1]="-tcpd"; + argvec[2]="-server"; + argvec[3]=NULL; + + printf("+OK Begin SSL/TLS negotiation now.\r\n"); + fflush(stdout); + + if (couriertls_start(argvec, &cinfo)) + { + close(pipefd[0]); + close(pipefd[1]); + printf("-ERR STARTTLS failed: %s\r\n", + cinfo.errmsg); + couriertls_destroy(&cinfo); + return (-1); + } + + couriertls_export_subject_environment(&cinfo); + couriertls_destroy(&cinfo); + + close(pipefd[1]); + close(0); + close(1); + if (dup(pipefd[0]) != 0 || dup(pipefd[0]) != 1) + { + perror("dup"); + exit(1); + } + close(pipefd[0]); + + putenv("POP3_STARTTLS=NO"); + putenv("POP3_TLS_REQUIRED=0"); + putenv("POP3_TLS=1"); + return (0); +} + +static char *authresp(const char *s, void *dummy) +{ +char *p; +char buf[BUFSIZ]; + + printf("+ %s\r\n", s); + fflush(stdout); + + if (fgets(buf, sizeof(buf), stdin) == 0) return (0); + if ((p=strchr(buf, '\n')) == 0) return (0); + if (p > buf && p[-1] == '\r') --p; + *p=0; + + p=strdup(buf); + if (!p) + { + perror("malloc"); + return (0); + } + return (p); +} + +struct pop3proxyinfo { + const char *uid; + const char *pwd; +}; + +static int login_pop3(int, const char *, void *); + +static int login_callback(struct authinfo *ainfo, void *dummy) +{ + int rc; + char *p; + + p=getenv("POP3_PROXY"); + + if (p && atoi(p)) + { + if (ainfo->options == NULL || + (p=auth_getoption(ainfo->options, + "mailhost")) == NULL) + { + fprintf(stderr,"WARN: proxy enabled, but no proxy" + " host defined for %s\n", + ainfo->address); + + /* Fallthru to account login */ + + } + else if (ainfo->clearpasswd == NULL) + { + free(p); + fprintf(stderr, "WARN: proxy enabled, but no password" + " for %s\n", ainfo->address); + return -1; + } + else + { + struct proxyinfo pi; + struct pop3proxyinfo ppi; + struct servent *se; + int fd; + + se=getservbyname("pop3", NULL); + + pi.host=p; + pi.port=se ? ntohs(se->s_port):110; + + ppi.uid=ainfo->address; + ppi.pwd=ainfo->clearpasswd; + + pi.connected_func=login_pop3; + pi.void_arg=&ppi; + + if ((fd=connect_proxy(&pi)) < 0) + { + free(p); + return -1; + } + free(p); + if (fd > 0) + { + alarm(0); + proxyloop(fd); + exit(0); + } + + /* FALLTHRU */ + } + } + + rc=auth_callback_default(ainfo); + + if (rc == 0) + { + char *p=malloc(sizeof("OPTIONS=") + strlen(ainfo->options ? + ainfo->options:"")); + + if (p) + { + strcat(strcpy(p, "OPTIONS="), + ainfo->options ? ainfo->options:""); + putenv(p); + + p=malloc(sizeof("AUTHENTICATED=")+ + strlen(ainfo->address)); + if (p) + { + strcat(strcpy(p, "AUTHENTICATED="), + ainfo->address); + putenv(p); + + alarm(0); + execl(pop3d, pop3d, + ainfo->maildir ? + ainfo->maildir:defaultmaildir, + NULL); + fprintf(stderr, "ERR: exec(%s) failed!!\n", + pop3d); + } + } + } + + return (rc); +} + +int main(int argc, char **argv) +{ +char *user=0; +char *p; +char buf[BUFSIZ]; +int c; +const char *ip=getenv("TCPREMOTEIP"); +char authservice[40]; +char *q ; + +#ifdef HAVE_SETVBUF_IOLBF + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); +#endif + + if (!ip || !*ip) + { + ip="127.0.0.1"; + } + + if (argc != 3) + { + printf("-ERR pop3login requires exactly two arguments.\r\n"); + fflush(stdout); + exit(1); + } + + pop3d=argv[1]; + defaultmaildir=argv[2]; + + courier_authdebug_login_init(); + + fprintf(stderr, "DEBUG: Connection, ip=[%s]\n", ip); + printf("+OK Hello there.\r\n"); + + fflush(stdout); + fflush(stderr); + alarm(60); + while (fgets(buf, sizeof(buf), stdin)) + { + c=1; + for (p=buf; *p; p++) + { + if (*p == '\n') + break; + + if (*p == ' ' || *p == '\t') c=0; + if (c) + *p=toupper((int)(unsigned char)*p); + } + + if (*p) + *p=0; + else while ((c=getchar()) != EOF && c != '\n') + ; + p=strtok(buf, " \t\r"); + if (p) + { + courier_authdebug_login( 1, "command=%s", p ); + + if ( strcmp(p, "QUIT") == 0) + { + fprintf(stderr, "INFO: LOGOUT, ip=[%s]\n", + ip); + fflush(stderr); + printf("+OK Better luck next time.\r\n"); + fflush(stdout); + break; + } + + if ( strcmp(p, "USER") == 0) + { + if (tls_required()) + { + printf("-ERR TLS required to log in.\r\n"); + fflush(stdout); + continue; + } + + p=strtok(0, "\r\n"); + if (p) + { + if (user) free(user); + if ((user=malloc(strlen(p)+1)) == 0) + { + printf("-ERR Server out of memory, aborting connection.\r\n"); + fflush(stdout); + perror("malloc"); + exit(1); + } + strcpy(user, p); + printf("+OK Password required.\r\n"); + fflush(stdout); + continue; + } + } else if (strcmp(p, "CAPA") == 0) + { + pop3dcapa(); + continue; + } else if (strcmp(p, "STLS") == 0) + { + if (!have_starttls()) + { + printf("-ERR TLS support not available.\r\n"); + fflush(stdout); + continue; + } + starttls(); + fflush(stdout); + continue; + } else if (strcmp(p, "AUTH") == 0) + { + char *authtype, *authdata; + char *method=strtok(0, " \t\r"); + + if (tls_required()) + { + printf("-ERR TLS required to log in.\r\n"); + fflush(stdout); + continue; + } + + if (method) + { + char *initreply=strtok(0, " \t\r"); + int rc; + char *p; + + for (p=method; *p; p++) + *p=toupper(*p); + + if (initreply && + strcmp(initreply, "=") == 0) + initreply=""; + + rc=auth_sasl_ex(method, initreply, + pop3_externalauth(), + authresp, + NULL, + &authtype, + &authdata); + + if (rc == 0) + { + strcat(strcpy(authservice, "AUTHSERVICE"),getenv("TCPLOCALPORT")); + q=getenv(authservice); + if (!q || !*q) + q="pop3"; + + rc=auth_generic(q, + authtype, + authdata, + login_callback, + NULL); + free(authtype); + free(authdata); + } + + courier_safe_printf("INFO: LOGIN " + "FAILED, method=%s, ip=[%s]", + method, ip); + if (rc == AUTHSASL_ABORTED) + printf("-ERR Authentication aborted.\r\n"); + else if (rc > 0) + { + perror("ERR: authentication error"); + printf("-ERR Temporary problem, please try again later\r\n"); + fflush(stdout); + exit(1); + } + else + { + sleep(5); + printf("-ERR Authentication failed.\r\n"); + } + + fflush(stdout); + continue; + } + } else if (strcmp(p, "PASS") == 0) + { + int rc; + + p=strtok(0, "\r\n"); + + if (!user || p == 0) + { + printf("-ERR USER/PASS required.\r\n"); + fflush(stdout); + continue; + } + + strcat(strcpy(authservice, "AUTHSERVICE"),getenv("TCPLOCALPORT")); + q=getenv(authservice); + if (!q || !*q) + q="pop3"; + + rc=auth_login(q, user, p, login_callback, NULL); + courier_safe_printf("INFO: LOGIN " + "FAILED, user=%s, ip=[%s]", + user, ip); + if (rc > 0) + { + perror("ERR: authentication error"); + printf("-ERR Temporary problem, please try again later\r\n"); + fflush(stdout); + exit(1); + } + sleep(5); + printf("-ERR Login failed.\r\n"); + fflush(stdout); + continue; + } + } + printf("-ERR Invalid command.\r\n"); + fflush(stdout); + } + fprintf(stderr, "DEBUG: Disconnected, ip=[%s]\n", ip); + exit(0); + return (0); +} + +static int login_pop3(int fd, const char *hostname, void *void_arg) +{ + struct pop3proxyinfo *ppi=(struct pop3proxyinfo *)void_arg; + struct proxybuf pb; + char linebuf[256]; + char *cmd; + + DPRINTF("Proxy connected to %s", hostname); + + memset(&pb, 0, sizeof(pb)); + + if (proxy_readline(fd, &pb, linebuf, sizeof(linebuf), 1) < 0) + return -1; + + DPRINTF("%s: %s", hostname, linebuf); + + if (linebuf[0] != '+') + { + fprintf(stderr, "WARN: Did not receive greeting from %s\n", + hostname); + return -1; + } + + cmd=malloc(strlen(ppi->uid) + strlen(ppi->pwd)+100); + /* Should be enough */ + + if (!cmd) + return -1; + + sprintf(cmd, "USER %s\r\n", ppi->uid); + + if (proxy_write(fd, hostname, cmd, strlen(cmd))) + { + free(cmd); + return -1; + } + + if (proxy_readline(fd, &pb, linebuf, sizeof(linebuf), 1) < 0) + { + free(cmd); + return -1; + } + + DPRINTF("%s: %s", hostname, linebuf); + + if (linebuf[0] != '+') + { + free(cmd); + fprintf(stderr, "WARN: Login userid rejected by %s\n", + hostname); + return -1; + } + + sprintf(cmd, "PASS %s\r\n", ppi->pwd); + + if (proxy_write(fd, hostname, cmd, strlen(cmd))) + { + free(cmd); + return -1; + } + + if (proxy_readline(fd, &pb, linebuf, sizeof(linebuf), 1) < 0) + { + free(cmd); + return -1; + } + + DPRINTF("%s: %s", hostname, linebuf); + + if (linebuf[0] != '+') + { + free(cmd); + fprintf(stderr, "WARN: Login password rejected by %s\n", + hostname); + return -1; + } + + free(cmd); + if (fcntl(1, F_SETFL, 0) < 0 || + (printf("+OK Connected to proxy server.\r\n"), fflush(stdout)) < 0) + return -1; + + return 0; +} diff --git a/imap/proxy.c b/imap/proxy.c new file mode 100644 index 0000000..3a73cf1 --- /dev/null +++ b/imap/proxy.c @@ -0,0 +1,721 @@ +/* +** Copyright 2004-2005 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <time.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> +#if HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#include <sys/time.h> +#if HAVE_POLL +#include <sys/poll.h> +#endif +#include <courierauthdebug.h> +#include "proxy.h" + + +static int checkhostname(const char *host) +{ + char hostbuf[256]; + const char *proxyhostname; + + if ((proxyhostname=getenv("PROXY_HOSTNAME")) != NULL) + { + if (strcmp(proxyhostname, host) == 0) + { + courier_authdebug_printf("Proxy host %s is me, normal login.", + host); + return -1; + } + } + + if (gethostname(hostbuf, sizeof(hostbuf))) + { + courier_authdebug_printf + ("gethostname failed: %s", + strerror(errno)); + return -1; + } + + if (strcmp(hostbuf, host) == 0) + { + courier_authdebug_printf("Proxy host %s is me, normal login.", + host); + return -1; + } + return 0; +} + +static int connect_host(struct proxyinfo *pi, const char *host); + +int connect_proxy(struct proxyinfo *pi) +{ + char *h=strdup(pi->host); + char *p, *q; + int fd; + + if (!h) + { + courier_authdebug_printf("%s", strerror(errno)); + return -1; + } + + for (p=h; *p;) + { + if (*p == ',') + { + ++p; + continue; + } + + for (q=p; *q; q++) + if (*q == ',') + break; + if (*q) + *q++=0; + + fd=connect_host(pi, p); + if (fd >= 0) + { + free(h); + return fd; + } + p=q; + } + free(h); + return -1; +} + + +static int proxyconnect(struct proxyinfo *pi, + int aftype, + void *addr, + size_t); + +#if HAVE_GETADDRINFO + +static int connect_host(struct proxyinfo *pi, const char *host) +{ + int fd; + char portbuf[40]; + int errcode; + struct addrinfo hints, *res, *p; + + if (checkhostname(host)) + return (0); + + sprintf(portbuf, "%d", pi->port); + memset(&hints, 0, sizeof(hints)); + hints.ai_family=PF_UNSPEC; + hints.ai_socktype=SOCK_STREAM; + + errcode=getaddrinfo(host, portbuf, &hints, &res); + + if (errcode) + { + courier_authdebug_printf + ("getaddrinfo on proxyhost %s failed: %s", + pi->host, gai_strerror(errcode)); + return (-1); + } + + for (p=res; p; p=p->ai_next) + { + if ((fd=proxyconnect(pi, p->ai_family, + p->ai_addr, + p->ai_addrlen)) + >= 0) + { + if ((*pi->connected_func)(fd, host, + pi->void_arg)) + { + close(fd); + courier_authdebug_printf + ("Failed: %s.", strerror(errno)); + continue; + } + freeaddrinfo(res); + return fd; + } + } + freeaddrinfo(res); + courier_authdebug_printf + ("Connection to proxyhost %s failed.", host); + return (-1); +} + +#else +static int connect_host(struct proxyinfo *pi, const char *host) +{ + struct hostent *he; + int i; + int fd; + + if (checkhostname(host)) + return (0); + + he=gethostbyname(host); + + if (he == NULL) + { + courier_authdebug_printf + ("gethostbyname on proxyhost %s failed.", + host); + return (-1); + } + + for (i=0; he->h_addr_list[i]; i++) + { + switch (he->h_addrtype) { + case AF_INET: + { + struct sockaddr_in sin; + + memset(&sin, 0, sizeof(sin)); + + sin.sin_family=AF_INET; + + memcpy(&sin.sin_addr, he->h_addr_list[i], + sizeof(sin.sin_addr)); + sin.sin_port=htons(pi->port); + + fd=proxyconnect(pi, PF_INET, &sin, + sizeof(sin)); + } + break; +#ifdef AF_INET6 + case AF_INET6: + { + struct sockaddr_in6 sin6; + + memset(&sin6, 0, sizeof(sin6)); + + sin6.sin6_family=AF_INET6; + + memcpy(&sin6.sin6_addr, he->h_addr_list[i], + sizeof(sin6.sin6_addr)); + + sin6.sin6_port=htons(pi->port); + fd=proxyconnect(pi, PF_INET6, &sin6, + sizeof(sin6)); + } + break; +#endif + default: + courier_authdebug_printf + ("Unknown address family type %d", + he->h_addrtype); + continue; + } + + if (fd >= 0) + { + if ((*pi->connected_func)(fd, host, + pi->void_arg)) + { + close(fd); + courier_authdebug_printf + ("Failed: %s.", strerror(errno)); + continue; + } + return fd; + } + } + courier_authdebug_printf + ("Connection to proxyhost %s failed.", host); + return (-1); +} +#endif + +#if HAVE_POLL + +static int proxy_waitfd(int fd, int waitWrite, const char *hostnamebuf) +{ + struct pollfd pfd; + + memset(&pfd, 0, sizeof(pfd)); + + pfd.fd=fd; + pfd.events= waitWrite ? POLLOUT:POLLIN; + + if (poll(&pfd, 1, 30 * 1000) < 0) + { + courier_authdebug_printf + ("Poll failed while waiting to connect to %s: %s", + hostnamebuf, strerror(errno)); + return -1; + } + + if (pfd.revents & (POLLOUT|POLLIN)) + return 0; + + courier_authdebug_printf + ("Timeout/error connecting to %s", hostnamebuf); + return -1; +} + +#else +static int proxy_waitfd(int fd, int waitWrite, const char *hostnamebuf) +{ + fd_set fds; + struct timeval tv; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec=30; + tv.tv_usec=0; + + if (select(fd+1, waitWrite ? NULL:&fds, + waitWrite ? &fds:NULL, NULL, &tv) < 0) + { + courier_authdebug_printf + ("Select failed while waiting to connect to %s: %s", + hostnamebuf, strerror(errno)); + + return -1; + } + + if (!FD_ISSET(fd, &fds)) + { + courier_authdebug_printf + ("Timeout connecting to %s", hostnamebuf); + return -1; + } + + return 0; +} +#endif + +static int proxyconnect(struct proxyinfo *pi, + int pf, + void *addr, + size_t addrLen) +{ + int fd; + char hostnamebuf[256]; + int errcode; + socklen_t errcode_l; + + struct sockaddr *sa=(struct sockaddr *)addr; + + switch (sa->sa_family) { + case AF_INET: + { + struct sockaddr_in *sin=(struct sockaddr_in *)sa; + + strcpy(hostnamebuf, inet_ntoa(sin->sin_addr)); + } + break; + +#ifdef AF_INET6 + case AF_INET6: + { + struct sockaddr_in6 *sin6= + (struct sockaddr_in6 *)sa; + + if (inet_ntop(AF_INET6, &sin6->sin6_addr, + hostnamebuf, + sizeof(hostnamebuf)) == NULL) + strcpy(hostnamebuf, "inet_ntop() failed"); + } + break; +#endif + } + + + fd=socket(pf, SOCK_STREAM, 0); + + if (fd < 0) + { + courier_authdebug_printf("socket: %s", strerror(errno)); + return (-1); + } + + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0 || + fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) + { + close(fd); + courier_authdebug_printf("fcntl(socket): %s", strerror(errno)); + } + + if (connect(fd, addr, addrLen) == 0) + return fd; + + if (errno != EINPROGRESS) + { + courier_authdebug_printf + ("Proxy connection to %s failed: %s", + hostnamebuf, strerror(errno)); + close(fd); + return -1; + } + + if (proxy_waitfd(fd, 1, hostnamebuf)) + { + close(fd); + return -1; + } + + errcode_l=sizeof(errcode); + + if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &errcode_l) < 0) + { + courier_authdebug_printf + ("getsockopt failed: %s", strerror(errno)); + close(fd); + return -1; + } + + if (errcode) + { + courier_authdebug_printf + ("Proxy connection to %s failed: %s", hostnamebuf, + strerror(errcode)); + close(fd); + return -1; + } + + return fd; +} + +static int proxy_getch(int fd, struct proxybuf *pb) +{ + if (pb->bufleft == 0) + { + int n; + + if (proxy_waitfd(fd, 0, "server")) + return -1; + + pb->bufptr=pb->buffer; + + n=read(fd, pb->buffer, sizeof(pb->buffer)); + + if (n < 0) + { + courier_authdebug_printf + ("Connection error: %s", + strerror(errno)); + return -1; + } + + if (n == 0) + { + courier_authdebug_printf + ("Connection closed by remote host"); + return -1; + } + + pb->bufleft=(size_t)n; + } + --pb->bufleft; + return ((int)(unsigned char)*pb->bufptr++); +} + +int proxy_readline(int fd, struct proxybuf *pb, + char *linebuf, + size_t linebuflen, + int imapmode) +{ + size_t i; + int ch; + int prevch; + + i=0; + + ch=0; + + do + { + prevch=ch; + + ch=proxy_getch(fd, pb); + + if (ch < 0) + return -1; + + if (i < linebuflen) + linebuf[i++]=(char)ch; + } while (ch != '\n' || (imapmode && prevch != '\r')); + + if (i) + linebuf[--i]=0; + + if (i && linebuf[i-1] == '\r') + linebuf[--i]=0; + + DPRINTF("Received: %s", linebuf); + + return 0; +} + +int proxy_write(int fd, const char *hostname, + const char *buf, size_t buf_len) +{ + DPRINTF("Sending: %s", buf); + + while (buf_len) + { + int n; + + if (proxy_waitfd(fd, 1, hostname)) + return -1; + + n=write(fd, buf, buf_len); + + if (n < 0) + { + courier_authdebug_printf + ("Error sending to %s: %s", + hostname, strerror(errno)); + return -1; + } + + if (n == 0) + { + courier_authdebug_printf + ("Connection close by %s", hostname); + return -1; + } + + buf_len -= n; + buf += n; + } + return 0; +} + +#if HAVE_POLL +void proxyloop(int fd) +{ + char stdin_buf[BUFSIZ]; + char stdout_buf[BUFSIZ]; + + char *stdin_ptr=NULL; + char *stdout_ptr=NULL; + size_t stdin_left=0; + size_t stdout_left=0; + int n; + + struct pollfd pfd[2]; + + if (fcntl(0, F_SETFL, O_NONBLOCK) || + fcntl(1, F_SETFL, O_NONBLOCK)) + { + courier_authdebug_printf("fcntl: %s", + strerror(errno)); + return; + } + + do + { + memset(&pfd, 0, sizeof(pfd)); + + if (stdin_left == 0) + { + pfd[0].fd=0; + pfd[0].events=POLLIN; + } + else + { + pfd[0].fd=fd; + pfd[0].events=POLLOUT; + } + + if (stdout_left == 0) + { + pfd[1].fd=fd; + pfd[1].events=POLLIN; + } + else + { + pfd[1].fd=1; + pfd[1].events=POLLOUT; + } + + n=1; + + if (poll(pfd, 2, -1) < 0) + { + courier_authdebug_printf("poll: %s", + strerror(errno)); + continue; + } + + if (stdin_left == 0) + { + if (pfd[0].revents) + { + n=read(0, stdin_buf, sizeof(stdin_buf)); + + if (n > 0) + { + stdin_ptr=stdin_buf; + stdin_left=(size_t)n; + } + } + } + else if (pfd[0].revents) + { + n=write(fd, stdin_ptr, stdin_left); + + if (n > 0) + { + stdin_ptr += n; + stdin_left -= n; + } + } + + if (n > 0) + { + if (stdout_left == 0) + { + if (pfd[1].revents) + { + n=read(fd, stdout_buf, + sizeof(stdout_buf)); + + if (n > 0) + { + stdout_ptr=stdout_buf; + stdout_left=(size_t)n; + } + } + } else if (pfd[1].revents) + { + n=write(1, stdout_ptr, stdout_left); + + if (n > 0) + { + stdout_ptr += n; + stdout_left -= n; + } + } + } + } while (n > 0); + + if (n < 0) + courier_authdebug_printf("%s", strerror(errno)); +} + +#else +void proxyloop(int fd) +{ + char stdin_buf[BUFSIZ]; + char stdout_buf[BUFSIZ]; + + char *stdin_ptr=NULL; + char *stdout_ptr=NULL; + size_t stdin_left=0; + size_t stdout_left=0; + int n; + + fd_set fdr, fdw; + + if (fcntl(0, F_SETFL, O_NONBLOCK) || + fcntl(1, F_SETFL, O_NONBLOCK)) + { + courier_authdebug_printf("fcntl: %s", + strerror(errno)); + return; + } + + do + { + FD_ZERO(&fdr); + FD_ZERO(&fdw); + + if (stdin_left == 0) + FD_SET(0, &fdr); + else + FD_SET(fd, &fdw); + + if (stdout_left == 0) + FD_SET(fd, &fdr); + else + FD_SET(1, &fdw); + + n=1; + + if (select(fd+1, &fdr, &fdw, NULL, NULL) < 0) + { + courier_authdebug_printf("select: %s", + strerror(errno)); + continue; + } + + if (stdin_left == 0) + { + if (FD_ISSET(0, &fdr)) + { + n=read(0, stdin_buf, sizeof(stdin_buf)); + + if (n > 0) + { + stdin_ptr=stdin_buf; + stdin_left=(size_t)n; + } + } + } + else if (FD_ISSET(fd, &fdw)) + { + n=write(fd, stdin_ptr, stdin_left); + + if (n > 0) + { + stdin_ptr += n; + stdin_left -= n; + } + } + + if (n > 0) + { + if (stdout_left == 0) + { + if (FD_ISSET(fd, &fdr)) + { + n=read(fd, stdout_buf, + sizeof(stdout_buf)); + + if (n > 0) + { + stdout_ptr=stdout_buf; + stdout_left=(size_t)n; + } + } + } else if (FD_ISSET(1, &fdw)) + { + n=write(1, stdout_ptr, stdout_left); + + if (n > 0) + { + stdout_ptr += n; + stdout_left -= n; + } + } + } + } while (n > 0); + + if (n < 0) + courier_authdebug_printf("%s", strerror(errno)); +} +#endif diff --git a/imap/proxy.h b/imap/proxy.h new file mode 100644 index 0000000..85566b5 --- /dev/null +++ b/imap/proxy.h @@ -0,0 +1,34 @@ +#ifndef proxy_h +#define proxy_h + +/* +** Copyright 2004 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +struct proxyinfo { + const char *host; + int port; + + int (*connected_func)(int, const char *, void *); + void *void_arg; +}; + +int connect_proxy(struct proxyinfo *); +void proxyloop(int); + +struct proxybuf { + char buffer[256]; + char *bufptr; + size_t bufleft; +}; + +int proxy_readline(int fd, struct proxybuf *pb, + char *linebuf, + size_t linebuflen, + int imapmode); +int proxy_write(int fd, const char *hostname, + const char *buf, size_t buf_len); + +#endif diff --git a/imap/search.c b/imap/search.c new file mode 100644 index 0000000..e532dcd --- /dev/null +++ b/imap/search.c @@ -0,0 +1,1066 @@ +/* +** Copyright 1998 - 2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <ctype.h> +#include <time.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "rfc822/rfc822.h" +#include "rfc822/rfc822hdr.h" +#include "rfc822/rfc2047.h" +#include "rfc2045/rfc2045.h" +#include "unicode/unicode.h" +#include "numlib/numlib.h" +#include "searchinfo.h" +#include "imapwrite.h" +#include "imaptoken.h" +#include "imapscanclient.h" + + +extern time_t rfc822_parsedt(const char *); +extern struct imapscaninfo current_maildir_info; +extern char *current_mailbox; + +extern int get_flagname(const char *, struct imapflags *); +extern void get_message_flags( struct imapscanmessageinfo *, + char *, struct imapflags *); +extern int valid_keyword(const char *kw); + +static void fill_search_preparse(struct searchinfo *); + +static void fill_search_veryquick(struct searchinfo *, + unsigned long, struct imapflags *); + +static void fill_search_quick(struct searchinfo *, + unsigned long, struct stat *); + +static void fill_search_header(struct searchinfo *, + const char *, + struct rfc2045 *, FILE *, + struct imapscanmessageinfo *); + +static void fill_search_body(struct searchinfo *, + struct rfc2045 *, FILE *, + struct imapscanmessageinfo *); + +static int search_evaluate(struct searchinfo *); + +static void search_callback(struct searchinfo *, struct searchinfo *, int, + unsigned long, void *); + +/* +** search_internal() does the main heavylifting of searching the +** maildir for qualifying messages. It calls a callback function +** when a matching message is found. +** +** For a plain SEARCH, the callback function merely prints the message +** number. +*/ + +void dosearch(struct searchinfo *si, struct searchinfo *sihead, + const char *charset, int isuid) +{ + search_internal(si, sihead, charset, isuid, search_callback, 0); +} + +static void search_callback(struct searchinfo *si, struct searchinfo *sihead, + int isuid, unsigned long i, void *dummy) +{ + writes(" "); + writen(isuid ? current_maildir_info.msgs[i].uid:i+1); +} + +static void search_oneatatime(struct searchinfo *si, + unsigned long i, + struct searchinfo *sihead, + const char *charset, int isuid, + void (*callback_func)(struct searchinfo *, + struct searchinfo *, int, + unsigned long, void *), + void *voidarg); + +static void search_byKeyword(struct searchinfo *tree, + struct searchinfo *keyword, + struct searchinfo *sihead, + const char *charset, int isuid, + void (*callback_func)(struct searchinfo *, + struct searchinfo *, int, + unsigned long, void *), + void *voidarg); + +void search_internal(struct searchinfo *si, struct searchinfo *sihead, + const char *charset, int isuid, + void (*callback_func)(struct searchinfo *, + struct searchinfo *, int, + unsigned long, void *), + void *voidarg) +{ + unsigned long i; + struct searchinfo *p; + + for (p=sihead; p; p=p->next) + fill_search_preparse(p); + + /* Shortcuts for keyword-based searches */ + + if (si->type == search_msgkeyword && si->bs == NULL && si->ke) + search_byKeyword(NULL, si, sihead, charset, isuid, + callback_func, voidarg); + else if (si->type == search_and && + si->a->type == search_msgkeyword && si->a->bs == NULL + && si->a->ke) + search_byKeyword(si->b, si->a, sihead, charset, isuid, + callback_func, voidarg); + else for (i=0; i<current_maildir_info.nmessages; i++) + search_oneatatime(si, i, sihead, charset, isuid, + callback_func, voidarg); +} + +static void search_byKeyword(struct searchinfo *tree, + struct searchinfo *keyword, + struct searchinfo *sihead, + const char *charset, int isuid, + void (*callback_func)(struct searchinfo *, + struct searchinfo *, int, + unsigned long, void *), + void *voidarg) +{ + struct libmail_kwMessageEntry *kme; + + for (kme=keyword->ke->firstMsg; kme; kme=kme->keywordNext) + { + unsigned long n=kme->libmail_kwMessagePtr->u.userNum; + if (!tree) + { + (*callback_func)(keyword, keyword, isuid, n, voidarg); + continue; + } + + search_oneatatime(tree, n, sihead, charset, isuid, + callback_func, voidarg); + } +} + +/* +** Evaluate the search tree for a given message. +*/ + +static void search_oneatatime(struct searchinfo *si, + unsigned long i, + struct searchinfo *sihead, + const char *charset, int isuid, + void (*callback_func)(struct searchinfo *, + struct searchinfo *, int, + unsigned long, void *), + void *voidarg) +{ +struct searchinfo *p; +struct imapflags flags; +int fd; +FILE *fp; +struct stat stat_buf; +int rc; + + { + for (p=sihead; p; p=p->next) + p->value= -1; /* Search result unknown */ + + /* First, see if non-content search will be sufficient */ + + get_message_flags(current_maildir_info.msgs+i, 0, &flags); + + for (p=sihead; p; p=p->next) + fill_search_veryquick(p, i, &flags); + + if ((rc=search_evaluate(si)) >= 0) + { + if (rc > 0) + (*callback_func)(si, sihead, isuid, i, + voidarg); + return; + } + + fd=imapscan_openfile(current_mailbox, + ¤t_maildir_info, i); + if (fd < 0) return; + + if ((fp=fdopen(fd, "r")) == 0) + write_error_exit(0); + + if (fstat(fileno(fp), &stat_buf)) + { + fclose(fp); + return; + } + + /* First, see if non-content search will be sufficient */ + + for (p=sihead; p; p=p->next) + fill_search_quick(p, i, &stat_buf); + + if ((rc=search_evaluate(si)) < 0) + { + /* No, search the headers then */ + /* struct rfc2045 *rfcp=rfc2045_fromfp(fp); */ + struct rfc2045 *rfcp=rfc2045header_fromfp(fp); + + fill_search_header(sihead, charset, rfcp, fp, + current_maildir_info.msgs+i); + rc=search_evaluate(si); + rfc2045_free(rfcp); + + if (rc < 0) + { + /* Ok, search message contents */ + struct rfc2045 *rfcp=rfc2045_fromfp(fp); + + fill_search_body(sihead, rfcp, fp, + current_maildir_info.msgs+i); + + /* + ** If there are still UNKNOWN nodes, change + ** them to fail. + */ + + for (p=sihead; p; p=p->next) + if (p->value < 0) + p->value=0; + + rc=search_evaluate(si); + rfc2045_free(rfcp); + } + /* rfc2045_free(rfcp); */ + } + + if (rc > 0) + { + (*callback_func)(si, sihead, isuid, i, voidarg); + } + fclose(fp); + close(fd); + } +} + +/* Check if the given index is included in the specified message set */ + +static int is_in_set(const char *msgset, unsigned long n) +{ +unsigned long i, j; + + while (isdigit((int)(unsigned char)*msgset)) + { + i=0; + while (isdigit((int)(unsigned char)*msgset)) + { + i=i*10 + (*msgset++-'0'); + } + if (*msgset != ':') + j=i; + else + { + j=0; + ++msgset; + if (*msgset == '*') + { + ++msgset; + /* + ** Ok, we don't really need to know the upper + ** end, just hack it. + */ + j=i; + if (j < n) + j=n; + } + else + while (isdigit((int)(unsigned char)*msgset)) + { + j=j*10 + (*msgset++-'0'); + } + } + if (n >= i && n <= j) return (1); + if (*msgset == 0 || *msgset++ != ',') break; + } + return (0); +} + +/* +** Search date comparisons compare the dates only, not the time. +** We convert all timestamps to midnight GMT on their respective dates. +** Use convenient RFC822 functions for that purpose. +*/ + +static time_t decode_date(char *p) +{ +char *s=malloc(strlen(p)+sizeof(" 00:00:00")); +unsigned i; +time_t t; + + if (!s) write_error_exit(0); + + /* Convert to format rfc822_parsedt likes */ + + for (i=1; p[i] != ' '; i++) + { + if (!p[i]) break; + } + memcpy(s, p, i); + strcpy(s+i, " 00:00:00"); + while (i) + { + if (s[--i] == '-') + s[i]=' '; + } + + t=rfc822_parsedt(s); + free(s); + return (t); +} + +/* Given a time_t that falls on, say, 3-Aug-1999 9:50:43 local time, +** calculate the time_t for midnight 3-Aug-1999 UTC. Search date comparisons +** are done against midnight UTCs */ + +static time_t timestamp_to_day(time_t t) +{ +char buf1[60], buf2[80]; + + rfc822_mkdate_buf(t, buf1); /* Converts to local time */ + (void)strtok(buf1, " "); /* Skip weekday */ + strcpy(buf2, strtok(0, " ")); + strcat(buf2, " "); + strcat(buf2, strtok(0, " ")); + strcat(buf2, " "); + strcat(buf2, strtok(0, " ")); + strcat(buf2, " 00:00:00"); + return (rfc822_parsedt(buf2)); +} + +static char *timestamp_for_sorting(time_t t) +{ +struct tm *tm=localtime(&t); +char buf[200]; + + buf[0]=0; + if ( strftime(buf, sizeof(buf), "%Y.%m.%d.%H.%M.%S", tm) == 0) + buf[0]=0; + return (my_strdup(buf)); +} + +static void fill_search_preparse(struct searchinfo *p) +{ + switch (p->type) { + case search_msgflag: + { + struct imapflags flags; + + memset(&flags, 0, sizeof(flags)); + p->ke=NULL; + + if (get_flagname(p->as, &flags) == 0) + { + p->bs=malloc(sizeof(flags)); + + if (!p->bs) + write_error_exit(0); + + memcpy(p->bs, &flags, sizeof(flags)); + } + } + break; + + case search_msgkeyword: + p->ke=NULL; + if (valid_keyword(p->as)) + p->ke=libmail_kweFind(current_maildir_info + .keywordList, + p->as, 0); + break; + default: + break; + } +} + +/* Evaluate non-content search nodes */ + +static void fill_search_veryquick(struct searchinfo *p, + unsigned long msgnum, struct imapflags *flags) +{ + switch (p->type) { + case search_msgflag: + { + struct imapflags *f=(struct imapflags *)p->bs; + + p->value=0; + if (strcmp(p->as, "\\RECENT") == 0 && + current_maildir_info.msgs[msgnum].recentflag) + p->value=1; + + if (f) + { + if (f->seen && flags->seen) + p->value=1; + if (f->answered && flags->answered) + p->value=1; + if (f->deleted && flags->deleted) + p->value=1; + if (f->flagged && flags->flagged) + p->value=1; + if (f->drafts && flags->drafts) + p->value=1; + } + break; + } + + case search_msgkeyword: + p->value=0; + if (p->ke) + { + struct libmail_kwMessage *km= + current_maildir_info.msgs[msgnum] + .keywordMsg; + struct libmail_kwMessageEntry *kme; + + for (kme=km ? km->firstEntry:NULL; + kme; kme=kme->next) + if (strcasecmp(keywordName(kme-> + libmail_keywordEntryPtr), + keywordName(p->ke))==0) + { + p->value=1; + break; + } + } + break; + case search_messageset: + if (is_in_set(p->as, msgnum+1)) + p->value=1; + else + p->value=0; + break; + case search_all: + p->value=1; + break; + case search_uid: + if (is_in_set(p->as, current_maildir_info.msgs[msgnum].uid)) + p->value=1; + else + p->value=0; + break; + case search_reverse: + p->value=1; + break; + default: + break; + } +} + +static void fill_search_quick(struct searchinfo *p, + unsigned long msgnum, struct stat *stat_buf) +{ + switch (p->type) { + case search_before: + p->value=0; + { + time_t t=decode_date(p->as); + + if (t && timestamp_to_day(stat_buf->st_mtime) < t) + p->value=1; + } + break; + case search_since: + p->value=0; + { + time_t t=decode_date(p->as); + + if (t && timestamp_to_day(stat_buf->st_mtime) >= t) + p->value=1; + } + break; + case search_on: + p->value=0; + { + time_t t=decode_date(p->as); + + if (t && timestamp_to_day(stat_buf->st_mtime) == t) + p->value=1; + } + break; + case search_smaller: + p->value=0; + { + unsigned long n; + + if (sscanf(p->as, "%lu", &n) > 0 && + stat_buf->st_size < n) + p->value=1; + } + break; + case search_larger: + p->value=0; + { + unsigned long n; + + if (sscanf(p->as, "%lu", &n) > 0 && + stat_buf->st_size > n) + p->value=1; + } + break; + case search_orderedsubj: + case search_references1: + case search_references2: + case search_references3: + case search_references4: + case search_arrival: + case search_cc: + case search_date: + case search_from: + case search_reverse: + case search_size: + case search_to: + + /* DUMMY nodes for SORT/THREAD. Make sure that the + ** dummy node is CLEARed */ + + if (p->as) + { + free(p->as); + p->as=0; + } + + if (p->bs) + { + free(p->bs); + p->bs=0; + } + + switch (p->type) { + case search_arrival: + p->as=timestamp_for_sorting(stat_buf->st_mtime); + p->value=1; + break; + case search_size: + { + char buf[NUMBUFSIZE], buf2[NUMBUFSIZE]; + char *q; + + libmail_str_size_t(stat_buf->st_size, buf); + sprintf(buf2, "%*s", (int)(sizeof(buf2)-1), buf); + for (q=buf2; *q == ' '; *q++='0') + ; + p->as=my_strdup(buf2); + p->value=1; + } + break; + default: + break; + } + break; + default: + break; + } +} + +/* Evaluate search results. Returns: 0 - false, 1 - true, -1 - unknown +** (partial search on message metadata, like size or flags, in hopes of +** preventing a search +** of message contents). +*/ + +static int search_evaluate(struct searchinfo *si) +{ +int rc, rc2; + + switch (si->type) { + case search_orderedsubj: /* DUMMIES for THREAD and SORT */ + case search_references1: + case search_references2: + case search_references3: + case search_references4: + case search_arrival: + case search_cc: + case search_date: + case search_from: + case search_reverse: + case search_size: + case search_to: + rc = search_evaluate(si->a); + if (rc == 0) return 0; + if (si->value < 0) return (-1); + break; + case search_not: + rc=search_evaluate(si->a); + if (rc >= 0) rc= 1-rc; + break; + case search_and: + rc=search_evaluate(si->a); + rc2=search_evaluate(si->b); + + rc= rc > 0 && rc2 > 0 ? 1: + rc == 0 || rc2 == 0 ? 0:-1; + break; + case search_or: + rc=search_evaluate(si->a); + rc2=search_evaluate(si->b); + + rc= rc > 0 || rc2 > 0 ? 1: + rc == 0 && rc2 == 0 ? 0:-1; + break; + default: + rc=si->value; + break; + } + return (rc); +} + +/* ------- header search -------- */ + +struct fill_search_header_info { + + struct searchinfo *si; + + char *utf8buf; + size_t utf8buflen; + size_t utf8bufsize; +}; + +static int headerfilter_func(const char *name, const char *value, void *arg); +static int fill_search_header_utf8(const char *, size_t, void *); +static int fill_search_header_done(const char *, void *); + +static void fill_search_header(struct searchinfo *si, + const char *charset, + struct rfc2045 *rfcp, FILE *fp, + struct imapscanmessageinfo *mi) +{ + struct searchinfo *sip; + struct rfc2045src *src; + struct rfc2045_decodemsgtoutf8_cb decodecb; + struct fill_search_header_info decodeinfo; + + /* Consider the following dummy nodes as evaluated */ + + for (sip=si; sip; sip=sip->next) + switch (sip->type) { + case search_orderedsubj: + case search_references1: + case search_references2: + case search_references3: + case search_references4: + case search_cc: + case search_date: + case search_from: + case search_to: + sip->value=1; + break; + default: + break; + } + + search_set_charset_conv(si, charset); + + src=rfc2045src_init_fd(fileno(fp)); + + if (!src) + return; + + memset(&decodecb, 0, sizeof(decodecb)); + memset(&decodeinfo, 0, sizeof(decodeinfo)); + + decodeinfo.si=si; + + decodecb.flags=RFC2045_DECODEMSG_NOBODY + | RFC2045_DECODEMSG_NOHEADERNAME; + decodecb.headerfilter_func=headerfilter_func; + decodecb.output_func=fill_search_header_utf8; + decodecb.headerdone_func=fill_search_header_done; + decodecb.arg=&decodeinfo; + + rfc2045_decodemsgtoutf8(src, rfcp, &decodecb); + rfc2045src_deinit(src); + if (decodeinfo.utf8buf) + free(decodeinfo.utf8buf); +} + +static int headerfilter_func(const char *name, const char *value, void *arg) +{ + struct fill_search_header_info *decodeinfo= + (struct fill_search_header_info *)arg; + struct searchinfo *sip; + const char *p; + int isto=rfc822hdr_namecmp(name, "to"); + int iscc=rfc822hdr_namecmp(name, "cc"); + int isfrom=rfc822hdr_namecmp(name, "from"); + int isinreplyto=rfc822hdr_namecmp(name, "in-reply-to"); + int isdate=rfc822hdr_namecmp(name, "date"); + + int isreferences=rfc822hdr_namecmp(name, "references"); + int ismessageid=rfc822hdr_namecmp(name, "message-id"); + + for (sip=decodeinfo->si; sip; sip=sip->next) + { + if (sip->type == search_text && sip->value <= 0) + { + /* + ** Full message search. Reset the search engine, + ** feed it "Headername: " + */ + + maildir_search_reset(&sip->sei); + + for (p=name; *p; p++) + { + maildir_search_step_unicode_lc(&sip->sei, + (unsigned char) + *p); + if (maildir_search_found(&sip->sei)) + sip->value=1; + } + for (p=": "; *p; p++) + { + maildir_search_step_unicode_lc(&sip->sei, + (unsigned char) + *p); + if (maildir_search_found(&sip->sei)) + sip->value=1; + } + } + + if ( (sip->type == search_cc && iscc == 0 && sip->as == 0) + || + (sip->type == search_from && isfrom == 0 && sip->as == 0) + || + (sip->type == search_to && isto == 0 && sip->as == 0) + || + (sip->type == search_references1 && isinreplyto == 0 + && sip->bs == 0)) + { + struct rfc822t *t; + struct rfc822a *a; + char *s; + + t=rfc822t_alloc_new(value, NULL, NULL); + if (!t) write_error_exit(0); + a=rfc822a_alloc(t); + if (!a) write_error_exit(0); + s=a->naddrs > 0 ? rfc822_getaddr(a, 0):strdup(""); + rfc822a_free(a); + rfc822t_free(t); + if (!s) write_error_exit(0); + + if (sip->type == search_references1) + { + sip->bs=malloc(strlen(s)+3); + if (!sip->bs) + write_error_exit(0); + strcat(strcat(strcpy(sip->bs, "<"), s), ">"); + free(s); + } + else + sip->as=s; + } + + switch (sip->type) { + case search_orderedsubj: + + if (isdate == 0 && sip->bs == 0) + { + sip->bs=strdup(value); + if (!sip->bs) + write_error_exit(0); + } + break; + + case search_date: + + if (isdate == 0 && sip->as == 0) + { + time_t msg_time=rfc822_parsedt(value); + + sip->as=timestamp_for_sorting(msg_time); + } + break; + + case search_sentbefore: + case search_sentsince: + case search_senton: + + if (sip->value > 0) + break; + + if (isdate == 0) + { + time_t given_time=decode_date(sip->as); + time_t msg_time=rfc822_parsedt(value); + + if (given_time == 0 || msg_time == 0) + break; + + msg_time=timestamp_to_day(msg_time); + sip->value=0; + if ((sip->type == search_sentbefore && + msg_time < given_time) || + (sip->type == search_sentsince&& + msg_time>=given_time)|| + (sip->type == search_senton && + msg_time == given_time)) + sip->value=1; + } + break; + + case search_references1: + if (isreferences == 0 && sip->as == 0) + { + sip->as=strdup(value); + if (!sip->as) + write_error_exit(0); + } + break; + case search_references2: + if (isdate == 0 && sip->as == 0) + { + sip->as=strdup(value); + if (!sip->as) + write_error_exit(0); + } + break; + case search_references4: + if (ismessageid == 0 && sip->as == 0) + { + sip->as=strdup(value); + if (!sip->as) + write_error_exit(0); + } + break; + default: + break; + } + } + decodeinfo->utf8buflen=0; + return 1; +} + +static int fill_search_header_utf8(const char *str, size_t cnt, void *arg) +{ + struct fill_search_header_info *decodeinfo= + (struct fill_search_header_info *)arg; + + if (decodeinfo->utf8bufsize - decodeinfo->utf8buflen < cnt) + { + size_t newsize=decodeinfo->utf8buflen + cnt*2; + char *p=decodeinfo->utf8buf + ? realloc(decodeinfo->utf8buf, newsize): + malloc(newsize); + + if (!p) + write_error_exit(0); + decodeinfo->utf8buf=p; + decodeinfo->utf8bufsize=newsize; + } + + if (cnt) + memcpy(decodeinfo->utf8buf+decodeinfo->utf8buflen, str, cnt); + decodeinfo->utf8buflen += cnt; + return 0; +} + +static int fill_search_header_done(const char *name, void *arg) +{ + struct fill_search_header_info *decodeinfo= + (struct fill_search_header_info *)arg; + struct searchinfo *sip; + int issubject=rfc822hdr_namecmp(name, "subject"); + size_t j; + libmail_u_convert_handle_t conv; + unicode_char *ucptr; + size_t ucsize; + int rc; + + if (decodeinfo->utf8buflen && + decodeinfo->utf8buf[decodeinfo->utf8buflen-1] == '\n') + --decodeinfo->utf8buflen; + + fill_search_header_utf8("", 1, arg); + + for (sip=decodeinfo->si; sip; sip=sip->next) + switch (sip->type) { + case search_references3: + if (issubject == 0 && sip->as == 0) + { + sip->as=strdup(decodeinfo->utf8buf); + if (!sip->as) + write_error_exit(0); + } + break; + case search_orderedsubj: + + if (issubject == 0 && sip->as == 0) + { + int dummy; + + sip->as=rfc822_coresubj(decodeinfo->utf8buf, + &dummy); + if (!sip->as) + write_error_exit(0); + } + break; + case search_header: + + if (sip->cs == NULL || rfc822hdr_namecmp(sip->cs, name)) + break; + + /* FALLTHRU */ + + case search_text: + if (sip->value > 0) + break; + + maildir_search_reset(&sip->sei); + + conv=libmail_u_convert_tou_init("utf-8", &ucptr, + &ucsize, 0); + + if (!conv) + break; + + rc=libmail_u_convert(conv, decodeinfo->utf8buf, + decodeinfo->utf8buflen-1); + + if (libmail_u_convert_deinit(conv, NULL)) + break; + + if (rc) + { + free(ucptr); + break; + } + + for (j=0; j<=ucsize; ++j) + { + maildir_search_step_unicode_lc(&sip->sei, + j == ucsize + ? ' ': + ucptr[j]); + if (maildir_search_found(&sip->sei)) + { + sip->value=1; + break; + } + } + free(ucptr); + break; + default: + break; + } + + + return 0; +} + +struct fill_search_body_info { + + struct searchinfo *si; + libmail_u_convert_handle_t toucs4_handle; + +}; + +static int fill_search_body_utf8(const char *str, size_t n, void *arg); +static int fill_search_body_ucs4(const char *str, size_t n, void *arg); + +static void fill_search_body(struct searchinfo *si, + struct rfc2045 *rfcp, FILE *fp, + struct imapscanmessageinfo *mi) +{ + struct rfc2045src *src; + struct rfc2045_decodemsgtoutf8_cb decodecb; + struct fill_search_body_info decodeinfo; + struct searchinfo *sip; + + src=rfc2045src_init_fd(fileno(fp)); + + if (!src) + return; + + memset(&decodecb, 0, sizeof(decodecb)); + memset(&decodeinfo, 0, sizeof(decodeinfo)); + + decodecb.flags=RFC2045_DECODEMSG_NOHEADERS; + decodecb.output_func=fill_search_body_utf8; + decodecb.arg=&decodeinfo; + + decodeinfo.si=si; + + if ((decodeinfo.toucs4_handle= + libmail_u_convert_init("utf-8", + libmail_u_ucs4_native, + fill_search_body_ucs4, + &decodeinfo)) == NULL) + { + write_error_exit("libmail_u_convert_init"); + } + + for (sip=decodeinfo.si; sip; sip=sip->next) + if ((sip->type == search_text || sip->type == search_body) + && sip->value <= 0) + { + rfc2045_decodemsgtoutf8(src, rfcp, &decodecb); + break; + } + + libmail_u_convert_deinit(decodeinfo.toucs4_handle, NULL); + + rfc2045src_deinit(src); +} + +static int fill_search_body_utf8(const char *str, size_t n, void *arg) +{ + struct fill_search_body_info *decodeinfo= + (struct fill_search_body_info *)arg; + + return libmail_u_convert(decodeinfo->toucs4_handle, str, n); +} + +static int fill_search_body_ucs4(const char *str, size_t n, void *arg) +{ + struct fill_search_body_info *decodeinfo= + (struct fill_search_body_info *)arg; + struct searchinfo *sip; + const unicode_char *u=(const unicode_char *)str; + int notfound=1; + + n /= 4; + + for (sip=decodeinfo->si; sip; sip=sip->next) + if ((sip->type == search_text || sip->type == search_body) + && sip->value <= 0) + { + size_t i; + + notfound=0; + + for (i=0; i<n; i++) + { + maildir_search_step_unicode_lc(&sip->sei, u[i]); + + if (maildir_search_found(&sip->sei)) + { + sip->value=1; + break; + } + } + } + + return notfound; +} diff --git a/imap/searchinfo.c b/imap/searchinfo.c new file mode 100644 index 0000000..0f43ad2 --- /dev/null +++ b/imap/searchinfo.c @@ -0,0 +1,689 @@ +/* +** Copyright 1998 - 2010 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include "unicode/unicode.h" +#include "searchinfo.h" +#include "imapwrite.h" +#include "imaptoken.h" + + +struct searchinfo *alloc_search(struct searchinfo **head) +{ +struct searchinfo *si=(struct searchinfo *)malloc(sizeof(**head)); + + if (si == 0) write_error_exit(0); + memset(si, 0, sizeof(*si)); + maildir_search_init(&si->sei); + si->next= *head; + *head=si; + return (si); +} + +void free_search(struct searchinfo *si) +{ +struct searchinfo *p; + + while (si) + { + p=si->next; + if (si->as) free(si->as); + if (si->bs) free(si->bs); + if (si->cs) free(si->cs); + + maildir_search_destroy(&si->sei); + + free(si); + si=p; + } +} + +static struct searchinfo *alloc_search_andlist(struct searchinfo **); +static struct searchinfo *alloc_search_notkey(struct searchinfo **); +static struct searchinfo *alloc_search_key(struct searchinfo **); + +struct searchinfo *alloc_parsesearch(struct searchinfo **head) +{ +struct searchinfo *si; + + *head=0; + if ((si=alloc_search_andlist(head)) == 0) + { + free_search(*head); + return (0); + } + return (si); +} + +struct searchinfo *alloc_searchextra(struct searchinfo *top, + struct searchinfo **head, search_type t) +{ + struct searchinfo *si; + + if (t == search_references1) + { + /* Automatically add third and second dummy node */ + + top=alloc_searchextra(top, head, search_references4); + top=alloc_searchextra(top, head, search_references3); + top=alloc_searchextra(top, head, search_references2); + } + si=alloc_search(head); + si->type=t; + si->a=top; + return (si); +} + +static struct searchinfo *alloc_search_andlist(struct searchinfo **head) +{ +struct searchinfo *si, *a, *b; +struct imaptoken *t; + + si=alloc_search_notkey(head); + if (!si) return (0); + while ((t=currenttoken())->tokentype != IT_RPAREN && t->tokentype != + IT_EOL) + { + if ((a=alloc_search_notkey(head)) == 0) return (0); + b=alloc_search(head); + b->type=search_and; + b->a=si; + b->b=a; + si=b; + } + return (si); +} + +static struct searchinfo *alloc_search_notkey(struct searchinfo **head) +{ +struct imaptoken *t=currenttoken(); + + if (t->tokentype == IT_ATOM && strcmp(t->tokenbuf, "NOT") == 0) + { + struct searchinfo *si=alloc_search(head); + + si->type=search_not; + nexttoken(); + if ((si->a=alloc_search_key(head)) == 0) + return (0); + return (si); + } + return (alloc_search_key(head)); +} + +static struct searchinfo *alloc_search_key(struct searchinfo **head) +{ +struct imaptoken *t=currenttoken(); +struct searchinfo *si; +const char *keyword; + + if (t->tokentype == IT_LPAREN) + { + nexttoken(); + if ((si=alloc_search_andlist(head)) == 0 || + currenttoken()->tokentype != IT_RPAREN) + return (0); + nexttoken(); + return (si); + } + + if (t->tokentype != IT_ATOM && t->tokentype != IT_NUMBER) + return (0); + + keyword=t->tokenbuf; + + if (strcmp(keyword, "ALL") == 0) + { + struct searchinfo *si; + + (si=alloc_search(head))->type=search_all; + nexttoken(); + return (si); + } + + if (strcmp(keyword, "OR") == 0) + { + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_or; + nexttoken(); + if ((si->a=alloc_search_notkey(head)) == 0 || + (si->b=alloc_search_notkey(head)) == 0) return (0); + return (si); + } + + if (strcmp(keyword, "HEADER") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_header; + t=nexttoken_okbracket(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->cs=strdup(t->tokenbuf); + if (!si->cs) + write_error_exit(0); + t=nexttoken_okbracket(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "BCC") == 0 || + strcmp(keyword, "CC") == 0 || + strcmp(keyword, "FROM") == 0 || + strcmp(keyword, "TO") == 0 || + strcmp(keyword, "SUBJECT") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_header; + si->cs=my_strdup(keyword); + t=nexttoken_okbracket(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "BEFORE") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_before; + t=nexttoken(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "BODY") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_body; + t=nexttoken_okbracket(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + if (strcmp(keyword, "LARGER") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_larger; + t=nexttoken(); + if (t->tokentype != IT_NUMBER) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "ON") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_on; + t=nexttoken(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "SENTBEFORE") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_sentbefore; + t=nexttoken(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "SENTON") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_senton; + t=nexttoken(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(keyword); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "SENTSINCE") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_sentsince; + t=nexttoken(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "SINCE") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_since; + t=nexttoken(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "SMALLER") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_smaller; + t=nexttoken(); + if (t->tokentype != IT_NUMBER) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "TEXT") == 0) + { + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_text; + t=nexttoken_okbracket(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "UID") == 0) + { + struct searchinfo *si; + struct imaptoken *t; + + si=alloc_search(head); + si->type=search_uid; + t=nexttoken(); + if (!ismsgset(t)) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "KEYWORD") == 0 + || strcmp(keyword, "UNKEYWORD") == 0) + { + int isnot= *keyword == 'U'; + struct imaptoken *t; + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_msgkeyword; + t=nexttoken_okbracket(); + if (t->tokentype != IT_ATOM && + t->tokentype != IT_NUMBER && + t->tokentype != IT_QUOTED_STRING) + return (0); + si->as=my_strdup(t->tokenbuf); + nexttoken(); + + if (isnot) + { + struct searchinfo *si2=alloc_search(head); + + si2->type=search_not; + si2->a=si; + si=si2; + } + return (si); + } + if (strcmp(keyword, "ANSWERED") == 0 || + strcmp(keyword, "DELETED") == 0 || + strcmp(keyword, "DRAFT") == 0 || + strcmp(keyword, "FLAGGED") == 0 || + strcmp(keyword, "RECENT") == 0 || + strcmp(keyword, "SEEN") == 0) + { + struct searchinfo *si; + + si=alloc_search(head); + si->type=search_msgflag; + if ((si->as=malloc(strlen(keyword)+2)) == 0) + write_error_exit(0); + si->as[0]='\\'; + strcpy(si->as+1, keyword); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "UNANSWERED") == 0 || + strcmp(keyword, "UNDELETED") == 0 || + strcmp(keyword, "UNDRAFT") == 0 || + strcmp(keyword, "UNFLAGGED") == 0 || + strcmp(keyword, "UNSEEN") == 0) + { + struct searchinfo *si; + struct searchinfo *si2; + + si=alloc_search(head); + si->type=search_msgflag; + if ((si->as=malloc(strlen(keyword))) == 0) + write_error_exit(0); + si->as[0]='\\'; + strcpy(si->as+1, keyword+2); + nexttoken(); + + si2=alloc_search(head); + si2->type=search_not; + si2->a=si; + return (si2); + } + + if (strcmp(keyword, "NEW") == 0) + { + struct searchinfo *si, *si2; + + si=alloc_search(head); + si->type=search_and; + si2=si->a=alloc_search(head); + si2->type=search_msgflag; + si2->as=my_strdup("\\RECENT"); + si2=si->b=alloc_search(head); + si2->type=search_not; + si2=si2->a=alloc_search(head); + si2->type=search_msgflag; + si2->as=my_strdup("\\SEEN"); + nexttoken(); + return (si); + } + + if (strcmp(keyword, "OLD") == 0) + { + struct searchinfo *si, *si2; + + si=alloc_search(head); + si->type=search_not; + si2=si->a=alloc_search(head); + si2->type=search_msgflag; + si2->as=my_strdup("\\RECENT"); + nexttoken(); + return (si); + } + + if (ismsgset(t)) + { + si=alloc_search(head); + si->type=search_messageset; + si->as=my_strdup(t->tokenbuf); + nexttoken(); + return (si); + } + + return (0); +} + +/* +** We are about to search in charset 'textcharset'. Make sure that all +** search_text nodes in the search string are in that character set. +*/ + +void search_set_charset_conv(struct searchinfo *si, const char *charset) +{ + for (; si; si=si->next) + { + if (si->type != search_text && si->type != search_body + && si->type != search_header) + continue; + if (si->value > 0) + continue; /* Already found, no need to do this again */ + + if (maildir_search_start_str_chset(&si->sei, + si->as ? si->as:"", + charset)) + { + si->value=0; + continue; + } + } +} + + +#if 0 + +void debug_search(struct searchinfo *si) +{ + if (!si) return; + + switch (si->type) { + case search_messageset: + writes("MESSAGE SET: "); + writes(si->as); + return; + case search_all: + writes("ALL"); + return; + case search_msgflag: + writes("FLAG \""); + writeqs(si->as); + writes("\""); + return; + case search_msgkeyword: + writes("KEYWORD \""); + writeqs(si->as); + writes("\""); + return; + case search_not: + writes("NOT ("); + debug_search(si->a); + writes(")"); + return; + case search_and: + writes("AND ("); + debug_search(si->a); + writes(", "); + debug_search(si->b); + writes(")"); + return; + case search_or: + writes("OR ("); + debug_search(si->a); + writes(", "); + debug_search(si->b); + writes(")"); + return; + case search_header: + writes("HEADER \""); + writeqs(si->cs); + writes("\" \""); + writeqs(si->bs); + writes("\""); + return; + case search_before: + writes("BEFORE \""); + writeqs(si->as); + writes("\""); + return; + case search_body: + writes("BODY \""); + writeqs(si->as); + writes("\""); + return; + case search_larger: + writes("LARGER \""); + writeqs(si->as); + writes("\""); + return; + case search_on: + writes("ON \""); + writeqs(si->as); + writes("\""); + return; + case search_sentbefore: + writes("SENTBEFORE \""); + writeqs(si->as); + writes("\""); + return; + case search_senton: + writes("SENTON \""); + writeqs(si->as); + writes("\""); + return; + case search_sentsince: + writes("SENTSINCE \""); + writeqs(si->as); + writes("\""); + return; + case search_since: + writes("SINCE \""); + writeqs(si->as); + writes("\""); + return; + case search_smaller: + writes("SMALLER \""); + writeqs(si->as); + writes("\""); + return; + case search_text: + writes("TEXT \""); + writeqs(si->as); + writes("\""); + return; + case search_uid: + writes("UID \""); + writeqs(si->as); + writes("\""); + return; + case search_orderedsubj: + writes("ORDEREDSUBJ "); + debug_search(si->a); + return; + case search_references1: + writes("REFERENCES[References/In-Reply-To]="); + writeqs(si->as ? si->as:""); + writes("/"); + writeqs(si->bs ? si->bs:""); + writes(" "); + debug_search(si->a); + return; + case search_references2: + writes("REFERENCES[Date:]="); + writeqs(si->as ? si->as:""); + writes(" "); + debug_search(si->a); + return; + case search_references3: + writes("REFERENCES[Subject]="); + writeqs(si->as ? si->as:""); + writes(" "); + debug_search(si->a); + return; + case search_references4: + writes("REFERENCES[Message-ID]="); + writeqs(si->as ? si->as:""); + writes(" "); + debug_search(si->a); + return; + case search_arrival: + writes("ARRIVAL"); + return; + case search_cc: + writes("CC"); + return; + case search_date: + writes("DATE"); + return; + case search_from: + writes("FROM"); + return; + case search_reverse: + writes("REVERSE"); + return; + case search_size: + writes("SIZE"); + return; + case search_to: + writes("TO"); + return; + } +} + +#endif diff --git a/imap/searchinfo.h b/imap/searchinfo.h new file mode 100644 index 0000000..e80f6a0 --- /dev/null +++ b/imap/searchinfo.h @@ -0,0 +1,126 @@ +#ifndef searchinfo_h +#define searchinfo_h + +#include "maildir/maildirsearch.h" + +/* +** Copyright 1998 - 2002 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + + /* Search keys */ + +typedef enum { + search_messageset, + search_all, + search_msgflag, /* Includes ANSWERED DELETED DRAFT FLAGGED RECENT SEEN */ + search_msgkeyword, /* KEYWORD */ + + search_not, /* Logical NOT, used to implement UNANSWERED + UNDELETED UNDRAFT UNFLAGGED UNKEYWORD UNSEEN, etc... */ + + /* NOTE: NEW gets parsed as ( RECENT UNSEEN ) OLD gets parsed as + NOT RECENT */ + + search_and, + search_or, + + + search_header, /* Also used to implement BCC, CC, FROM, TO, SUBJECT */ + + search_before, + search_body, + search_larger, + search_on, + search_sentbefore, + search_senton, + search_sentsince, + search_since, + search_smaller, + search_text, + search_uid, + + /* + ** search_orderedsubj is a dummy node that's allocated in order to + ** implement an ORDEREDSUBJ THREAD/SORT. as points to the stripped + ** subject from the message. + */ + + search_orderedsubj, + + /* + ** search_references? are dummy nodes that are allocated in order to + ** implement a REFERENCES THREAD. + */ + + search_references1, /* References: and In-Reply-To: header */ + search_references2, /* Date: header */ + search_references3, /* Subject: header */ + search_references4, /* Message-ID: header */ + + /* + ** And the following dummies are used for similar purposes for the + ** SORT command. + */ + + search_arrival, + search_cc, + search_date, + search_from, + search_reverse, + search_size, + search_to + + } search_type; + +/* This structure is used when doing content searching */ + + + +/* A SEARCH request gets parsed into the following structure */ + +struct searchinfo { + struct searchinfo *next; /* Link list of all searchinfos */ + + struct searchinfo *a, *b; /* Nested search requests */ + + search_type type; + + char *as, *bs, *cs; /* As needed */ + + const struct unicode_info *bs_charset; + /* search_text: text string in orig charset is as, text string in + bs_charset charset is in bs */ + + + int value; /* When evaluating: 0 - false, 1 - true, -1 - unknown */ + /* Not used in AND, OR, and NOT nodes */ + + struct maildir_searchengine sei; /* Used when searching */ + + struct libmail_keywordEntry *ke; + } ; + +void free_search(struct searchinfo *); +struct searchinfo *alloc_search(struct searchinfo **); + +struct searchinfo *alloc_parsesearch(struct searchinfo **); +struct searchinfo *alloc_searchextra(struct searchinfo *, + struct searchinfo **, search_type); +void debug_search(struct searchinfo *); + +struct unicode_info; + +void dosearch(struct searchinfo *, struct searchinfo *, + const char *, int); + +void search_internal(struct searchinfo *, struct searchinfo *, + const char *, + int, void (*)(struct searchinfo *, + struct searchinfo *, int, + unsigned long, void *), void *); + +void search_set_charset_conv(struct searchinfo *, const char *); + +#endif diff --git a/imap/smap.c b/imap/smap.c new file mode 100644 index 0000000..20a7c2c --- /dev/null +++ b/imap/smap.c @@ -0,0 +1,4557 @@ +/* +** Copyright 2003-2011 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <fcntl.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif +#if HAVE_LOCALE_H +#include <locale.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include "mysignal.h" +#include "imapd.h" +#include "fetchinfo.h" +#include "searchinfo.h" +#include "storeinfo.h" +#include "mailboxlist.h" +#include "thread.h" +#include "outbox.h" + +#include "imapwrite.h" +#include "imaptoken.h" +#include "imapscanclient.h" +#include "searchinfo.h" +#include "maildir/config.h" +#include "maildir/maildircreate.h" +#include "maildir/maildirrequota.h" +#include "maildir/maildirgetquota.h" +#include "maildir/maildirquota.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirwatch.h" +#include "maildir/maildiraclt.h" +#include "maildir/maildirnewshared.h" +#include "maildir/maildirinfo.h" +#include "unicode/unicode.h" + +#include "rfc2045/rfc2045.h" +#include "rfc822/rfc822.h" + +#define SMAP_BUFSIZ 8192 + +#define SHARED "shared" + +#define LIST_FOLDER 1 +#define LIST_DIRECTORY 2 + +#define FETCH_UID 1 +#define FETCH_SIZE 2 +#define FETCH_FLAGS 4 +#define FETCH_KEYWORDS 8 +#define FETCH_INTERNALDATE 16 + +extern dev_t homedir_dev; +extern ino_t homedir_ino; + +int mdcreate(const char *mailbox); +int mddelete(const char *s); + +extern int folder_rename(struct maildir_info *mi1, + struct maildir_info *mi2, + const char **errmsg); +extern int current_temp_fd; +extern const char *current_temp_fn; + +extern int snapshot_init(const char *, const char *); +extern int keywords(); + +extern unsigned long header_count, body_count; + +extern char *compute_myrights(maildir_aclt_list *l, + const char *l_owner); + +extern int addRemoveKeywords(int (*callback_func)(void *, void *), + void *callback_func_arg, + struct storeinfo *storeinfo_s); +extern int doAddRemoveKeywords(unsigned long n, int uid, void *vp); + + +extern void snapshot_select(int); +extern void doflags(FILE *fp, struct fetchinfo *fi, + struct imapscaninfo *i, unsigned long msgnum, + struct rfc2045 *mimep); +extern void set_time(const char *tmpname, time_t timestamp); +extern int imapenhancedidle(void); +extern void imapidle(void); + +extern void expunge(); +extern void doNoop(int); +extern int do_store(unsigned long, int, void *); +extern int reflag_filename(struct imapscanmessageinfo *mi, + struct imapflags *flags, int fd); + +extern void do_expunge(unsigned long expunge_start, + unsigned long expunge_end, + int force); + +extern char *current_mailbox, *current_mailbox_acl; +static int current_mailbox_shared; + +extern struct imapscaninfo current_maildir_info; +extern void get_message_flags(struct imapscanmessageinfo *, + char *, struct imapflags *); +extern void fetchflags(unsigned long); +extern int acl_lock(const char *homedir, + int (*func)(void *), + void *void_arg); +extern void aclminimum(const char *); + +struct rfc2045 *fetch_alloc_rfc2045(unsigned long, FILE *); +FILE *open_cached_fp(unsigned long); +void fetch_free_cache(); + +extern FILE *maildir_mkfilename(const char *mailbox, struct imapflags *flags, + unsigned long s, char **tmpname, + char **newname); + +/* +** Parse a word from the current SMAP command. +*/ + +static char *getword(char **ptr) +{ + char *p= *ptr, *q, *r; + + while (*p && isspace((int)(unsigned char)*p)) + p++; + + if (*p != '"') + { + for (q=p; *q; q++) + { + if (isspace((int)(unsigned char)*q)) + { + *q++=0; + break; + } + } + + *ptr=q; + return p; + } + + ++p; + r=q=p; + + while (*r) + { + if (*r == '"') + { + if (r[1] == '"') + { + r += 2; + *q++='"'; + continue; + } + ++r; + break; + } + + *q++ = *r++; + } + + *q=0; + *ptr=r; + return p; +} + +#define UC(c) if ( (c) >= 'a' && (c) <= 'z') (c) += 'A' - 'a' + +static void up(char *p) +{ + while (*p) + { + UC(*p); + p++; + } +} + +/* +** Write a WORD reply. +*/ +static void smapword_s(const char *w); + +void smapword(const char *w) +{ + writes("\""); + smapword_s(w); + writes("\""); +} + +static void smapword_s(const char *w) +{ + while (w && *w) + { + size_t i; + + for (i=0; w[i]; i++) + if (w[i] == '"') + break; + if (i) + writemem(w, i); + + w += i; + + if (*w) + { + writes("\"\""); + ++w; + } + } +} + +struct fn_word_list { + struct fn_word_list *next; + char *w; +}; + +/* +** Create a folder word array by reading words from the SMAP command. +*/ + +static char **fn_fromwords(char **ptr) +{ + struct fn_word_list *h=NULL, *n, **t=&h; + size_t cnt=0; + char *p; + char **fn; + + while (*(p=getword(ptr))) + { + n=malloc(sizeof(struct fn_word_list)); + + if (!n || !(n->w=strdup(p))) + { + if (n) + free(n); + + while ((n=h) != NULL) + { + h=n->next; + free(n->w); + free(n); + } + return NULL; + } + + n->next=NULL; + *t=n; + t= &n->next; + cnt++; + } + + if (!h) + { + errno=EINVAL; + return NULL; + } + + fn=malloc((cnt+1)*sizeof(char *)); + cnt=0; + + while ((n=h) != NULL) + { + h=n->next; + + if (fn) + fn[cnt]=n->w; + else + free(n->w); + free(n); + cnt++; + } + if (fn) + fn[cnt]=0; + return fn; +} + +/* +** LIST-related functions. +*/ + +struct list_hier { + struct list_hier *next; + char *hier; + int flags; +}; + +struct list_callback_info { + + struct list_hier *hier; /* Hierarchy being listed */ + + struct list_hier *found; +}; + +static void list(char *folder, const char *descr, int type) +{ + writes("* LIST "); + + smapword(folder); + + writes(" "); + + smapword(descr); + + if (type & LIST_FOLDER) + writes(" FOLDER"); + if (type & LIST_DIRECTORY) + writes(" DIRECTORY"); + writes("\n"); +} + +/* +** Callback from maildir_list. f="INBOX.folder.name" +** +*/ + +struct list_callback_utf8 { + + void (*callback_func)(const char *, char **, void *); + void *callback_arg; + const char *homedir; + const char *owner; +}; + +static void list_callback(const char *f, void *vp) +{ + struct list_callback_utf8 *utf8=(struct list_callback_utf8 *)vp; + maildir_aclt_list l; + + char **fn=maildir_smapfn_fromutf7(f); + + if (!fn) + { + perror(f); + return; + } + + if (maildir_acl_read(&l, utf8->homedir, strchr(f, '.')) == 0) + { + char *myrights; + char *owner=malloc(sizeof("user=")+strlen(utf8->owner)); + + if (!owner) + write_error_exit(0); + + strcat(strcpy(owner, "user="), utf8->owner); + myrights=compute_myrights(&l, owner); + free(owner); + + if (myrights && strchr(myrights, ACL_LOOKUP[0]) != NULL) + (*utf8->callback_func)(f, fn, utf8->callback_arg); + if (myrights) + free(myrights); + + maildir_aclt_list_destroy(&l); + } + maildir_smapfn_free(fn); +} + +/* +** list_callback callback that accumulates existing folders beneath a +** certain hierarchy. +*/ + +static void list_utf8_callback(const char *n, char **f, void *vp) +{ + struct list_callback_info *lci=(struct list_callback_info *)vp; + struct list_hier *h=lci->hier; + + for (;;) + { + if (!*f) + return; + + if (h) + { + if (strcmp(h->hier, *f)) + break; + + h=h->next; + f++; + continue; + } + + for (h=lci->found; h; h=h->next) + { + if (strcmp(h->hier, *f) == 0) + break; + } + + if (!h) + { + if ((h=malloc(sizeof(struct list_hier))) == NULL || + (h->hier=strdup(*f)) == NULL) + { + if (h) + free(h); + perror("malloc"); + break; + } + + h->next=lci->found; + lci->found=h; + h->flags=0; + } + + if (f[1]) + h->flags |= LIST_DIRECTORY; + else + h->flags |= LIST_FOLDER; + break; + } +} + +/* +** SMAP1 list command goes here. Dirty hack: build the hierarchy list on +** the stack. +*/ + +static void do_listcmd(struct list_hier **head, + struct list_hier **tail, + char **ptr); + +static void listcmd(struct list_hier **head, + struct list_hier **tail, + char **ptr) +{ + char *p; + + if (*(p=getword(ptr))) + { + struct list_hier node; + node.next=NULL; + node.hier=p; + + *tail= &node; + listcmd(head, &node.next, ptr); + return; + } + do_listcmd(head, tail, ptr); +} + +struct smap_find_info { + char *homedir; + char *maildir; +}; + +static int smap_find_cb(struct maildir_newshared_enum_cb *cb); +static int smap_list_cb(struct maildir_newshared_enum_cb *cb); +static int read_acls(maildir_aclt_list *aclt_list, + struct maildir_info *minfo); + +static void do_listcmd(struct list_hier **head, + struct list_hier **tail, + char **ptr) +{ + struct list_hier *p; + size_t cnt; + char **vecs; + int hierlist=0; + + if (!*head) /* No arguments to LIST */ + { + list(INBOX, "New Mail", LIST_FOLDER); + list(INBOX, "Folders", LIST_DIRECTORY); + list(PUBLIC, "Public Folders", LIST_DIRECTORY); + } + else + { + struct list_callback_info lci; + struct list_callback_utf8 list_utf8_info; + + list_utf8_info.callback_func= &list_utf8_callback; + list_utf8_info.callback_arg= &lci; + + lci.hier= *head; + lci.found=NULL; + + if (strcmp(lci.hier->hier, PUBLIC) == 0) + { + struct maildir_shindex_cache *curcache; + struct list_hier *p=lci.hier->next; + struct smap_find_info sfi; + int eof; + char *d; + + curcache=maildir_shared_cache_read(NULL, NULL, NULL); + + while (curcache && p) + { + size_t i; + int rc; + struct list_hier inbox; + + for (i=0; i<curcache->nrecords; i++) + if (strcmp(curcache->records[i].name, + p->hier) == 0) + break; + if (i >= curcache->nrecords) + { + curcache=NULL; + break; + } + + sfi.homedir=NULL; + sfi.maildir=NULL; + curcache->indexfile.startingpos= + curcache->records[i].offset; + rc=maildir_newshared_nextAt(&curcache + ->indexfile, + &eof, + smap_find_cb, + &sfi); + + if (rc || eof) + { + fprintf(stderr, "ERR: Internal error -" + " maildir_newshared_nextAt: %s\n", + strerror(errno)); + curcache=NULL; + break; + } + + if (!sfi.homedir) + { + curcache= + maildir_shared_cache_read(curcache, + sfi.maildir, + p->hier); + p=p->next; + free(sfi.maildir); + continue; + } + + inbox.next=p->next; + inbox.hier=INBOX; + + d=maildir_location(sfi.homedir, sfi.maildir); + free(sfi.homedir); + free(sfi.maildir); + + lci.hier= &inbox; + list_utf8_info.homedir=d; + list_utf8_info.owner=p->hier; + + maildir_list(d, &list_callback, + &list_utf8_info); + free(d); + curcache=NULL; + break; + } + + if (curcache) /* List a shared hierarchy */ + { + int rc; + + curcache->indexfile.startingpos=0; + eof=0; + + do + { + rc=(curcache->indexfile.startingpos + ? maildir_newshared_next: + maildir_newshared_nextAt) + (&curcache->indexfile, &eof, + &smap_list_cb, + &list_utf8_info); + + if (rc) + fprintf(stderr, + "ERR: Internal error -" + " maildir_newshared_next: %s\n", + strerror(errno)); + } while (rc == 0 && !eof); + + hierlist=1; + } + } + else + { + list_utf8_info.homedir="."; + list_utf8_info.owner=getenv("AUTHENTICATED"); + maildir_list(".", &list_callback, + &list_utf8_info); + } + + for (cnt=0, p= *head; p; p=p->next) + ++cnt; + + vecs=malloc(sizeof(char *)*(cnt+2)); + + if (!vecs) + { + while (lci.found) + { + struct list_hier *h=lci.found; + + lci.found=h->next; + + free(h->hier); + free(h); + } + write_error_exit(0); + } + + + for (cnt=0, p= *head; p; p=p->next) + { + vecs[cnt]=p->hier; + ++cnt; + } + + while (lci.found) + { + struct list_hier *h=lci.found; + struct maildir_info minfo; + maildir_aclt_list aclt_list; + + lci.found=h->next; + + vecs[cnt]=h->hier; + vecs[cnt+1]=0; + + if (maildir_info_smap_find(&minfo, vecs, + getenv("AUTHENTICATED")) == 0) + { + if (read_acls(&aclt_list, &minfo) == 0) + { + char *acl; + + acl=compute_myrights(&aclt_list, + minfo.owner); + + if (acl) + { + if (strchr(acl, ACL_LOOKUP[0]) + == NULL) + { + h->flags=LIST_DIRECTORY; + + if (hierlist) + list(h->hier, + h->hier, + h->flags); + + } + else + list(h->hier, h->hier, + h->flags); + free(acl); + } + else + { + fprintf(stderr, + "ERR: Cannot compute" + " my access rights" + " for %s: %s\n", + h->hier, + strerror(errno)); + } + + maildir_aclt_list_destroy(&aclt_list); + } + else + { + fprintf(stderr, + "ERR: Cannot read ACLs" + " for %s(%s): %s\n", + minfo.homedir ? minfo.homedir + : ".", + minfo.maildir ? minfo.maildir + : "unknown", + strerror(errno)); + } + } + else + { + fprintf(stderr, + "ERR: Internal error in list():" + " cannot find folder %s: %s\n", + h->hier, + strerror(errno)); + } + + free(h->hier); + free(h); + } + free(vecs); + } + writes("+OK LIST completed\n"); +} + +static int smap_find_cb(struct maildir_newshared_enum_cb *cb) +{ + struct smap_find_info *ifs=(struct smap_find_info *)cb->cb_arg; + + if (cb->homedir) + ifs->homedir=my_strdup(cb->homedir); + if (cb->maildir) + ifs->maildir=my_strdup(cb->maildir); + return 0; +} + +static int smap_list_cb(struct maildir_newshared_enum_cb *cb) +{ + struct list_callback_utf8 *list_utf8_info= + (struct list_callback_utf8 *)cb->cb_arg; + struct list_callback_info *lci= + (struct list_callback_info *)list_utf8_info->callback_arg; + char *d; + + struct list_hier *h; + struct stat stat_buf; + + if (cb->homedir == NULL) + { + if ((h=malloc(sizeof(struct list_hier))) == NULL || + (h->hier + =strdup(cb->name)) == NULL) + { + if (h) + free(h); + perror("ERR: malloc"); + return 0; + } + + h->next= lci->found; + lci->found=h; + h->flags = LIST_DIRECTORY; + return 0; + } + + d=maildir_location(cb->homedir, cb->maildir); + + if (!d) + { + perror("ERR: get_topmaildir"); + return 0; + } + + if (stat(d, &stat_buf) < 0 || + (stat_buf.st_dev == homedir_dev && + stat_buf.st_ino == homedir_ino)) + { + free(d); + return 0; + } + + list_utf8_info->homedir=d; + list_utf8_info->owner=cb->name; + lci->hier=NULL; + h=lci->found; + lci->found=NULL; + maildir_list(d, &list_callback, list_utf8_info); + free(d); + + if (!lci->found) + lci->found=h; + else + { + char *p; + + while (lci->found->next) /* SHOULDN'T HAPPEN!!! */ + { + struct list_hier *p=lci->found->next; + + lci->found->next=p->next; + free(p->hier); + free(p); + fprintf(stderr, "ERR: Unexpected folder list" + " in smap_list_cb()\n"); + } + lci->found->next=h; + + p=my_strdup(cb->name); + free(lci->found->hier); + lci->found->hier=p; + } + + return (0); +} + +/* +** Read the name of a new folder. Returns the pathname to the folder, suitable +** for immediate creation. +*/ + +static char *getCreateFolder_int(char **ptr, char *need_perms) +{ + char **fn; + char *n; + struct maildir_info minfo; + size_t i; + char *save; + maildir_aclt_list aclt_list; + + fn=fn_fromwords(ptr); + if (!fn) + return NULL; + + + if (need_perms) + { + for (i=0; fn[i]; i++) + ; + + if (i == 0) + { + *need_perms=0; + maildir_smapfn_free(fn); + errno=EINVAL; + return NULL; + } + + save=fn[--i]; + fn[i]=NULL; + if (maildir_info_smap_find(&minfo, fn, + getenv("AUTHENTICATED")) < 0) + { + fn[i]=save; + maildir_smapfn_free(fn); + return NULL; + } + + fn[i]=save; + + if (read_acls(&aclt_list, &minfo)) + { + maildir_smapfn_free(fn); + maildir_info_destroy(&minfo); + return NULL; + } + + save=compute_myrights(&aclt_list, minfo.owner); + maildir_aclt_list_destroy(&aclt_list); + + for (i=0; need_perms[i]; i++) + if (save == NULL || strchr(save, need_perms[i])==NULL) + { + if (save) + free(save); + maildir_smapfn_free(fn); + maildir_info_destroy(&minfo); + *need_perms=0; + errno=EPERM; + return NULL; + } + + if (save) + free(save); + + maildir_info_destroy(&minfo); + } + + + if (maildir_info_smap_find(&minfo, fn, getenv("AUTHENTICATED")) < 0) + { + maildir_smapfn_free(fn); + return NULL; + } + + maildir_smapfn_free(fn); + + if (minfo.homedir == NULL || minfo.maildir == NULL) + { + maildir_info_destroy(&minfo); + errno=ENOENT; + return NULL; + } + + n=maildir_name2dir(minfo.homedir, minfo.maildir); + + if (need_perms && strchr(need_perms, ACL_CREATE[0])) + { + /* Initialize the ACL structures */ + + if (read_acls(&aclt_list, &minfo) == 0) + maildir_aclt_list_destroy(&aclt_list); + } + + maildir_info_destroy(&minfo); + + return n; +} + +static char *getCreateFolder(char **ptr, char *perms) +{ + char *p=getCreateFolder_int(ptr, perms); + + if (p && strncmp(p, "./", 2) == 0) + { + char *q=p+2; + + while ((q[-2]=*q) != 0) + q++; + } + return p; +} + + +static int read_acls(maildir_aclt_list *aclt_list, + struct maildir_info *minfo) +{ + char *q; + int rc; + + if (minfo->homedir == NULL || minfo->maildir == NULL) + { + if (minfo->mailbox_type == MAILBOXTYPE_NEWSHARED) + { + /* Intermediate node in public hier */ + + maildir_aclt_list_init(aclt_list); + + if (maildir_aclt_list_add(aclt_list, + "anyone", + ACL_LOOKUP, + NULL) < 0) + { + maildir_aclt_list_destroy(aclt_list); + return -1; + } + return 0; + } + + return -1; + } + + q=maildir_name2dir(".", minfo->maildir); + if (!q) + { + fprintf(stderr, "ERR: Internal error" + " in read_acls(%s)\n", minfo->maildir); + return -1; + } + + rc=maildir_acl_read(aclt_list, minfo->homedir, + q[0] == '.' && + q[1] == '/' ? q+2:q); + free(q); + + if (current_mailbox) + { + q=maildir_name2dir(minfo->homedir, minfo->maildir); + + if (q) + { + if (strcmp(q, current_mailbox) == 0) + { + char *r=compute_myrights(aclt_list, + minfo->owner); + + if (r && strcmp(current_mailbox_acl, r)) + { + free(current_mailbox_acl); + current_mailbox_acl=r; + r=NULL; + } + if (r) free(r); + } + free(q); + } + } + return rc; +} + +static char *getExistingFolder_int(char **ptr, + char *rightsWanted) +{ + char **fn; + char *n; + struct maildir_info minfo; + + fn=fn_fromwords(ptr); + if (!fn) + return NULL; + + if (maildir_info_smap_find(&minfo, fn, getenv("AUTHENTICATED")) < 0) + { + maildir_smapfn_free(fn); + return NULL; + } + maildir_smapfn_free(fn); + + if (minfo.homedir == NULL || minfo.maildir == NULL) + { + maildir_info_destroy(&minfo); + errno=ENOENT; + return NULL; + } + + n=maildir_name2dir(minfo.homedir, minfo.maildir); + + if (n && rightsWanted) + { + maildir_aclt_list aclt_list; + char *q, *r, *s; + + if (read_acls(&aclt_list, &minfo) < 0) + { + free(n); + maildir_info_destroy(&minfo); + return NULL; + + } + + q=compute_myrights(&aclt_list, minfo.owner); + + maildir_aclt_list_destroy(&aclt_list); + + if (q == NULL) + { + free(n); + maildir_info_destroy(&minfo); + return NULL; + } + + for (r=s=rightsWanted; *r; r++) + if (strchr(q, *r)) + *s++ = *r; + *s=0; + free(q); + } + + maildir_info_destroy(&minfo); + return n; +} + +static char *getAccessToFolder(char **ptr, char *rightsWanted) +{ + char *p=getExistingFolder_int(ptr, rightsWanted); + + if (p && strncmp(p, "./", 2) == 0) + { + char *q=p+2; + + while ((q[-2]=*q) != 0) + q++; + } + + return p; +} + +static void smap1_noop(int real_noop) +{ + if (current_mailbox) + doNoop(real_noop); + writes("+OK Folder updated\n"); +} + +/* Parse a message set. Return the next word following the message set */ + +struct smapmsgset { + struct smapmsgset *next; + unsigned nranges; + unsigned long range[2][2]; +}; + +static struct smapmsgset msgset; +static const char digit[]="0123456789"; + +static char *markmsgset(char **ptr, int *hasmsgset) +{ + unsigned long n; + char *w; + + struct smapmsgset *msgsetp; + + while ((msgsetp=msgset.next) != NULL) + { + msgset.next=msgsetp->next; + free(msgsetp); + } + + msgsetp= &msgset; + + msgsetp->nranges=0; + + *hasmsgset=0; + + n=0; + + while (*(w=getword(ptr))) + { + unsigned long a=0, b=0; + const char *d; + + if (!*w || (d=strchr(digit, *w)) == NULL) + break; + + *hasmsgset=1; + + while ( *w && (d=strchr(digit, *w)) != NULL) + { + a=a * 10 + d-digit; + w++; + } + + b=a; + + if (*w == '-') + { + ++w; + b=0; + while ( *w && (d=strchr(digit, *w)) != NULL) + { + b=b * 10 + d-digit; + w++; + } + } + + if (a <= n || b < a) + { + errno=EINVAL; + return NULL; + } + + n=b; + + if (msgsetp->nranges >= + sizeof(msgsetp->range)/sizeof(msgsetp->range[0])) + { + if ((msgsetp->next=malloc(sizeof(struct smapmsgset))) + == NULL) + { + write_error_exit(0); + } + + msgsetp=msgsetp->next; + msgsetp->next=NULL; + msgsetp->nranges=0; + } + + msgsetp->range[msgsetp->nranges][0]=a; + msgsetp->range[msgsetp->nranges][1]=b; + ++msgsetp->nranges; + } + + return w; +} + +static void parseflags(char *q, struct imapflags *flags) +{ + char *p; + + if ((q=strchr(q, '=')) == NULL) + return; + ++q; + + while (*q) + { + p=q; + + while (*q) + { + if (*q == ',') + { + *q++=0; + break; + } + q++; + } + + if (strcmp(p, "SEEN") == 0) + flags->seen=1; + else if (strcmp(p, "REPLIED") == 0) + flags->answered=1; + else if (strcmp(p, "DRAFT") == 0) + flags->drafts=1; + else if (strcmp(p, "DELETED") == 0) + flags->deleted=1; + else if (strcmp(p, "MARKED") == 0) + flags->flagged=1; + + } +} + +extern int get_keyword(struct libmail_kwMessage **kwPtr, const char *kw); +extern int valid_keyword(const char *kw); + +static void parsekeywords(char *q, struct libmail_kwMessage **msgp) +{ + char *p; + + if ((q=strchr(q, '=')) == NULL) + return; + ++q; + + while (*q) + { + p=q; + + while (*q) + { + if (*q == ',') + { + *q++=0; + break; + } + q++; + } + + get_keyword(msgp, p); + } +} + +static int applymsgset( int (*callback_func)(unsigned long, void *), + void *callback_arg) +{ + struct smapmsgset *msgsetp= &msgset; + unsigned long n; + int rc; + + while (msgsetp) + { + unsigned i; + + for (i=0; i<msgsetp->nranges; i++) + { + for (n=msgsetp->range[i][0]; + n <= msgsetp->range[i][1]; n++) + { + if (current_mailbox == NULL || + n > current_maildir_info.nmessages) + break; + rc=(*callback_func)(n-1, callback_arg); + if (rc) + return rc; + } + } + + msgsetp=msgsetp->next; + } + return 0; +} + +static int do_attrfetch(unsigned long n, void *vp); + +static int applyflags(unsigned long n, void *vp) +{ + struct storeinfo *si=(struct storeinfo *)vp; + int attrs; + struct libmail_kwMessage *newKw; + + if (n >= current_maildir_info.nmessages) + return 0; + + attrs= si->keywords ? FETCH_KEYWORDS:FETCH_FLAGS; + + if (!si->plusminus) + { + if (si->keywords == NULL) /* STORE FLAGS= */ + si->keywords=current_maildir_info.msgs[n].keywordMsg; + else /* STORE KEYWORDS= */ + get_message_flags(current_maildir_info.msgs+n, 0, + &si->flags); + } + + /* do_store may clobber si->keywords. Punt */ + + newKw=si->keywords; + if (do_store(n+1, 0, si)) + { + si->keywords=newKw; + return -1; + } + si->keywords=newKw; + + do_attrfetch(n, &attrs); + return 0; +} + +struct smapAddRemoveKeywordInfo { + struct storeinfo *si; + void *storeVoidArg; +}; + +static int addRemoveSmapKeywordsCallback(void *myVoidArg, void *storeVoidArg); + +static int addRemoveSmapKeywords(struct storeinfo *si) +{ + struct smapAddRemoveKeywordInfo ar; + + ar.si=si; + + return addRemoveKeywords(addRemoveSmapKeywordsCallback, &ar, si); +} + +static int doAddRemoveSmapKeywords(unsigned long n, void *voidArg); + +static int addRemoveSmapKeywordsCallback(void *myVoidArg, void *storeVoidArg) +{ + struct smapAddRemoveKeywordInfo *info= + (struct smapAddRemoveKeywordInfo *)myVoidArg; + + info->storeVoidArg=storeVoidArg; + return applymsgset(doAddRemoveSmapKeywords, info); +} + +static int doAddRemoveSmapKeywords(unsigned long n, void *voidArg) +{ + struct smapAddRemoveKeywordInfo *info= + (struct smapAddRemoveKeywordInfo *)voidArg; + + return doAddRemoveKeywords(n+1, 0, info->storeVoidArg); +} + +static int setdate(unsigned long n, void *vp) +{ + time_t datestamp=*(time_t *)vp; + char *filename=maildir_filename(current_mailbox, 0, + current_maildir_info.msgs[n] + .filename); + + if (filename) + { + set_time(filename, datestamp); + free(filename); + } + return 0; +} + +static int msg_expunge(unsigned long n, void *vp) +{ + do_expunge(n, n+1, 1); + return 0; +} + +struct smapfetchinfo { + int peek; + char *entity; + char *hdrs; + char *mimeid; +}; + +static int hashdr(const char *hdrList, const char *hdr) +{ + if (!hdrList || !*hdrList) + return 1; + + while (*hdrList) + { + size_t n; + int is_envelope=0; + int is_mime=0; + + if (*hdrList == ',') + { + ++hdrList; + continue; + } + + if (strncmp(hdrList, ":ENVELOPE", 9) == 0) + { + switch (hdrList[9]) { + case 0: + case ',': + is_envelope=1; + break; + } + } + + if (strncmp(hdrList, ":MIME", 5) == 0) + { + switch (hdrList[5]) { + case 0: + case ',': + is_mime=1; + break; + } + } + + + if (is_envelope || is_mime) + { + char hbuf[30]; + + hbuf[0]=0; + strncat(hbuf, hdr, 29); + up(hbuf); + + if (strcmp(hbuf, "DATE") == 0) + return 1; + if (strcmp(hbuf, "SUBJECT") == 0) + return 1; + if (strcmp(hbuf, "FROM") == 0) + return 1; + if (strcmp(hbuf, "SENDER") == 0) + return 1; + if (strcmp(hbuf, "REPLY-TO") == 0) + return 1; + if (strcmp(hbuf, "TO") == 0) + return 1; + if (strcmp(hbuf, "CC") == 0) + return 1; + if (strcmp(hbuf, "BCC") == 0) + return 1; + if (strcmp(hbuf, "IN-REPLY-TO") == 0) + return 1; + if (strcmp(hbuf, "MESSAGE-ID") == 0) + return 1; + if (strcmp(hbuf, "REFERENCES") == 0) + return 1; + + if (is_mime) + { + if (strcmp(hbuf, "MIME-VERSION") == 0) + return 1; + + if (strncmp(hbuf, "CONTENT-", 8) == 0) + return 1; + } + } + + for (n=0; hdrList[n] && hdrList[n] != ',' && hdr[n]; n++) + { + char a=hdrList[n]; + char b=hdr[n]; + + UC(b); + if (a != b) + break; + } + + if ((hdrList[n] == 0 || hdrList[n] == ',') && hdr[n] == 0) + return 1; + + hdrList += n; + while (*hdrList && *hdrList != ',') + ++hdrList; + } + return 0; +} + +static void writemimeid(struct rfc2045 *rfcp) +{ + if (rfcp->parent) + { + writemimeid(rfcp->parent); + writes("."); + } + writen(rfcp->pindex); +} + +static int dump_hdrs(int fd, unsigned long n, + struct rfc2045 *rfcp, const char *hdrs, + const char *type) +{ + struct rfc2045src *src; + struct rfc2045headerinfo *h; + char *header; + char *value; + int rc; + off_t start_pos, end_pos, dummy, start_body; + off_t nbodylines; + int get_flags=RFC2045H_NOLC; + + rc=0; + + if (type && strcmp(type, "RAWHEADERS") == 0) + get_flags |= RFC2045H_KEEPNL; + + if (!rfcp) + { + struct stat stat_buf; + + if (fstat(fd, &stat_buf)) + end_pos=8000; /* Heh */ + else + end_pos=stat_buf.st_size; + start_pos=0; + start_body=0; + } + else rfc2045_mimepos(rfcp, &start_pos, &end_pos, &start_body, &dummy, + &nbodylines); + + writes("{."); + writen(start_body - start_pos); + writes("} FETCH "); + writen(n+1); + if (type) + { + writes(" "); + writes(type); + writes("\n"); + } + else /* MIME */ + { + writes(" LINES="); + writen(nbodylines); + writes(" SIZE="); + writen(end_pos-start_body); + writes(" \"MIME.ID="); + + if (rfcp->parent) + { + writemimeid(rfcp); + writes("\" \"MIME.PARENT="); + if (rfcp->parent->parent) + writemimeid(rfcp->parent); + } + writes("\"\n"); + } + + src=rfc2045src_init_fd(fd); + h=src ? rfc2045header_start(src, rfcp):NULL; + + while (h && + (rc=rfc2045header_get(h, &header, &value, get_flags)) == 0 + && header) + { + if (hashdr(hdrs, header)) + { + if (*header == '.') + writes("."); + writes(header); + writes(": "); + writes(value); + writes("\n"); + + header_count += strlen(header)+strlen(value)+3; + } + } + writes(".\n"); + + if (h) + rfc2045header_end(h); + else + rc= -1; + if (src) + rfc2045src_deinit(src); + return rc; +} + +static int dump_body(FILE *fp, unsigned long msgNum, + struct rfc2045 *rfcp, int dump_all) +{ + char buffer[SMAP_BUFSIZ]; + off_t start_pos, end_pos, dummy, start_body; + int i; + int first; + + if (!rfcp) + { + struct stat stat_buf; + + if (fstat(fileno(fp), &stat_buf) < 0) + return -1; + + if (dump_all) + { + start_pos=start_body=0; + } + else + { + if (fseek(fp, 0L, SEEK_SET) < 0) + return -1; + + if (!(rfcp=rfc2045_alloc())) + return -1; + + do + { + i=fread(buffer, 1, sizeof(buffer), fp); + + if (i < 0) + { + rfc2045_free(rfcp); + return -1; + } + + if (i == 0) + break; + rfc2045_parse(rfcp, buffer, i); + } while (rfcp->workinheader); + + rfc2045_mimepos(rfcp, &start_pos, &end_pos, + &start_body, &dummy, + &dummy); + rfc2045_free(rfcp); + + start_pos=0; + } + end_pos=stat_buf.st_size; + } + else rfc2045_mimepos(rfcp, &start_pos, &end_pos, &start_body, &dummy, + &dummy); + + if (dump_all) + start_body=start_pos; + + if (fseek(fp, start_body, SEEK_SET) < 0) + return -1; + + first=1; + do + { + int n=sizeof(buffer); + + if (n > end_pos - start_body) + n=end_pos - start_body; + + for (i=0; i<n; i++) + { + int ch=getc(fp); + + if (ch == EOF) + { + errno=EIO; + return -1; + } + buffer[i]=ch; + } + + if (first) + { + if (start_body == end_pos) + { + writes("{.0} FETCH "); + writen(msgNum+1); + writes(" CONTENTS\n."); + } + else + { + writes("{"); + writen(i); + writes("/"); + writen(end_pos - start_body); + writes("} FETCH "); + writen(msgNum+1); + writes(" CONTENTS\n"); + } + } + else + { + writen(i); + writes("\n"); + } + + first=0; + writemem(buffer, i); + + start_body += i; + body_count += i; + } while (start_body < end_pos); + writes("\n"); + return 0; +} + +struct decodeinfo { + char buffer[SMAP_BUFSIZ]; + size_t bufptr; + + int first; + unsigned long msgNum; + off_t estSize; +}; + +static void do_dump_decoded_flush(struct decodeinfo *); + +static struct rfc2045 *decodeCreateRfc(FILE *fp); +static int do_dump_decoded(const char *, size_t, void *); + +static int dump_decoded(FILE *fp, unsigned long msgNum, + struct rfc2045 *rfcp) +{ + struct decodeinfo myDecodeInfo; + const char *content_type; + const char *content_transfer_encoding; + const char *charset; + off_t start_pos, end_pos, dummy, start_body; + + struct rfc2045src *src; + struct rfc2045 *myrfcp=NULL; + int fd; + int i; + + if (!rfcp) + { + rfcp=myrfcp=decodeCreateRfc(fp); + if (!rfcp) + return -1; + } + + if ((fd=dup(fileno(fp))) < 0) + { + if (myrfcp) + rfc2045_free(myrfcp); + return -1; + } + + myDecodeInfo.first=1; + myDecodeInfo.msgNum=msgNum; + myDecodeInfo.bufptr=0; + + rfc2045_mimeinfo(rfcp, &content_type, &content_transfer_encoding, + &charset); + rfc2045_mimepos(rfcp, &start_pos, &end_pos, &start_body, &dummy, + &dummy); + myDecodeInfo.estSize=end_pos - start_body; + + if (content_transfer_encoding + && strlen(content_transfer_encoding) == 6) + { + char buf[7]; + + strcpy(buf, content_transfer_encoding); + up(buf); + + if (strcmp(buf, "BASE64") == 0) + myDecodeInfo.estSize = myDecodeInfo.estSize / 4 * 3; + + /* Better estimate of base64 content */ + } + + src=rfc2045src_init_fd(fd); + + i=src ? rfc2045_decodemimesection(src, rfcp, &do_dump_decoded, + &myDecodeInfo):-1; + + do_dump_decoded_flush(&myDecodeInfo); + + if (src) + rfc2045src_deinit(src); + + close(fd); + + if (i == 0 && myDecodeInfo.first) /* Empty body, punt */ + { + writes("{.0} FETCH "); + writen(msgNum+1); + writes(" CONTENTS\n."); + } + writes("\n"); + if (myrfcp) + rfc2045_free(myrfcp); + return i; +} + +/* Dummy up a rfc2045 structure for retrieving the entire msg body */ + +static struct rfc2045 *decodeCreateRfc(FILE *fp) +{ + char buffer[SMAP_BUFSIZ]; + struct stat stat_buf; + int i; + struct rfc2045 *myrfcp; + + if (fstat(fileno(fp), &stat_buf) < 0) + return NULL; + + if (fseek(fp, 0L, SEEK_SET) < 0) + return NULL; + + if (!(myrfcp=rfc2045_alloc())) + return NULL; + + do + { + i=fread(buffer, 1, sizeof(buffer), fp); + + if (i < 0) + { + rfc2045_free(myrfcp); + return NULL; + } + + if (i == 0) + break; + rfc2045_parse(myrfcp, buffer, i); + } while (myrfcp->workinheader); + + myrfcp->endpos=stat_buf.st_size; + return myrfcp; +} + +static int do_dump_decoded(const char *chunk, size_t chunkSize, + void *vp) +{ + struct decodeinfo *myDecodeInfo=(struct decodeinfo *) vp; + + while (chunkSize) + { + size_t n; + + if (myDecodeInfo->bufptr >= sizeof(myDecodeInfo->buffer)) + do_dump_decoded_flush(myDecodeInfo); + + n=sizeof(myDecodeInfo->buffer)-myDecodeInfo->bufptr; + + if (n > chunkSize) + n=chunkSize; + memcpy(myDecodeInfo->buffer + myDecodeInfo->bufptr, chunk, n); + myDecodeInfo->bufptr += n; + chunk += n; + chunkSize -= n; + } + return 0; +} + +static void do_dump_decoded_flush(struct decodeinfo *myDecodeInfo) +{ + size_t chunkSize= myDecodeInfo->bufptr; + + myDecodeInfo->bufptr=0; + + if (chunkSize == 0) + return; + + if (myDecodeInfo->first) + { + myDecodeInfo->first=0; + writes("{"); + writen(chunkSize); + writes("/"); + writen(myDecodeInfo->estSize); + writes("} FETCH "); + writen(myDecodeInfo->msgNum+1); + writes(" CONTENTS\n"); + } + else + { + writen(chunkSize); + writes("\n"); + } + writemem(myDecodeInfo->buffer, chunkSize); + body_count += chunkSize; +} + +static int mime(int fd, unsigned long n, + struct rfc2045 *rfcp, const char *hdrs) +{ + int rc=dump_hdrs(fd, n, rfcp, hdrs, NULL); + + if (rc) + return rc; + + for (rfcp=rfcp->firstpart; rfcp; rfcp=rfcp->next) + if (!rfcp->isdummy) + { + rc=mime(fd, n, rfcp, hdrs); + if (rc) + return rc; + } + + return 0; +} + +/* +** Find the specified MIME id. +*/ + +static struct rfc2045 *findmimeid(struct rfc2045 *rfcp, + const char *mimeid) +{ + unsigned long n; + + while (mimeid && *mimeid) + { + const char *d; + + n=0; + + if (strchr(digit, *mimeid) == NULL) + return NULL; + + while (*mimeid && (d=strchr(digit, *mimeid)) != NULL) + { + n=n * 10 + d-digit; + mimeid++; + } + + while (rfcp) + { + if (!rfcp->isdummy && rfcp->pindex == n) + break; + rfcp=rfcp->next; + } + + if (!rfcp) + return NULL; + + if (*mimeid == '.') + { + ++mimeid; + rfcp=rfcp->firstpart; + } + } + return rfcp; +} + +static int do_fetch(unsigned long n, void *vp) +{ + struct smapfetchinfo *fi=(struct smapfetchinfo *)vp; + FILE *fp=open_cached_fp(n); + int rc=0; + + if (!fp) + return -1; + + if (strcmp(fi->entity, "MIME") == 0) + { + struct rfc2045 *rfcp=fetch_alloc_rfc2045(n, fp); + int fd; + + if (!rfcp) + return -1; + + fd=dup(fileno(fp)); + if (fd < 0) + return -1; + + rc=mime(fd, n, rfcp, fi->hdrs); + close(fd); + } + else if (strcmp(fi->entity, "HEADERS") == 0 || + strcmp(fi->entity, "RAWHEADERS") == 0) + { + int fd; + struct rfc2045 *rfcp; + + fd=dup(fileno(fp)); + if (fd < 0) + return -1; + + if (!fi->mimeid || !*fi->mimeid) + rfcp=NULL; + else + { + rfcp=fetch_alloc_rfc2045(n, fp); + + rfcp=findmimeid(rfcp, fi->mimeid); + + if (!rfcp) + { + close(fd); + errno=EINVAL; + return -1; + } + } + + rc=dump_hdrs(fd, n, rfcp, fi->hdrs, fi->entity); + close(fd); + } + else if (strcmp(fi->entity, "BODY") == 0 + || strcmp(fi->entity, "ALL") == 0) + { + struct rfc2045 *rfcp; + + if (!fi->mimeid || !*fi->mimeid) + rfcp=NULL; + else + { + rfcp=fetch_alloc_rfc2045(n, fp); + + rfcp=findmimeid(rfcp, fi->mimeid); + + if (!rfcp) + { + errno=EINVAL; + return -1; + } + } + + rc=dump_body(fp, n, rfcp, fi->entity[0] == 'A'); + } + else if (strcmp(fi->entity, "BODY.DECODED") == 0) + { + struct rfc2045 *rfcp; + + if (!fi->mimeid || !*fi->mimeid) + rfcp=NULL; + else + { + rfcp=fetch_alloc_rfc2045(n, fp); + + rfcp=findmimeid(rfcp, fi->mimeid); + + if (!rfcp) + { + errno=EINVAL; + return -1; + } + } + + rc=dump_decoded(fp, n, rfcp); + } + else + { + rc=0; + } + + if (rc == 0 && !fi->peek) + { + struct imapflags flags; + + get_message_flags(current_maildir_info.msgs+n, + 0, &flags); + if (!flags.seen) + { + flags.seen=1; + reflag_filename(¤t_maildir_info.msgs[n], + &flags, fileno(fp)); + current_maildir_info.msgs[n].changedflags=1; + } + } + + if (current_maildir_info.msgs[n].changedflags) + fetchflags(n); + + return rc; +} + +void smap_fetchflags(unsigned long n) +{ + int items=FETCH_FLAGS | FETCH_KEYWORDS; + + do_attrfetch(n, &items); +} + +static int do_attrfetch(unsigned long n, void *vp) +{ + int items=*(int *)vp; + + if (n >= current_maildir_info.nmessages) + return 0; + + writes("* FETCH "); + writen(n+1); + + if (items & FETCH_FLAGS) + { + char buf[256]; + + get_message_flags(current_maildir_info.msgs+n, buf, 0); + + writes(" FLAGS="); + writes(buf); + + current_maildir_info.msgs[n].changedflags=0; + } + + if ((items & FETCH_KEYWORDS) && keywords()) + { + struct libmail_kwMessageEntry *kme; + + writes(" \"KEYWORDS="); + + if (current_maildir_info.msgs[n].keywordMsg && + current_maildir_info.msgs[n].keywordMsg->firstEntry) + { + const char *p=""; + + for (kme=current_maildir_info.msgs[n] + .keywordMsg->firstEntry; + kme; kme=kme->next) + { + writes(p); + p=","; + writes(keywordName(kme->libmail_keywordEntryPtr)); + } + } + writes("\""); + } + + if (items & FETCH_UID) + { + char *p, *q; + + writes(" \"UID="); + + p=current_maildir_info.msgs[n].filename; + + q=strrchr(p, MDIRSEP[0]); + if (q) + *q=0; + smapword_s(p); + if (q) + *q=MDIRSEP[0]; + writes("\""); + } + + if (items & FETCH_SIZE) + { + char *p=current_maildir_info.msgs[n].filename; + unsigned long cnt; + + if (maildir_parsequota(p, &cnt)) + { + FILE *fp=open_cached_fp(n); + struct stat stat_buf; + + if (fp && fstat(fileno(fp), &stat_buf) == 0) + cnt=stat_buf.st_size; + else + cnt=0; + } + + writes(" SIZE="); + writen(cnt); + } + + if (items & FETCH_INTERNALDATE) + { + struct stat stat_buf; + FILE *fp=open_cached_fp(n); + + if (fp && fstat(fileno(fp), &stat_buf) == 0) + { + char buf[256]; + + rfc822_mkdate_buf(stat_buf.st_mtime, buf); + writes(" \"INTERNALDATE="); + smapword_s(buf); + writes("\""); + } + } + writes("\n"); + return 0; +} + +struct add_rcptlist { + struct add_rcptlist *next; + char *rcptto; +}; + +static unsigned long add_msg(FILE *fp, const char *format, + char *buffer, + size_t bufsize) +{ + unsigned long n=0; + + writes("> Go ahead\n"); + writeflush(); + + if (*format == '.') + { + int last_eol=1; + int dot_stuffed=0; + int counter=-1; + + for (;;) + { + char c; + + if ( ((counter=counter + 1 ) % 8192) == 0) + read_timeout(60); + + c=READ(); + + if (c == '\r') + continue; + + if (dot_stuffed && c == '\n') + break; + dot_stuffed=0; + + if (c == '.') + { + if (last_eol) + { + dot_stuffed=1; + continue; + } + } + last_eol= c == '\n'; + putc( (int)(unsigned char)c, fp); + n++; + } + + if (!last_eol) + { + putc('\n', fp); + n++; + } + } + else + { + unsigned long chunkSize; + char last_char='\n'; + + while (sscanf(format, "%lu", &chunkSize) == 1) + { + while (chunkSize) + { + size_t nn=bufsize; + size_t i; + + if (nn > chunkSize) + nn=(size_t)chunkSize; + + read_timeout(60); + nn=doread(buffer, nn); + + chunkSize -= nn; + n += nn; + + for (i=0; i<nn; i++) + { + last_char=buffer[i]; + + if (last_char == '\r') + continue; + putc((int)(unsigned char)last_char, + fp); + } + } + + read_timeout(60); + smap_readline(buffer, bufsize); + format=buffer; + } + + if (last_char != '\n') + { + putc('\n', fp); + n++; + } + } + + if (n == 0) + { + ++n; + putc('\n', fp); + } + + if (fflush(fp) < 0 || ferror(fp)) + return 0; + return n; +} + +static void adduid(char *n) +{ + char *q; + + q=strrchr(n, '/'); + if (q) + n=q+1; + + q=strrchr(n, MDIRSEP[0]); + if (q) + *q=0; + writes("* ADD \"UID="); + smapword_s(n); + writes("\"\n"); + if (q) + *q=MDIRSEP[0]; +} + +static void senderr(char *errmsg) +{ + writes("-ERR "); + writes(errmsg); + writes("\n"); +} + +static int calc_quota(unsigned long n, void *voidptr) +{ + return do_copy_quota_calc(n+1, 0, voidptr); +} + +/* Copy msg to another folder */ + +static void copieduid(unsigned long n, char *newname) +{ + char *p, *q; + + writes("* COPY "); + writen(n); + writes(" \"NEWUID="); + + p=strrchr(newname, '/')+1; + + if ((q=strrchr(p, MDIRSEP[0])) != NULL) + *q=0; + + smapword_s(p); + writes("\"\n"); +} + +static int do_copyKeywords(struct libmail_kwMessage *msg, + const char *destmailbox, + const char *newname) +{ + char *tmpkname, *newkname; + + if (!msg || !msg->firstEntry) + return 0; + + if (maildir_kwSave(destmailbox, newname, + msg, &tmpkname, &newkname, 0)) + { + perror("maildir_kwSave"); + return -1; + } + + rename(tmpkname, newkname); + free(tmpkname); + free(newkname); + return 0; +} + +static void fixnewfilename(char *p) +{ + char *q; + + /* Nice hack: */ + + q=strrchr(strrchr(p, '/'), MDIRSEP[0]); + + if (strcmp(q, MDIRSEP "2,") == 0) + { + *q=0; + memcpy(strrchr(p, '/')-3, "new", 3); + } +} + +static int do_copymsg(unsigned long n, void *voidptr) +{ + char buf[SMAP_BUFSIZ]; + struct copyquotainfo *cqinfo=(struct copyquotainfo *)voidptr; + struct imapflags new_flags; + int fd; + struct stat stat_buf; + FILE *fp; + char *tmpname, *newname; + + fd=imapscan_openfile(current_mailbox, ¤t_maildir_info, n); + if (fd < 0) return (0); + + if (fstat(fd, &stat_buf) < 0) + { + close(fd); + return (0); + } + + get_message_flags(current_maildir_info.msgs+n, 0, &new_flags); + + fp=maildir_mkfilename(cqinfo->destmailbox, + &new_flags, stat_buf.st_size, + &tmpname, &newname); + + fixnewfilename(newname); + + if (!fp) + { + close(fd); + return (-1); + } + + while (stat_buf.st_size) + { + int n=sizeof(buf); + + if (n > stat_buf.st_size) + n=stat_buf.st_size; + + n=read(fd, buf, n); + + if (n <= 0 || fwrite(buf, 1, n, fp) != n) + { + close(fd); + fclose(fp); + unlink(tmpname); + free(tmpname); + free(newname); + fprintf(stderr, + "ERR: error copying a message, user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + + return (-1); + } + stat_buf.st_size -= n; + } + close(fd); + + if (fflush(fp) || ferror(fp)) + { + fclose(fp); + unlink(tmpname); + free(tmpname); + free(newname); + fprintf(stderr, + "ERR: error copying a message, user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + return (-1); + } + fclose(fp); + + if (do_copyKeywords(current_maildir_info.msgs[n].keywordMsg, + cqinfo->destmailbox, + strrchr(newname, '/')+1)) + { + unlink(tmpname); + free(tmpname); + free(newname); + fprintf(stderr, + "ERR: error copying keywords, " + "user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + return (-1); + } + + current_maildir_info.msgs[n].copiedflag=1; + + maildir_movetmpnew(tmpname, newname); + set_time(newname, stat_buf.st_mtime); + free(tmpname); + + copieduid(n+1, newname); + free(newname); + return 0; +} + +static int do_movemsg(unsigned long n, void *voidptr) +{ + char *filename; + struct copyquotainfo *cqinfo=(struct copyquotainfo *)voidptr; + char *newfilename; + + if (n >= current_maildir_info.nmessages) + return 0; + + filename=maildir_filename(current_mailbox, 0, + current_maildir_info.msgs[n].filename); + + if (!filename) + return 0; + + newfilename=malloc(strlen(cqinfo->destmailbox) + sizeof("/cur") + + strlen(strrchr(filename, '/'))); + + if (!newfilename) + { + free(filename); + write_error_exit(0); + } + + strcat(strcat(strcpy(newfilename, cqinfo->destmailbox), + "/cur"), strrchr(filename, '/')); + + if (do_copyKeywords(current_maildir_info.msgs[n].keywordMsg, + cqinfo->destmailbox, + strrchr(newfilename, '/')+1)) + { + fprintf(stderr, + "ERR: error copying keywords, " + "user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + + free(filename); + free(newfilename); + return -1; + } + + + if (maildir_movetmpnew(filename, newfilename) == 0) + { + copieduid(n+1, newfilename); + free(filename); + free(newfilename); + return 0; + } + + if (do_copymsg(n, voidptr)) + return -1; + + unlink(filename); + free(filename); + free(newfilename); + return 0; +} + +static struct searchinfo *createSearch2(char *w, + struct searchinfo **head, char **ptr); + +static struct searchinfo *createSearch(struct searchinfo **head, char **ptr) +{ + char *w=getword(ptr); + struct searchinfo *siAnd, *n; + + up(w); + + if (strcmp(w, "MARKED") == 0) + { + w=getword(ptr); + up(w); + + n=createSearch2(w, head, ptr); + + if (!n) + return NULL; + + siAnd=alloc_search(head); + siAnd->type=search_and; + + siAnd->b=n; + + siAnd->a=n=alloc_search(head); + + n->type=search_msgflag; + if (!(n->as=strdup("\\FLAGGED"))) + write_error_exit(0); + + return siAnd; + } + + if (strcmp(w, "UNMARKED") == 0) + { + w=getword(ptr); + up(w); + + n=createSearch2(w, head, ptr); + + if (!n) + return NULL; + + siAnd=alloc_search(head); + siAnd->type=search_and; + + siAnd->b=n; + + siAnd->a=n=alloc_search(head); + + n->type=search_not; + + n=n->a=alloc_search(head); + + n->type=search_msgflag; + if (!(n->as=strdup("\\FLAGGED"))) + write_error_exit(0); + + return siAnd; + } + + if (strcmp(w, "ALL") == 0) + { + w=getword(ptr); + up(w); + return createSearch2(w, head, ptr); + } + + { + char *ww=getword(ptr); + up(ww); + n=createSearch2(ww, head, ptr); + + if (!n) + return NULL; + + siAnd=alloc_search(head); + siAnd->type=search_and; + + siAnd->b=n; + + siAnd->a=n=alloc_search(head); + + n->type=search_messageset; + if (!(n->as=strdup(w))) + write_error_exit(0); + + for (ww=n->as; *ww; ww++) + if (*ww == '-') + *ww=':'; + + if (!ismsgset_str(n->as)) + { + errno=EINVAL; + return NULL; + } + } + return siAnd; +} + +static struct searchinfo *createSearch2(char *w, + struct searchinfo **head, char **ptr) +{ + int notflag=0; + struct searchinfo *n; + + if (strcmp(w, "NOT") == 0) + { + notflag=1; + w=getword(ptr); + up(w); + } + + if (strcmp(w, "REPLIED") == 0) + { + n=alloc_search(head); + n->type=search_msgflag; + if (!(n->as=strdup("\\ANSWERED"))) + write_error_exit(0); + } + else if (strcmp(w, "DELETED") == 0) + { + n=alloc_search(head); + n->type=search_msgflag; + if (!(n->as=strdup("\\DELETED"))) + write_error_exit(0); + } + else if (strcmp(w, "DRAFT") == 0) + { + n=alloc_search(head); + n->type=search_msgflag; + if (!(n->as=strdup("\\DRAFT"))) + write_error_exit(0); + } + else if (strcmp(w, "SEEN") == 0) + { + n=alloc_search(head); + n->type=search_msgflag; + if (!(n->as=strdup("\\SEEN"))) + write_error_exit(0); + } + else if (strcmp(w, "KEYWORD") == 0) + { + n=alloc_search(head); + n->type=search_msgkeyword; + if (!(n->as=strdup(getword(ptr)))) + write_error_exit(0); + } + else if (strcmp(w, "FROM") == 0 || + strcmp(w, "TO") == 0 || + strcmp(w, "CC") == 0 || + strcmp(w, "BCC") == 0 || + strcmp(w, "SUBJECT") == 0) + { + n=alloc_search(head); + n->type=search_header; + if (!(n->cs=strdup(w))) + write_error_exit(0); + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "HEADER") == 0) + { + n=alloc_search(head); + n->type=search_header; + if (!(n->cs=strdup(getword(ptr)))) + write_error_exit(0); + up(n->cs); + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "BODY") == 0) + { + n=alloc_search(head); + n->type=search_body; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "TEXT") == 0) + { + n=alloc_search(head); + n->type=search_text; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "BEFORE") == 0) + { + n=alloc_search(head); + n->type=search_before; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "ON") == 0) + { + n=alloc_search(head); + n->type=search_on; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "SINCE") == 0) + { + n=alloc_search(head); + n->type=search_since; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "SENTBEFORE") == 0) + { + n=alloc_search(head); + n->type=search_sentbefore; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "SENTON") == 0) + { + n=alloc_search(head); + n->type=search_senton; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "SINCE") == 0) + { + n=alloc_search(head); + n->type=search_sentsince; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "SMALLER") == 0) + { + n=alloc_search(head); + n->type=search_smaller; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else if (strcmp(w, "LARGER") == 0) + { + n=alloc_search(head); + n->type=search_larger; + n->as=strdup(getword(ptr)); + if (!n->as) + write_error_exit(0); + } + else + { + errno=EINVAL; + return NULL; + } + + if (notflag) + { + struct searchinfo *p=alloc_search(head); + + p->type=search_not; + p->a=n; + n=p; + } + return n; +} + +static int do_copyto(char *toFolder, + int (*do_func)(unsigned long, void *), + const char *acls) +{ + int has_quota=0; + struct copyquotainfo cqinfo; + struct maildirsize quotainfo; + + cqinfo.destmailbox=toFolder; + cqinfo.nbytes=0; + cqinfo.nfiles=0; + cqinfo.acls=acls; + + if (maildirquota_countfolder(toFolder)) + { + if (maildir_openquotafile("ainfo, ".") == 0) + { + if (quotainfo.fd >= 0) + has_quota=1; + maildir_closequotafile("ainfo); + } + + if (has_quota > 0 && applymsgset( &calc_quota, &cqinfo )) + has_quota= -1; + } + + if (has_quota > 0 && cqinfo.nfiles > 0) + { + if (maildir_quota_add_start(".", "ainfo, + cqinfo.nbytes, + cqinfo.nfiles, + getenv("MAILDIRQUOTA"))) + { + errno=ENOSPC; + return (-1); + } + + maildir_quota_add_end("ainfo, + cqinfo.nbytes, + cqinfo.nfiles); + } + + return applymsgset(do_func, &cqinfo); +} + +static int copyto(char *toFolder, int do_move, const char *acls) +{ + if (!do_move) + return do_copyto(toFolder, &do_copymsg, acls); + + if (!current_mailbox_shared && + maildirquota_countfolder(current_mailbox) == + maildirquota_countfolder(toFolder)) + { + if (do_copyto(toFolder, do_movemsg, acls)) + return -1; + + doNoop(0); + return(0); + } + + if (do_copyto(toFolder, &do_copymsg, acls)) + return -1; + + applymsgset(&msg_expunge, NULL); + doNoop(0); + return 0; +} + +struct smap1_search_results { + + unsigned prev_runs; + + unsigned long prev_search_hit; + unsigned long prev_search_hit_start; +}; + +static void smap1_search_cb_range(struct smap1_search_results *searchResults) +{ + if (searchResults->prev_runs > 100) + { + writes("\n"); + searchResults->prev_runs=0; + } + + if (searchResults->prev_runs == 0) + writes("* SEARCH"); + + writes(" "); + writen(searchResults->prev_search_hit_start); + if (searchResults->prev_search_hit_start != + searchResults->prev_search_hit) + { + writes("-"); + writen(searchResults->prev_search_hit); + } + ++searchResults->prev_runs; +} + +static void smap1_search_cb(struct searchinfo *si, + struct searchinfo *sihead, + int isuid, unsigned long i, void *dummy) +{ + struct smap1_search_results *searchResults= + (struct smap1_search_results *)dummy; + + ++i; + + if (searchResults->prev_search_hit == 0) + { + searchResults->prev_search_hit= + searchResults->prev_search_hit_start=i; + return; + } + + if (i != searchResults->prev_search_hit+1) + { + smap1_search_cb_range(searchResults); + searchResults->prev_search_hit_start=i; + } + + searchResults->prev_search_hit=i; +} + +static void accessdenied(const char *acl_required) +{ + writes("-ERR Access denied: ACL \""); + writes(acl_required); + writes("\" is required\n"); +} + +static int getacl(const char *ident, + const maildir_aclt *acl, + void *cb_arg) +{ + int *n=(int *)cb_arg; + + if (*n > 5) + { + writes("\n"); + *n=0; + } + + if (*n == 0) + writes("* GETACL"); + + writes(" "); + smapword(ident); + writes(" "); + smapword(maildir_aclt_ascstr(acl)); + ++*n; + return 0; +} + +struct setacl_info { + struct maildir_info minfo; + char **ptr; +}; + +static int dosetdeleteacl(void *cb_arg, int); + +static int setacl(void *cb_arg) +{ + return dosetdeleteacl(cb_arg, 0); +} + +static int deleteacl(void *cb_arg) +{ + return dosetdeleteacl(cb_arg, 1); +} + +static int dosetdeleteacl(void *cb_arg, int dodelete) +{ + struct setacl_info *sainfo=(struct setacl_info *)cb_arg; + char *q; + int cnt; + const char *identifier; + const char *action; + const char *err_failedrights; + char *path; + + maildir_aclt_list aclt_list; + + if (read_acls(&aclt_list, &sainfo->minfo) < 0) + { + writes("-ERR Unable to read existing ACLS: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + + q=compute_myrights(&aclt_list, + sainfo->minfo.owner); + + if (!q || !strchr(q, ACL_ADMINISTER[0])) + { + if (q) free(q); + maildir_aclt_list_destroy(&aclt_list); + accessdenied(ACL_ADMINISTER); + return 0; + } + + free(q); + + while (*(identifier=getword(sainfo->ptr))) + { + if (dodelete) + { + if (maildir_aclt_list_del(&aclt_list, + identifier) < 0) + { + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + continue; + } + + action=getword(sainfo->ptr); + + if (*action == '+') + { + maildir_aclt newacl; + const maildir_aclt *oldacl; + + if (maildir_aclt_init(&newacl, + action+1, + NULL) < 0) + { + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + + + oldacl=maildir_aclt_list_find(&aclt_list, + identifier + ); + if (oldacl) + { + if (maildir_aclt_add(&newacl, + NULL, + oldacl) + < 0) + { + maildir_aclt_destroy(&newacl); + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + } + + if (maildir_aclt_list_add(&aclt_list, + identifier, + NULL, + &newacl) < 0) + { + maildir_aclt_destroy(&newacl); + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + + } + maildir_aclt_destroy(&newacl); + continue; + } + + if (*action == '-') + { + maildir_aclt newacl; + const maildir_aclt *oldacl; + + oldacl=maildir_aclt_list_find(&aclt_list, + identifier + ); + + if (!oldacl) + continue; + + if (maildir_aclt_init(&newacl, + NULL, + oldacl) < 0) + { + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + + + if (maildir_aclt_del(&newacl, + action+1, NULL) + < 0) + { + maildir_aclt_destroy(&newacl); + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + + if (strlen(maildir_aclt_ascstr(&newacl)) + == 0 ? + maildir_aclt_list_del(&aclt_list, + identifier) + :maildir_aclt_list_add(&aclt_list, + identifier, + NULL, + &newacl) < 0) + { + maildir_aclt_destroy(&newacl); + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + + } + maildir_aclt_destroy(&newacl); + continue; + } + + if (strlen(action) == 0 ? + maildir_aclt_list_del(&aclt_list, + identifier): + maildir_aclt_list_add(&aclt_list, + identifier, + action, NULL) < 0) + { + maildir_aclt_list_destroy(&aclt_list); + writes("-ERR Error: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + } + + path=maildir_name2dir(".", sainfo->minfo.maildir); + + err_failedrights=NULL; + if (!path || + maildir_acl_write(&aclt_list, sainfo->minfo.homedir, + path[0] == '.' && path[1] == '/' + ? path+2:path, + sainfo->minfo.owner, + &err_failedrights)) + { + if (path) + free(path); + + if (err_failedrights) + { + writes("* ACLMINIMUM "); + writes(err_failedrights); + writes(" "); + aclminimum(err_failedrights); + writes("\n"); + } + writes("-ERR ACL update failed\n"); + maildir_aclt_list_destroy(&aclt_list); + return 0; + } + + cnt=0; + maildir_aclt_list_enum(&aclt_list, + getacl, &cnt); + if (cnt) + writes("\n"); + maildir_aclt_list_destroy(&aclt_list); + + /* Reread ACLs if the current mailbox's ACLs have changed */ + + if (read_acls(&aclt_list, &sainfo->minfo) < 0) + { + writes("-ERR Unable to re-read ACLS: "); + writes(strerror(errno)); + writes("\n"); + return 0; + } + + maildir_aclt_list_destroy(&aclt_list); + writes("+OK Updated ACLs\n"); + return 0; +} + +static int checkacl(char **folder, struct maildir_info *minfo, + const char *acls) +{ + char *q; + + maildir_aclt_list aclt_list; + + if (maildir_info_smap_find(minfo, folder, getenv("AUTHENTICATED")) < 0) + return -1; + + if (read_acls(&aclt_list, minfo) < 0) + { + maildir_info_destroy(minfo); + return -1; + } + + q=compute_myrights(&aclt_list, minfo->owner); + maildir_aclt_list_destroy(&aclt_list); + + while (*acls) + { + if (q == NULL || strchr(q, *acls) == NULL) + { + if (q) free(q); + maildir_info_destroy(minfo); + return -1; + } + ++acls; + } + if (q) + free(q); + return 0; +} + +void smap() +{ + char buffer[8192]; + char *ptr; + struct imapflags add_flags; + int in_add=0; + char *add_from=NULL; + char *add_folder=NULL; + time_t add_internaldate=0; + char *add_notify=NULL; + unsigned add_rcpt_count=0; + struct libmail_kwMessage *addKeywords=NULL; + + struct add_rcptlist *add_rcpt_list=NULL; + + char rights_buf[40]; + + imapscan_init(¤t_maildir_info); + memset(&add_flags, 0, sizeof(add_flags)); + +#define GETFOLDER(acl) ( strcpy(rights_buf, (acl)), \ + getAccessToFolder(&ptr, rights_buf)) + + for (;;) + { + char *p; + + writeflush(); + read_timeout(30 * 60); + smap_readline(buffer, sizeof(buffer)); + + ptr=buffer; + + p=getword(&ptr); + up(p); + + if (strcmp(p, "ADD") == 0) + { + char **argvec; + const char *okmsg="So far, so good..."; + int err_sent=0; + + in_add=1; + while (*(p=getword(&ptr))) + { + char *q=strchr(p, '='); + + if (q) + *q++=0; + up(p); + + if (strcmp(p, "FOLDER") == 0) + { + if (add_folder) + free(add_folder); + + add_folder= + GETFOLDER(ACL_INSERT + ACL_DELETEMSGS + ACL_SEEN + ACL_WRITE); + if (!add_folder) + { + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + break; + } + + if (strchr(rights_buf, + ACL_INSERT[0]) + == NULL) + { + accessdenied(ACL_INSERT); + free(add_folder); + add_folder=NULL; + break; + } + + okmsg="Will add to this folder"; + } + + if (strcmp(p, "MAILFROM") == 0 && q) + { + if (add_from) + free(add_from); + if ((add_from=strdup(q)) == NULL) + { + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + break; + } + okmsg="MAIL FROM set"; + } + + if (strcmp(p, "NOTIFY") == 0 && q) + { + if (add_notify) + free(add_notify); + if ((add_notify=strdup(q)) == NULL) + { + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + break; + } + okmsg="NOTIFY set"; + } + + if (strcmp(p, "RCPTTO") == 0 && q) + { + struct add_rcptlist *rcpt= + malloc(sizeof(struct + add_rcptlist)); + + if (rcpt == NULL || + (rcpt->rcptto=strdup(q)) == NULL) + { + if (rcpt) + free(rcpt); + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + break; + } + rcpt->next=add_rcpt_list; + add_rcpt_list=rcpt; + ++add_rcpt_count; + okmsg="RCPT TO set"; + } + + if (strcmp(p, "FLAGS") == 0 && q) + { + memset(&add_flags, 0, + sizeof(add_flags)); + *--q='='; + parseflags(q, &add_flags); + + if (strchr(rights_buf, + ACL_SEEN[0]) + == NULL) + add_flags.seen=0; + if (strchr(rights_buf, + ACL_DELETEMSGS[0]) + == NULL) + add_flags.deleted=0; + if (strchr(rights_buf, + ACL_WRITE[0]) + == NULL) + add_flags.answered= + add_flags.flagged= + add_flags.drafts=0; + + okmsg="FLAGS set"; + } + + if (strcmp(p, "KEYWORDS") == 0 && q && + keywords() && strchr(rights_buf, + ACL_WRITE[0])) + { + if (addKeywords) + libmail_kwmDestroy(addKeywords); + + addKeywords=libmail_kwmCreate(); + + if (addKeywords == NULL) + { + write_error_exit(0); + } + + *--q='='; + parsekeywords(q, &addKeywords); + okmsg="KEYWORDS set"; + } + + if (strcmp(p, "INTERNALDATE") == 0 && q) + { + add_internaldate=rfc822_parsedt(q); + + if (add_internaldate) + okmsg="INTERNALDATE set"; + } + + if (p[0] == '{') + { + char *tmpname, *newname; + char *s; + char *tmpKeywords=NULL; + char *newKeywords=NULL; + FILE *fp; + unsigned long n; + + fp=maildir_mkfilename(add_folder + ?add_folder + :".", + &add_flags, + 0, + &tmpname, + &newname); + + if (!fp) + { + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + break; + } + + fixnewfilename(newname); + + current_temp_fd=fileno(fp); + current_temp_fn=tmpname; + + n=add_msg(fp, p+1, buffer, + sizeof(buffer)); + + if (n) + { + s=maildir_requota(newname, n); + + if (!s) + n=0; + else + { + free(newname); + newname=s; + } + } + + current_temp_fd= -1; + current_temp_fn= NULL; + + if (n > 0 && add_folder && + maildirquota_countfolder(add_folder) + && maildirquota_countfile(newname)) + { + struct maildirsize quotainfo; + + if (maildir_quota_add_start(add_folder, "ainfo, n, 1, + getenv("MAILDIRQUOTA"))) + { + errno=ENOSPC; + n=0; + } + else + maildir_quota_add_end("ainfo, n, 1); + } + + fclose(fp); + + chmod(tmpname, 0600); + + if (add_folder && n && addKeywords) + { + if (maildir_kwSave(add_folder, + strrchr(newname, '/')+1, + addKeywords, + &tmpKeywords, + &newKeywords, + 0)) + { + tmpKeywords=NULL; + newKeywords=NULL; + n=0; + perror("maildir_kwSave"); + } + } + + argvec=NULL; + + if (add_rcpt_count > 0 && n) + { + argvec=malloc(sizeof(char *) + * (add_rcpt_count + +10)); + + if (!argvec) + n=0; + } + + if (argvec) + { + int i=1; + struct add_rcptlist *l; + + argvec[i++]="-oi"; + + argvec[i++]="-f"; + argvec[i++]=add_from + ? add_from: + (char *) + defaultSendFrom(); + + if (add_notify) + { + argvec[i++]="-N"; + argvec[i++]=add_notify; + } + + for (l=add_rcpt_list; l; + l=l->next) + { + argvec[i++]=l->rcptto; + } + argvec[i]=0; + + i=imapd_sendmsg(tmpname, argvec, + &senderr); + free(argvec); + if (i) + { + n=0; + err_sent=1; + } + } + + if (tmpKeywords) + { + rename(tmpKeywords, + newKeywords); + free(tmpKeywords); + free(newKeywords); + } + + if (add_folder && n) + { + if (maildir_movetmpnew(tmpname, + newname) + ) + n=0; + else + { + if (add_internaldate) + set_time(newname, + add_internaldate); + adduid(newname); + } + } + + if (n == 0) + { + unlink(tmpname); + free(tmpname); + free(newname); + if (!err_sent) + { + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + } + break; + } + + unlink(tmpname); + + free(tmpname); + free(newname); + okmsg="Message saved"; + p=NULL; + break; + } + } + + if (p && *p) + continue; /* Error inside the loop */ + + writes("+OK "); + writes(okmsg); + writes("\n"); + + if (p) + continue; + } + + if (in_add) + { + struct add_rcptlist *l; + + while ((l=add_rcpt_list) != NULL) + { + add_rcpt_list=l->next; + free(l->rcptto); + free(l); + } + memset(&add_flags, 0, sizeof(add_flags)); + if (add_from) + free(add_from); + if (add_folder) + free(add_folder); + if (add_notify) + free(add_notify); + + if (addKeywords) + libmail_kwmDestroy(addKeywords); + + in_add=0; + add_from=NULL; + add_folder=NULL; + add_internaldate=0; + add_notify=NULL; + addKeywords=NULL; + add_rcpt_count=0; + if (!p) + continue; /* Just added a message */ + } + + if (strcmp(p, "LOGOUT") == 0) + break; + + if (strcmp(p, "RSET") == 0) + { + writes("+OK Reset\n"); + continue; + } + + if (strcmp(p, "GETACL") == 0 || + strcmp(p, "ACL") == 0) + { + char **fn=fn_fromwords(&ptr); + struct maildir_info minfo; + maildir_aclt_list aclt_list; + char *q; + int cnt; + + if (!fn) + { + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (maildir_info_smap_find(&minfo, fn, + getenv("AUTHENTICATED")) + < 0) + { + maildir_smapfn_free(fn); + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (read_acls(&aclt_list, &minfo) < 0) + { + maildir_info_destroy(&minfo); + maildir_smapfn_free(fn); + writes("-ERR Unable to read" + " existing ACLS: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + q=compute_myrights(&aclt_list, + minfo.owner); + + if (!q || + strcmp(p, "ACL") ? + !strchr(q, ACL_ADMINISTER[0]) + : + !maildir_acl_canlistrights(q) + ) + { + if (q) free(q); + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&minfo); + maildir_smapfn_free(fn); + accessdenied(ACL_ADMINISTER); + continue; + } + if (strcmp(p, "ACL") == 0) + { + writes("* ACL "); + smapword(q); + writes("\n"); + free(q); + } + else + { + free(q); + cnt=0; + maildir_aclt_list_enum(&aclt_list, + getacl, &cnt); + if (cnt) + writes("\n"); + } + maildir_aclt_list_destroy(&aclt_list); + maildir_info_destroy(&minfo); + maildir_smapfn_free(fn); + writes("+OK ACLs retrieved\n"); + continue; + } + + if (strcmp(p, "SETACL") == 0 || + strcmp(p, "DELETEACL") == 0) + { + char **fn=fn_fromwords(&ptr); + struct setacl_info sainfo; + + if (!fn) + { + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (maildir_info_smap_find(&sainfo.minfo, + fn, getenv("AUTHENTICATED")) + < 0) + { + maildir_smapfn_free(fn); + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + + sainfo.ptr= &ptr; + + acl_lock(sainfo.minfo.homedir, + *p == 'S' ? setacl:deleteacl, + &sainfo); + + maildir_smapfn_free(fn); + maildir_info_destroy(&sainfo.minfo); + continue; + } + + if (strcmp(p, "LIST") == 0) + { + struct list_hier *hier=NULL; + + listcmd(&hier, &hier, &ptr); + continue; + } + + if (strcmp(p, "STATUS") == 0) + { + char *t; + struct imapscaninfo other_info, *loaded_infoptr, + *infoptr; + unsigned long n, i; + + getword(&ptr); + + t=GETFOLDER(ACL_LOOKUP); + + if (!t) + { + writes("-ERR Cannot read folder status: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (strchr(rights_buf, ACL_LOOKUP[0]) == NULL) + { + accessdenied(ACL_LOOKUP); + continue; + } + + if (current_mailbox && + strcmp(current_mailbox, t) == 0) + { + loaded_infoptr=0; + infoptr= ¤t_maildir_info; + } + else + { + loaded_infoptr= &other_info; + infoptr=loaded_infoptr; + + imapscan_init(loaded_infoptr); + + if (imapscan_maildir(infoptr, t, 1, 1, NULL)) + { + writes("-ERR Cannot read" + " folder status: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + } + + writes("* STATUS EXISTS="); + writen(infoptr->nmessages+infoptr->left_unseen); + + n=infoptr->left_unseen, i; + + for (i=0; i<infoptr->nmessages; i++) + { + const char *p=infoptr->msgs[i].filename; + + p=strrchr(p, MDIRSEP[0]); + if (p && strncmp(p, MDIRSEP "2,", 3) == 0 && + strchr(p, 'S')) continue; + ++n; + } + writes(" UNSEEN="); + writen(n); + writes("\n+OK Folder status retrieved\n"); + if (loaded_infoptr) + imapscan_free(loaded_infoptr); + continue; + } + + if (strcmp(p, "CREATE") == 0) + { + char *t; + + strcpy(rights_buf, ACL_CREATE); + t=getCreateFolder(&ptr, rights_buf); + + if (t) + { + if (mdcreate(t)) + { + writes("-ERR Cannot create folder: "); + writes(strerror(errno)); + writes("\n"); + } + else + { + writes("+OK Folder created\n"); + } + free(t); + } + else + { + if (rights_buf[0] == 0) + accessdenied(ACL_CREATE); + else + { + writes("-ERR Cannot create folder: "); + writes(strerror(errno)); + writes("\n"); + } + } + continue; + } + + if (strcmp(p, "MKDIR") == 0) + { + char *t; + + strcpy(rights_buf, ACL_CREATE); + t=getCreateFolder(&ptr, rights_buf); + + if (t) + { + writes("+OK Folder created\n"); + free(t); + } + else if (rights_buf[0] == 0) + accessdenied(ACL_CREATE); + else + { + writes("-ERR Cannot create folder: "); + writes(strerror(errno)); + writes("\n"); + } + continue; + } + + if (strcmp(p, "RMDIR") == 0) + { + char *t; + + strcpy(rights_buf, ACL_DELETEFOLDER); + t=getCreateFolder(&ptr, rights_buf); + + if (t) + { + writes("+OK Folder deleted\n"); + free(t); + } + else if (rights_buf[0] == 0) + accessdenied(ACL_DELETEFOLDER); + else + { + writes("-ERR Cannot create folder: "); + writes(strerror(errno)); + writes("\n"); + } + continue; + } + + if (strcmp(p, "DELETE") == 0) + { + char **fn; + char *t=NULL; + + fn=fn_fromwords(&ptr); + + if (fn) + { + struct maildir_info minfo; + + if (maildir_info_smap_find(&minfo, fn, + getenv("AUTHENTICATED")) == 0) + { + if (minfo.homedir && minfo.maildir) + { + maildir_aclt_list list; + char *q; + + if (strcmp(minfo.maildir, + INBOX) == 0) + { + writes("-ERR INBOX may" + " not be deleted\n"); + maildir_info_destroy(&minfo); + continue; + } + + if (read_acls(&list, &minfo) + < 0) + { + maildir_info_destroy(&minfo); + accessdenied(ACL_DELETEFOLDER); + continue; + } + + q=compute_myrights(&list, + minfo.owner + ); + + if (!q || + strchr(q, + ACL_DELETEFOLDER[0]) + == NULL) + { + if (q) + free(q); + maildir_info_destroy(&minfo); + accessdenied(ACL_DELETEFOLDER); + continue; + } + free(q); + maildir_aclt_list_destroy(&list); + t=maildir_name2dir(minfo.homedir, + minfo.maildir); + } + maildir_info_destroy(&minfo); + } + } + + if (t && current_mailbox && + strcmp(t, current_mailbox) == 0) + { + writes("-ERR Cannot DELETE currently open folder.\n"); + free(t); + continue; + } + + + if (t) + { + if (mddelete(t) == 0) + { + maildir_quota_recalculate("."); + writes("+OK Folder deleted"); + } + else + { + writes("-ERR Cannot delete folder: "); + writes(strerror(errno)); + } + writes("\n"); + free(t); + + } + else + { + if (t) + { + free(t); + errno=EINVAL; + } + + writes("-ERR Unable to delete folder: "); + writes(strerror(errno)); + writes("\n"); + } + continue; + } + + if (strcmp(p, "RENAME") == 0) + { + struct maildir_info msrc, mdst; + char **fnsrc, **fndst; + size_t i; + char *save; + const char *errmsg; + + if ((fnsrc=fn_fromwords(&ptr)) == NULL) + { + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if ((fndst=fn_fromwords(&ptr)) == NULL) + { + maildir_smapfn_free(fnsrc); + writes("-ERR "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + for (i=0; fndst[i]; i++) + ; + + if (i == 0) + { + maildir_smapfn_free(fnsrc); + maildir_smapfn_free(fndst); + writes("-ERR Invalid destination folder name\n"); + continue; + } + + if (checkacl(fnsrc, &msrc, ACL_DELETEFOLDER)) + { + maildir_smapfn_free(fnsrc); + maildir_smapfn_free(fndst); + accessdenied(ACL_DELETEFOLDER); + continue; + } + save=fndst[--i]; + fndst[i]=NULL; + + if (checkacl(fndst, &mdst, ACL_CREATE)) + { + fndst[i]=save; + maildir_smapfn_free(fnsrc); + maildir_smapfn_free(fndst); + maildir_info_destroy(&msrc); + accessdenied(ACL_CREATE); + continue; + } + + fndst[i]=save; + + maildir_info_destroy(&mdst); + + if (maildir_info_smap_find(&mdst, fndst, + getenv("AUTHENTICATED")) < 0) + { + maildir_smapfn_free(fnsrc); + maildir_smapfn_free(fndst); + maildir_info_destroy(&msrc); + writes("-ERR Internal error in RENAME: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (folder_rename(&msrc, &mdst, &errmsg)) + { + writes("-ERR "); + writes(*errmsg == '@' ? errmsg+1:errmsg); + if (*errmsg == '@') + writes(strerror(errno)); + writes("\n"); + } + else + { + writes("+OK Folder renamed.\n"); + } + maildir_info_destroy(&msrc); + maildir_info_destroy(&mdst); + continue; + } + + if (strcmp(p, "OPEN") == 0 || + strcmp(p, "SOPEN") == 0) + { + char **fn; + char *q; + const char *snapshot=0; + struct maildir_info minfo; + maildir_aclt_list aclt_list; + + if (current_mailbox) + { + free(current_mailbox); + imapscan_free(¤t_maildir_info); + imapscan_init(¤t_maildir_info); + current_mailbox=0; + } + if (current_mailbox_acl) + free(current_mailbox_acl); + current_mailbox_acl=NULL; + current_mailbox_shared=0; + + fetch_free_cache(); + + if (p[0] == 'S') + snapshot=getword(&ptr); + + fn=fn_fromwords(&ptr); + + if (!fn) + { + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (maildir_info_smap_find(&minfo, fn, + getenv("AUTHENTICATED")) + < 0) + { + maildir_smapfn_free(fn); + writes("-ERR Invalid folder: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + if (read_acls(&aclt_list, &minfo) < 0) + { + maildir_info_destroy(&minfo); + maildir_smapfn_free(fn); + writes("-ERR Unable to read" + " existing ACLS: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + + q=compute_myrights(&aclt_list, minfo.owner); + maildir_aclt_list_destroy(&aclt_list); + maildir_smapfn_free(fn); + + if (!q || strchr(q, ACL_READ[0]) == NULL) + { + if (q) free(q); + maildir_info_destroy(&minfo); + accessdenied(ACL_READ); + maildir_info_destroy(&minfo); + continue; + } + current_mailbox_acl=q; + current_mailbox=maildir_name2dir(minfo.homedir, + minfo.maildir); + + if (current_mailbox == NULL) + { + fprintf(stderr, "ERR: Internal error" + " in maildir_name2dir(%s,%s)\n", + minfo.homedir, + minfo.maildir); + maildir_info_destroy(&minfo); + continue; + } + maildir_info_destroy(&minfo); + + snapshot_select(snapshot != NULL); + + if (snapshot_init(current_mailbox, snapshot)) + { + writes("* SNAPSHOTEXISTS "); + smapword(snapshot); + writes("\n"); + smap1_noop(0); + continue; + } + + if (imapscan_maildir(¤t_maildir_info, + current_mailbox, 0, 0, NULL) == 0) + { + snapshot_init(current_mailbox, NULL); + writes("* EXISTS "); + writen(current_maildir_info.nmessages); + writes("\n+OK Folder opened\n"); + continue; + } + + writes("-ERR Cannot open the folder: "); + writes(strerror(errno)); + writes("\n"); + + free(current_mailbox); + current_mailbox=NULL; + continue; + } + + if (strcmp(p, "CLOSE") == 0) + { + if (current_mailbox) + { + free(current_mailbox); + imapscan_free(¤t_maildir_info); + imapscan_init(¤t_maildir_info); + current_mailbox=0; + } + writes("+OK Folder closed\n"); + continue; + } + + if (strcmp(p, "NOOP") == 0) + { + smap1_noop(1); + continue; + } + + if (strcmp(p, "IDLE") == 0) + { + if ((p=getenv("IMAP_ENHANCEDIDLE")) == NULL + || !atoi(p) + || imapenhancedidle()) + imapidle(); + + read_timeout(60); + smap_readline(buffer, sizeof(buffer)); + ptr=buffer; + p=getword(&ptr); + up(p); + if (strcmp(p, "RESUME")) + { + writes("-ERR RESUME is required to follow IDLE\n"); + } + else + writes("+OK Resumed...\n"); + continue; + } + + if (!current_mailbox) + p=""; /* FALLTHROUGH */ + + if (strcmp(p, "EXPUNGE") == 0) + { + int hasSet; + + p=markmsgset(&ptr, &hasSet); + + if (p) + { + if (strchr(current_mailbox_acl, + ACL_EXPUNGE[0]) == NULL) + { + accessdenied(ACL_EXPUNGE); + continue; + } + + if (hasSet) + { + if (strchr(current_mailbox_acl, + ACL_DELETEMSGS[0]) == NULL) + { + accessdenied(ACL_DELETEMSGS); + continue; + } + + applymsgset( &msg_expunge, NULL); + } + else + expunge(); + smap1_noop(0); + continue; + } + } + + if (strcmp(p, "STORE") == 0) + { + struct storeinfo si; + int dummy; + + p=markmsgset(&ptr, &dummy); + + dummy=0; + + if (!p) + dummy=1; + + while (p && *p) + { + char *q=strchr(p, '='); + + if (q) + *q=0; + up(p); + /* Uppercase only the keyword, for now */ + if (q) + *q='='; + + if (strncmp(p, "FLAGS=", 6) == 0) + { + memset(&si, 0, sizeof(si)); + up(p); + parseflags(p, &si.flags); + if ((dummy=applymsgset(&applyflags, + &si)) != 0) + break; + } + else if (strncmp(p, "+FLAGS=", 7) == 0 || + strncmp(p, "-FLAGS=", 7) == 0) + { + memset(&si, 0, sizeof(si)); + up(p); + si.plusminus=p[0]; + parseflags(p, &si.flags); + if ((dummy=applymsgset(&applyflags, + &si)) != 0) + break; + } + else if (strncmp(p, "KEYWORDS=", 9) == 0 && + keywords()) + { + struct libmail_kwMessage *kwm; + + memset(&si, 0, sizeof(si)); + kwm=si.keywords=libmail_kwmCreate(); + + if (!kwm) + write_error_exit(0); + + parsekeywords(p, &si.keywords); + dummy=applymsgset(&applyflags, + &si); + + libmail_kwmDestroy(kwm); + + if (dummy != 0) + break; + } + else if ((strncmp(p, "+KEYWORDS=", 10) == 0 || + strncmp(p, "-KEYWORDS=", 10) == 0) && + keywords()) + { + memset(&si, 0, sizeof(si)); + si.keywords=libmail_kwmCreate(); + + if (!si.keywords) + write_error_exit(0); + si.plusminus=p[0]; + parsekeywords(p, &si.keywords); + dummy=applymsgset(&applyflags, + &si); + + if (dummy == 0) + dummy=addRemoveSmapKeywords(&si); + libmail_kwmDestroy(si.keywords); + + if (dummy != 0) + break; + } + else if (strncmp(p, "INTERNALDATE=", 13) == 0) + { + time_t t; + + up(p); + + t=rfc822_parsedt(p+13); + + if (t && + (dummy=applymsgset(&setdate, &t)) + != 0) + break; + } + + p=getword(&ptr); + } + if (dummy) + { + writes("-ERR Cannot update folder status: "); + writes(strerror(errno)); + writes("\n"); + } + else + writes("+OK Folder status updated\n"); + continue; + } + + if (strcmp(p, "FETCH") == 0) + { + int dummy; + struct smapfetchinfo fi; + int fetch_items=0; + + for (p=markmsgset(&ptr, &dummy); + p && *p; p=getword(&ptr)) + { + if ((fi.entity=strchr(p, '=')) == NULL) + { + up(p); + + if (strcmp(p, "UID") == 0) + fetch_items |= FETCH_UID; + if (strcmp(p, "SIZE") == 0) + fetch_items |= FETCH_SIZE; + if (strcmp(p, "FLAGS") == 0) + fetch_items |= FETCH_FLAGS; + if (strcmp(p, "KEYWORDS") == 0) + fetch_items |= FETCH_KEYWORDS; + if (strcmp(p, "INTERNALDATE") == 0) + fetch_items + |= FETCH_INTERNALDATE; + continue; + } + + *fi.entity++=0; + + fi.hdrs=strrchr(fi.entity, '('); + if (fi.hdrs) + { + char *q; + + *fi.hdrs++=0; + + q=strrchr(fi.hdrs, ')'); + if (q) + *q=0; + up(fi.hdrs); + } + + fi.mimeid=strrchr(fi.entity, '['); + if (fi.mimeid) + { + char *q; + + *fi.mimeid++=0; + q=strrchr(fi.mimeid, ']'); + if (q) + *q=0; + } + + up(p); + + if (strcmp(p, "CONTENTS") == 0 || + strcmp(p, "CONTENTS.PEEK") == 0) + { + fi.peek=strchr(p, '.') != NULL; + if (applymsgset(&do_fetch, &fi) == 0) + { + continue; + } + } + else + { + continue; + } + + writes("-ERR Cannot retrieve message: "); + writes(strerror(errno)); + writes("\n"); + break; + } + + if (!p || !*p) + { + if (fetch_items && + applymsgset(&do_attrfetch, &fetch_items)) + { + writes("-ERR Cannot retrieve message: "); + writes(strerror(errno)); + writes("\n"); + } + else + writes("+OK Message retrieved.\n"); + } + continue; + } + + if (strcmp(p, "COPY") == 0 + || strcmp(p, "MOVE") == 0) + { + int dummy; + int domove= *p == 'M'; + + p=markmsgset(&ptr, &dummy); + + if (dummy && *p == 0) + { + p=GETFOLDER(ACL_INSERT + ACL_DELETEMSGS + ACL_SEEN + ACL_WRITE); + + if (p) + { + if (strchr(rights_buf, ACL_INSERT[0]) + == NULL) + { + free(p); + accessdenied(ACL_INSERT); + continue; + } + + if (copyto(p, domove, rights_buf) == 0) + { + free(p); + writes("+OK Messages copied.\n" + ); + continue; + } + free(p); + } + + writes("-ERR Cannot copy messages: "); + writes(strerror(errno)); + writes("\n"); + continue; + } + writes("-ERR Syntax error.\n"); + continue; + } + + if (strcmp(p, "SEARCH") == 0) + { + struct searchinfo *searchInfo=NULL; + struct searchinfo *si; + struct smap1_search_results searchResults; + + if ((si=createSearch(&searchInfo, &ptr)) == NULL) + { + writes("-ERR SEARCH failed: "); + writes(strerror(errno)); + writes("\n"); + free_search(searchInfo); + continue; + } + + searchResults.prev_runs=0; + searchResults.prev_search_hit=0; + searchResults.prev_search_hit_start=0; + + search_internal(si, searchInfo, "utf-8", 0, + smap1_search_cb, &searchResults); + + if (searchResults.prev_search_hit) + smap1_search_cb_range(&searchResults); + + if (searchResults.prev_runs) + writes("\n"); + + writes("+OK Search completed.\n"); + free_search(searchInfo); + continue; + } + + + writes("-ERR Syntax error.\n"); + } + + writes("* BYE Courier-SMAP server shutting down\n" + "+OK LOGOUT completed\n"); + writeflush(); +} diff --git a/imap/smapsnapshot.c b/imap/smapsnapshot.c new file mode 100644 index 0000000..805fd07 --- /dev/null +++ b/imap/smapsnapshot.c @@ -0,0 +1,739 @@ +/* +** Copyright 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <signal.h> +#include <fcntl.h> +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif +#if HAVE_LOCALE_H +#include <locale.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#include "mysignal.h" +#include "imapd.h" +#include "imapscanclient.h" +#include "imapwrite.h" + +#include "maildir/config.h" +#include "maildir/maildircreate.h" +#include "maildir/maildirrequota.h" +#include "maildir/maildirgetquota.h" +#include "maildir/maildirquota.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildirwatch.h" + +#if HAVE_DIRENT_H +#include <dirent.h> +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include <sys/ndir.h> +#endif +#if HAVE_SYS_DIR_H +#include <sys/dir.h> +#endif +#if HAVE_NDIR_H +#include <ndir.h> +#endif +#endif + + +extern int keywords(); + +/* +** Implement SMAP snapshots. A snapshot is implemented, essentially, by +** saving the current folder index, restoring it, then doing a noop(). +** +** The snapshot file saves uids, not complete filenames. The complete +** filenames are already in courierimapuiddb. Filenames are long, and saving +** them can result in huge snapshot files for large folders. So only uids +** are saved, and when the snapshot is restored the courierimapuiddb file is +** read to obtain the filenames. +*/ + +extern char *current_mailbox; +extern struct imapscaninfo current_maildir_info; +extern char *readline(unsigned i, FILE *); + +static char *snapshot_dir; /* Directory with snapshots */ + +static char *snapshot_last; /* Last snapshot */ +static char *snapshot_cur; /* Current snapshot */ + +static int index_dirty; +static int snapshots_enabled; + +extern void set_time(const char *tmpname, time_t timestamp); +extern void smapword(const char *); + +struct snapshot_list { + struct snapshot_list *next; + + char *filename; + char *prev; + time_t mtime; +}; + +/* +** When cleaning up a snapshot directory, we need to know whether there's +** a later snapshot that claims that this snapshot is the previous +** snapshot (we can safely dump snapshots that were previous snapshots +** of previous snapshots). +*/ + +static struct snapshot_list *find_next_snapshot(struct snapshot_list *s, + const char *n) +{ + const char *p, *q; + + p=strrchr(n, '/'); + + if (p) + n=p+1; + + while (s) + { + p=s->prev; + q=p ? strrchr(p, '/'):NULL; + + if (q && strcmp(q+1, n) == 0) + return s; + + s=s->next; + } + return NULL; +} + +/* +** Delete a snapshot structure, and the actual file +*/ + +static void delete_snapshot(struct snapshot_list *snn) +{ + char *p=malloc(strlen(snapshot_dir)+strlen(snn->filename)+2); + + if (p) + { + strcat(strcat(strcpy(p, snapshot_dir), "/"), snn->filename); + unlink(p); + } + + free(snn->filename); + free(snn->prev); + free(snn); +} + +/* +** Restore a snapshot +*/ + +static int restore_snapshot2(const char *snapshot_dir, + FILE *fp, + struct imapscaninfo *new_index); + +/* +** Part 1: process the first header line of a snapshot file, and allocate a +** new folder index list. +*/ + +static int restore_snapshot(const char *dir, FILE *snapshot_fp, + char **last_snapshot) +{ + int format; + unsigned long s_nmessages, s_uidv, s_nextuid; + char *p; + char *buf; + struct imapscaninfo new_index; + + if ((buf=readline(0, snapshot_fp)) == NULL) + return 0; + + p=strchr(buf, ':'); + if (p) + *p++=0; + + *last_snapshot=NULL; + + if (sscanf(buf, "%d %lu %lu %lu", &format, &s_nmessages, &s_uidv, + &s_nextuid) != 4 || format != SNAPSHOTVERSION) + return 0; /* Don't recognize the header */ + + /* Save the previous snapshot ID */ + + if (p) + { + *last_snapshot=malloc(strlen(dir)+strlen(p)+2); + + if (!last_snapshot) + { + write_error_exit(0); + return 0; + } + + strcat(strcat(strcpy(*last_snapshot, dir), "/"), p); + } + + imapscan_init(&new_index); + + if (s_nmessages && (new_index.msgs=(struct imapscanmessageinfo *) + malloc(s_nmessages * sizeof(*new_index.msgs))) + == 0) + { + write_error_exit(0); + return (0); + } + memset(new_index.msgs, 0, s_nmessages * sizeof(*new_index.msgs)); + + new_index.nmessages=s_nmessages; + new_index.uidv=s_uidv; + new_index.nextuid=s_nextuid; + + if (restore_snapshot2(dir, snapshot_fp, &new_index)) + { + imapscan_copy(¤t_maildir_info, &new_index); + imapscan_free(&new_index); + return 1; + } + imapscan_free(&new_index); + + if (*last_snapshot) + { + free(*last_snapshot); + *last_snapshot=0; + } + + return 0; +} + +/* +** Part 2: combine the snapshot and courierimapuiddb, create a halfbaked +** index from the combination. +*/ + +static int restore_snapshot2(const char *snapshot_dir, + FILE *fp, + struct imapscaninfo *new_index) +{ + unsigned long i; + char *p=malloc(strlen(snapshot_dir) + sizeof("/../" IMAPDB)); + FILE *courierimapuiddb; + int version; + unsigned long uidv; + unsigned long nextuid; + char *uid_line; + unsigned long uid=0; + + if (!p) + { + write_error_exit(0); + return 0; + } + + strcat(strcpy(p, snapshot_dir), "/../" IMAPDB); + + courierimapuiddb=fopen(p, "r"); + free(p); + + if (!courierimapuiddb) + return 0; /* Can't open the uiddb file, no dice */ + + if ((p=readline(0, courierimapuiddb)) == NULL || + sscanf(p, "%d %lu %lu", &version, &uidv, &nextuid) != 3 || + version != IMAPDBVERSION /* Do not recognize the uiddb file */ + + || uidv != new_index->uidv /* Something major happened, abort */ ) + { + fclose(courierimapuiddb); + return 0; + } + + uid_line=readline(0, courierimapuiddb); + + if (uid_line) + { + if (sscanf(uid_line, "%lu", &uid) != 1) + { + fclose(courierimapuiddb); + return 0; + } + } + + /* + ** Both the snapshot file and courierimapuiddb should be in sorted + ** order, by UIDs, rely on that and do what amounts to a merge sort. + */ + + for (i=0; i<new_index->nmessages; i++) + { + unsigned long s_uid; + char flag_buf[128]; + + p=fgets(flag_buf, sizeof(flag_buf)-1, fp); + + if (p == NULL || (p=strchr(p, '\n')) == NULL || + (*p = 0, sscanf(flag_buf, "%lu", &s_uid)) != 1 || + (p=strchr(flag_buf, ':')) == NULL) /* Corrupted file */ + { + fclose(courierimapuiddb); + return 0; + } + + new_index->msgs[i].uid=s_uid; + + /* Try to fill in the filenames to as much of an extent as + ** possible. If IMAPDB no longer has a particular uid listed, + ** that's ok, because the message is now gone, so we just + ** insert an empty filename, which will be expunged by + ** noop() processing, after the snapshot is restored. + */ + + while (uid_line && uid <= s_uid) + { + if (uid == s_uid && + (uid_line=strchr(uid_line, ' ')) != NULL) + /* Jackpot */ + { + new_index->msgs[i].filename= + malloc(strlen(uid_line)+ + strlen(flag_buf)+2); + + if (!new_index->msgs[i].filename) + { + fclose(courierimapuiddb); + write_error_exit(0); + return 0; + } + + strcpy(new_index->msgs[i].filename, + uid_line+1); + + if (p) + { + strcat(strcat(new_index->msgs[i] + .filename, MDIRSEP), + p+1); + } + } + + uid_line=readline(0, courierimapuiddb); + + if (uid_line) + { + if (sscanf(uid_line, "%lu", &uid) != 1) + { + fclose(courierimapuiddb); + return 0; + } + } + } + + + if (new_index->msgs[i].filename == 0) + { + new_index->msgs[i].filename=strdup(""); + /* A noop should get rid of this entry anyway */ + + if (!new_index->msgs[i].filename) + { + fclose(courierimapuiddb); + write_error_exit(0); + return 0; + } + } + } + + fclose(courierimapuiddb); + if (keywords()) + imapscan_restoreKeywordSnapshot(fp, new_index); + return 1; +} + +void snapshot_select(int flag) +{ + snapshots_enabled=flag; +} + +/* +** Initialize snapshots for an opened folder. +** +** Parameters: +** +** folder - the path to a folder that's in the process of opening. +** +** snapshot - not NULL if the client requested a snapshot restore. +** +** Exit code: +** +** When a snapshot is requested, a non-zero exit code means that the +** snapshot has been succesfully restored, and current_mailbox is now +** initialized based on the snapshot. A zero exit code means that the +** snapshot has not been restored, and snapshot_init() needs to be called +** again with snapshot=NULL in order to initialize the snapshot structures. +** +** When a snapshot is not requested, the exit code is always 0 +*/ + +int snapshot_init(const char *folder, const char *snapshot) +{ + struct snapshot_list *sl=NULL; + DIR *dirp; + struct dirent *de; + struct snapshot_list *snn, **ptr; + int cnt; + char *new_dir; + int rc=0; + char *new_snapshot_cur=NULL; + char *new_snapshot_last=NULL; + + if ((new_dir=malloc(strlen(folder)+sizeof("/" SNAPSHOTDIR))) + == NULL) + { + write_error_exit(0); + return rc; + } + + strcat(strcpy(new_dir, folder), "/" SNAPSHOTDIR); + mkdir(new_dir, 0755); /* Create, if doesn't exist */ + + if (snapshot) + { + FILE *fp; + + if (*snapshot == 0 || strchr(snapshot, '/') || + *snapshot == '.') /* Monkey business */ + { + free(new_dir); + return 0; + } + + new_snapshot_cur=malloc(strlen(new_dir) + + strlen(snapshot) + 2); + + if (!new_snapshot_cur) + { + free(new_dir); + write_error_exit(0); + return rc; + } + + strcat(strcat(strcpy(new_snapshot_cur, new_dir), "/"), + snapshot); + + if ((fp=fopen(new_snapshot_cur, "r")) != NULL && + restore_snapshot(new_dir, fp, &new_snapshot_last)) + { + set_time(new_snapshot_cur, time(NULL)); + rc=1; /* We're good to go. Finish everything else */ + } + + if (fp) + { + fclose(fp); + fp=NULL; + } + + if (!rc) /* Couldn't get the snapshot, abort */ + { + free(new_snapshot_cur); + free(new_dir); + return 0; + } + } + + if (snapshot_dir) free(snapshot_dir); + if (snapshot_last) free(snapshot_last); + if (snapshot_cur) free(snapshot_cur); + + snapshot_dir=NULL; + snapshot_last=new_snapshot_last; + snapshot_cur=new_snapshot_cur; + + snapshot_dir=new_dir; + + index_dirty=1; + + /* Get rid of old snapshots as follows */ + + /* Step 1, compile a list of snapshots, sorted in mtime order */ + + dirp=opendir(snapshot_dir); + + while (dirp && (de=readdir(dirp)) != NULL) + { + FILE *fp; + struct stat stat_buf; + + char *n; + + if (de->d_name[0] == '.') continue; + + n=malloc(strlen(snapshot_dir)+strlen(de->d_name)+2); + if (!n) break; /* Furrfu */ + + strcat(strcat(strcpy(n, snapshot_dir), "/"), de->d_name); + + fp=fopen(n, "r"); + + if (fp) + { + char buf[1024]; + + if (fgets(buf, sizeof(buf)-1, fp) != NULL && + fstat(fileno(fp), &stat_buf) == 0) + { + char *p=strchr(buf, '\n'); + int fmt; + + if (p) *p=0; + + p=strchr(buf, ':'); + + if (p) + *p++=0; + + + if (sscanf(buf, "%d", &fmt) == 1 && + fmt == SNAPSHOTVERSION) + { + snn=malloc(sizeof(*sl)); + + if (snn) memset(snn, 0, sizeof(*snn)); + + if (snn == NULL || + (snn->filename=strdup(de->d_name)) + == NULL || + (snn->prev=strdup(p ? p:"")) + == NULL) + { + if (snn && snn->filename) + free(snn->filename); + if (snn) + free(snn); + + snn=NULL; + } + + if (snn) + { + snn->mtime=stat_buf.st_mtime; + + for (ptr= &sl; *ptr; + ptr=&(*ptr)->next) + { + if ( (*ptr)->mtime > + snn->mtime) + break; + } + + snn->next= *ptr; + *ptr=snn; + } + free(n); + n=NULL; + } + + } + fclose(fp); + } + if (n) + { + unlink(n); + free(n); + } + } + if (dirp) + closedir(dirp); + + /* Step 2: drop snapshots that are definitely obsolete */ + + for (ptr= &sl; *ptr; ) + { + if ((snn=find_next_snapshot(sl, (*ptr)->filename)) && + find_next_snapshot(sl, snn->filename)) + { + snn= *ptr; + + *ptr=snn->next; + + delete_snapshot(snn); + } + else + ptr=&(*ptr)->next; + + } + + /* If there are more than 10 snapshots, drop older snapshots */ + + cnt=0; + for (snn=sl; snn; snn=snn->next) + ++cnt; + + if (cnt > 10) + { + time_t now=time(NULL); + + while (sl && sl->mtime < now && + (now - sl->mtime) > 60 * 60 * 24 * (7 + (cnt-10)*2)) + { + snn=sl; + sl=sl->next; + delete_snapshot(snn); + --cnt; + } + } + + /* All right, put a lid on 50 snapshots */ + + while (cnt > 50) + { + snn=sl; + sl=sl->next; + delete_snapshot(snn); + --cnt; + } + + return rc; +} + +/* +** Something changed in the folder, so next time snapshot_save() was called, +** take a snapshot. +*/ + +void snapshot_needed() +{ + index_dirty=1; +} + +/* +** Save a snapshot, if the folder was changed. +*/ + +void snapshot_save() +{ + int rc; + struct maildir_tmpcreate_info createInfo; + FILE *fp; + unsigned long i; + const char *q; + + if (!index_dirty || !snapshots_enabled) + return; + + index_dirty=0; + + maildir_tmpcreate_init(&createInfo); + + createInfo.maildir=current_mailbox; + createInfo.uniq="snapshot"; + createInfo.hostname=getenv("HOSTNAME"); + createInfo.doordie=1; + + if ((rc=maildir_tmpcreate_fd(&createInfo)) < 0) + { + perror("maildir_tmpcreate_fd"); + return; + } + close(rc); + + q=strrchr(createInfo.tmpname, '/'); /* Always there */ + + free(createInfo.newname); + createInfo.newname=malloc(strlen(snapshot_dir)+strlen(q)+2); + + if (!createInfo.newname) + { + unlink(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + perror("malloc"); + return; + } + + strcat(strcat(strcpy(createInfo.newname, snapshot_dir), "/"), q); + + if ((fp=fopen(createInfo.tmpname, "w")) == NULL) + { + perror(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + return; + } + + fprintf(fp, "%d %lu %lu %lu", SNAPSHOTVERSION, + current_maildir_info.nmessages, + current_maildir_info.uidv, + current_maildir_info.nextuid); + if (snapshot_cur) + fprintf(fp, ":%s", strrchr(snapshot_cur, '/')+1); + fprintf(fp, "\n"); + + for (i=0; i<current_maildir_info.nmessages; i++) + { + struct imapscanmessageinfo *p=current_maildir_info.msgs + i; + q=strrchr(p->filename, MDIRSEP[0]); + + fprintf(fp, "%lu:%s\n", p->uid, q ? q+1:""); + } + + if (keywords()) + imapscan_saveKeywordSnapshot(fp, ¤t_maildir_info); + + if (fflush(fp) < 0 || ferror(fp) < 0) + { + fclose(fp); + perror(createInfo.tmpname); + unlink(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + return; + } + fclose(fp); + if (rename(createInfo.tmpname, createInfo.newname) < 0) + { + perror(createInfo.tmpname); + unlink(createInfo.tmpname); + maildir_tmpcreate_free(&createInfo); + return; + } + if (snapshot_last) + { + unlink(snapshot_last); /* Obsolete snapshot */ + free(snapshot_last); + } + + snapshot_last=snapshot_cur; + snapshot_cur=createInfo.newname; + createInfo.newname=NULL; + maildir_tmpcreate_free(&createInfo); + + writes("* SNAPSHOT "); + smapword(strrchr(snapshot_cur, '/')+1); + writes("\n"); +} diff --git a/imap/smaptestsuite b/imap/smaptestsuite new file mode 100644 index 0000000..77e012a --- /dev/null +++ b/imap/smaptestsuite @@ -0,0 +1,663 @@ +SED='s/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g;s/SNAPSHOT "[^"]*"/SNAPSHOT -SNAPSHOT-/g;s/SNAPSHOTEXISTS "[^"]*"/SNAPSHOTEXISTS -SNAPSHOT-/g;s:^-ERR .*:-ERR --error--:' + +rm -rf confmdtest +../maildir/maildirmake confmdtest +i=0; +while test "$i" -lt 200 +do + ../maildir/deliverquota confmdtest <<EOF +Subject: test message + +test message +EOF + i=`expr $i + 1` +done + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +OPEN INBOX +STORE 1-10 20-80 90-130 +FLAGS=DELETED +EXPUNGE +LOGOUT +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest +i=0; +while test "$i" -lt 200 +do + ../maildir/deliverquota confmdtest <<EOF +Subject: test message + +test message +EOF + i=`expr $i + 1` +done + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +OPEN INBOX +EXPUNGE 1-10 20-80 90-130 +LOGOUT +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest +../maildir/deliverquota confmdtest <<EOF +Subject: Test MIME message +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary=aaa + +--aaa +Content-Type: multipart/alternative; boundary=bbb + +--bbb +Content-Type: text/plain; charset=iso-8859-1 +Content-Disposition: inline + +text/plain content + +--bbb +Content-Type: text/html; charset=iso-8859-1 +Content-Disposition: inline + +text/html content + +--bbb-- + +--aaa +Content-Type: message/rfc822 + +Mime-Version: 1.0 +Subject: Attached message +Content-Type: text/plain; charset=utf-8 +Content-Transfer-Encoding: 7bit + +Attached message + +--aaa-- +EOF + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +OPEN INBOX +FETCH 1 CONTENTS.PEEK=MIME(Mime-Version,Content-Type,Content-Disposition) +FETCH 1 CONTENTS=MIME(Mime-Version,Content-Type,Content-Disposition) +FETCH 1 CONTENTS=HEADERS(Mime-Version,Content-Type) +FETCH 1 CONTENTS=HEADERS +FETCH 1 CONTENTS=HEADERS[1.1] +FETCH 1 CONTENTS=HEADERS[1.1.1] +FETCH 1 CONTENTS=HEADERS[1.1.2] +FETCH 1 CONTENTS=HEADERS[1.2] +FETCH 1 CONTENTS=HEADERS[1.2.0] +FETCH 1 CONTENTS=HEADERS[A] +FETCH 1 CONTENTS=HEADERS[2] +FETCH 1 CONTENTS=HEADERS[1.1.3] +FETCH 1 CONTENTS=HEADERS[1.2.1] +FETCH 1 CONTENTS=BODY +FETCH 1 CONTENTS=BODY[1.2.0] +LOGOUT +EOF + +rm -rf confmdtest confmdtest2 +../maildir/maildirmake confmdtest +../maildir/maildirmake confmdtest2 +../maildir/maildirmake -f folder1 confmdtest2 +../maildir/maildirmake --add test=`pwd`/confmdtest2 confmdtest +../maildir/maildirmake -q10C confmdtest +../maildir/deliverquota confmdtest <<EOF +Subject: testing body.decoded +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary=zzz + +--zzz +Content-Type: text/plain +Content-Transfer-Encoding: 8bit + +This is a text/plain 8bit section + +--zzz +Content-Type: text/plain +Content-Transfer-Encoding: quoted-printable + +This is =61 text/plain = +quoted-printable section + +--zzz +Content-Type: text/plain +Content-Transfer-Encoding: base64 + +YWNsb2NhbC5tNAkJICAgIGltYXBkLm8JCSAgICAgbXNnZW52ZWxvcGUubwphdXRoZW50aWNhdGVf +YXV0aC5jCSAgICBpbWFwZC5wYW0JCSAgICAgbXlzaWduYWwuYwphdXRoZW50aWNhdGVfYXV0aC5j +Ln4xLjYufiAgaW1hcGQuc2dtbAkJICAgICBteXNpZ25hbC5oCmF1dGhlbnRpY2F0ZV9hdXRoLm8J +ICAgIGltYXBkLXNzbC5kaXN0CSAgICAgbXlzaWduYWwubwphdXRoZW50aWNhdGVfc3R1Yi5jCSAg +ICBpbWFwZC1zc2wuZGlzdC5pbgkgICAgIG91dGJveC5jCmF1dG9tNHRlLTIuNTMuY2FjaGUJICAg +IGltYXBsb2dpbgkJICAgICBvdXRib3guaApCVUdTCQkJICAgIGltYXBsb2dpbi5jCQkgICAgIG91 +dGJveC5vCmNhcGFiaWxpdHkuYwkJICAgIGltYXBsb2dpbi5jLn4xLjE5Ln4gICAgICBwb3AzZApj +YXBhYmlsaXR5LmMufjEuNi5+CSAgICBpbWFwbG9naW4ubwkJICAgICBwb3AzZC5hdXRocGFtCmNh +cGFiaWxpdHkubwkJICAgIGltYXBzY2FuY2xpZW50LmMJICAgICBwb3AzZGNhcGEuYwpDaGFuZ2VM +b2cJCSAgICBpbWFwc2NhbmNsaWVudC5oCSAgICAgcG9wM2RjYXBhLm8KY29uZmlnLmgJCSAgICBp +bWFwc2NhbmNsaWVudC5oLn4xLjYufiAgcG9wM2QuY25mCmNvbmZpZy5oLmluCQkgICAgaW1hcHNj +YW5jbGllbnQubwkgICAgIHBvcDNkLmNuZi5pbgpjb25maWcuaC5pbn4JCSAgICBpbWFwdG9rZW4u +YwkJICAgICBwb3AzZC5kaXN0CmNvbmZpZy5sb2cJCSAgICBpbWFwdG9rZW4uYy5+MS4xMy5+ICAg +ICAgcG9wM2QuZGlzdC5pbgpjb25maWcuc3RhdHVzCQkgICAgaW1hcHRva2VuLmgJCSAgICAgcG9w +M2QucGFtCmNvbmZpZ3VyZQkJICAgIGltYXB0b2tlbi5oLn4xLjYufgkgICAgIHBvcDNkc2VydmVy +LmMKY29uZmlndXJlLmluCQkgICAgaW1hcHRva2VuLm8JCSAgICAgcG9wM2RzZXJ2ZXIubwpjb25m +aWd1cmUuaW4ufjEuNzkufgkgICAgaW1hcHdyaXRlLmMJCSAgICAgcG9wM2Qtc3NsLmRpc3QKY29u +Zm1kdGVzdAkJICAgIGltYXB3cml0ZS5oCQkgICAgIHBvcDNkLXNzbC5kaXN0LmluCmNvbmZtZHRl +c3QyCQkgICAgaW1hcHdyaXRlLm8JCSAgICAgcG9wM2xvZ2luCmNvbmZtZHRlc3Quc3Rkb3V0CSAg +ICBsaWJpbWFwZC5hCQkgICAgIHBvcDNsb2dpbi5jCmNvdXJpZXJwb3AzZC44CQkgICAgbWFpbGJv +eGxpc3QuYwkgICAgIHBvcDNsb2dpbi5vCmNvdXJpZXJwb3AzZC44LmluCSAgICBtYWlsYm94bGlz +dC5oCSAgICAgUkVBRE1FCmNvdXJpZXJwb3AzZC5odG1sCSAgICBtYWlsYm94bGlzdC5vCSAgICAg +UkVBRE1FLmh0bWwKY291cmllcnBvcDNkLmh0bWwuaW4JICAgIG1haW5sb29wLmMJCSAgICAgc2Vh +cmNoLmMKY291cmllcnBvcDNkLnNnbWwJICAgIG1haW5sb29wLmMufjEuNi5+CSAgICAgc2VhcmNo +aW5mby5jCkNWUwkJCSAgICBtYWlubG9vcC5vCQkgICAgIHNlYXJjaGluZm8uaApGQVEJCQkgICAg +TWFrZWZpbGUJCSAgICAgc2VhcmNoaW5mby5vCkZBUS5odG1sCQkgICAgTWFrZWZpbGV+CQkgICAg +IHNlYXJjaC5vCmZldGNoLmMJCQkgICAgTWFrZWZpbGUuYW0JCSAgICAgc21hcC5jCmZldGNoLmMu +fjEuMjEufgkJICAgIE1ha2VmaWxlLmFtLn4xLjMxLn4gICAgICBzbWFwLmN+CmZldGNoaW5mby5j +CQkgICAgTWFrZWZpbGUuaW4JCSAgICAgc21hcC5vCmZldGNoaW5mby5oCQkgICAgbWtpbWFwZGNl +cnQJCSAgICAgc21hcHRlc3RzdWl0ZQpmZXRjaGluZm8ubwkJICAgIG1raW1hcGRjZXJ0LjgJICAg +ICBzbWFwdGVzdHN1aXRlfgpmZXRjaC5vCQkJICAgIG1raW1hcGRjZXJ0LjguaW4JICAgICBzbWFw +dGVzdHN1aXRlLnR4dApodG1sMm1hbi5wbC5pbgkJICAgIG1raW1hcGRjZXJ0Lmh0bWwJICAgICBz +dGFtcC1oMQppbWFwZAkJCSAgICBta2ltYXBkY2VydC5odG1sLmluICAgICAgc3RvcmVpbmZvLmMK +aW1hcGQuOAkJCSAgICBta2ltYXBkY2VydC5pbgkgICAgIHN0b3JlaW5mby5oCmltYXBkLjguaW4J +CSAgICBta2ltYXBkY2VydC5zZ21sCSAgICAgc3RvcmVpbmZvLm8KaW1hcGQuYXV0aHBhbQkJICAg +IG1rcG9wM2RjZXJ0CQkgICAgIHN5c3RlbS1hdXRoLmF1dGhwYW0KaW1hcGQuYwkJCSAgICBta3Bv +cDNkY2VydC44CSAgICAgdAppbWFwZC5jLn4xLjk1Ln4JCSAgICBta3BvcDNkY2VydC44LmluCSAg +ICAgdGVzdHN1aXRlCmltYXBkLmNuZgkJICAgIG1rcG9wM2RjZXJ0Lmh0bWwJICAgICB0ZXN0c3Vp +dGVmaXgucGwKaW1hcGQuY25mLmluCQkgICAgbWtwb3AzZGNlcnQuaHRtbC5pbiAgICAgIHRlc3Rz +dWl0ZWZpeC5wbC5pbgppbWFwZC5kaXN0CQkgICAgbWtwb3AzZGNlcnQuaW4JICAgICB0ZXN0c3Vp +dGUudHh0CmltYXBkLmRpc3QuaW4JCSAgICBta3BvcDNkY2VydC5zZ21sCSAgICAgdGhyZWFkLmMK +aW1hcGQuaAkJCSAgICBtc2dib2R5c3RydWN0dXJlLmMJICAgICB0aHJlYWQuaAppbWFwZC5odG1s +CQkgICAgbXNnYm9keXN0cnVjdHVyZS5vCSAgICAgdGhyZWFkLm8KaW1hcGQuaHRtbC5pbgkJICAg +IG1zZ2VudmVsb3BlLmMK + +--zzz-- +EOF + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +open INBOX +FETCH 1 CONTENTS=BODY[1.1] +FETCH 1 CONTENTS=BODY.DECODED[1.1] +FETCH 1 CONTENTS=BODY[1.2] +FETCH 1 CONTENTS=BODY.DECODED[1.2] +FETCH 1 CONTENTS=BODY[1.3] +FETCH 1 CONTENTS=BODY.DECODED[1.3] +ADD FOLDER INBOX "" FLAGS=SEEN "INTERNALDATE=03 Aug 2003 03:00:00 -0500" +ADD {.100} +Subject: test add +Mime-Version: 1.0 + +This is a test of the +.add command. +. +NOOP +COPY 1-2 "" INBOX +NOOP +COPY 1-4 "" INBOX +NOOP +COPY 1-4 "" INBOX +COPY 1-2 "" INBOX +NOOP +ADD FOLDER INBOX "" FLAGS=SEEN "INTERNALDATE=03 Aug 2003 03:00:00 -0500" +ADD {.100} +Subject: test add +Mime-Version: 1.0 + +This is a test of the +.add command. +. +open INBOX +fetch 10 CONTENTS=HEADERS(Mime-Version) +LOGOUT +EOF + +rm -f confmdtest/maildirsize +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +open INBOX +COPY 1-10 "" INBOX +NOOP +COPY 1-20 "" INBOX +NOOP +COPY 1-40 "" INBOX +NOOP +COPY 1-80 "" INBOX +NOOP +COPY 1-160 "" INBOX +NOOP +COPY 1-320 "" INBOX +NOOP +LOGOUT +EOF + +( +echo "open INBOX" +n=0 +while test $n -lt 64 +do + m=$n + if test "$m" = "" + then + m="" + fi + + echo "STORE ${m}3-${m}5 ${m}7 ${m}9 +FLAGS=DELETED" + n=`expr $n + 1` +done +echo "LOGOUT" +) | env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd | sed "$SED" + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +open INBOX +SEARCH UNMARKED NOT DELETED +SEARCH MARKED NOT DELETED +SEARCH UNMARKED DELETED +STORE 1-10 +FLAGS=DRAFT +STORE 15-25 30 +FLAGS=SEEN +SEARCH UNMARKED DRAFT +SEARCH UNMARKED SEEN +EXPUNGE 1-640 +ADD FOLDER INBOX "" FLAGS=SEEN "INTERNALDATE=03 Aug 2003 03:00:00 -0500" +ADD {.100} +Subject: iso-8859-1 encoding test: =?iso-8859-1?Q?S=E2m?= +Date: 03 Aug 2003 03:00:00 -0500 +Mime-Version: 1.0 + +This is a test of the +.add command. +. +NOOP +ADD FOLDER INBOX "" FLAGS=SEEN "INTERNALDATE=08 Aug 2003 03:00:00 -0500" +ADD {.100} +Subject: iso-8859-1 encoding test: =?iso-8859-1?Q?S=E2m?= +Date: 05 Aug 2003 05:00:00 -0500 +Content-Type: text/plain; charset=iso-8859-1 +Content-Transfer-Encoding: quoted-printable +Mime-Version: 1.0 + +S=E2m This is a ttest of the +.add command. +. +NOOP +SEARCH UNMARKED SUBJECT "sâm" +SEARCH UNMARKED HEADER DATE "05 AUG" +SEARCH UNMARKED TEXT "ttest" +SEARCH UNMARKED BODY "sâm" +SEARCH UNMARKED TEXT "sâm" +SEARCH UNMARKED ON "08-Aug-2003" +SEARCH UNMARKED ON "03-Aug-2003" +SEARCH UNMARKED SENTON "05-Aug-2003" +SEARCH UNMARKED SMALLER "200" +SEARCH UNMARKED NOT SMALLER "200" +SEARCH UNMARKED INVALID +LOGOUT +EOF + +rm -rf confmdtest confmdtest2 +../maildir/maildirmake confmdtest +../maildir/maildirmake confmdtest2 +../maildir/maildirmake -f folder1 confmdtest2 +../maildir/maildirmake --add test=`pwd`/confmdtest2 confmdtest +../maildir/maildirmake -q20C confmdtest +../maildir/deliverquota confmdtest <<EOF +Subject: testing body.decoded +Mime-Version: 1.0 +Content-Type: text/plain + +Test +EOF + +env IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +open INBOX +COPY 1 "" INBOX +NOOP +open INBOX +CREATE INBOX Trash +copy 1 "" INBOX Trash +CREATE INBOX a +MOVE 2 "" INBOX a +LOGOUT +EOF +cat confmdtest/maildirsize +env IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +open INBOX a +LOGOUT +EOF +cat confmdtest/maildirsize + +env IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +sopen "" INBOX a +NOOP +NOOP +STORE 1 FLAGS=REPLIED +NOOP +NOOP +STORE 1 FLAGS=REPLIED +NOOP +NOOP +STORE 1 FLAGS=DELETED +EXPUNGE +EXPUNGE +NOOP +NOOP +EXPUNGE 1 +NOOP +LOGOUT +EOF +echo `ls confmdtest/.a/courierimapsnapshots | wc -l` + +env IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +delete INBOX a +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +sopen "" INBOX +NOOP +CLOSE +LOGOUT +EOF +echo `ls confmdtest/courierimapsnapshots | wc -l` +touch -t 199901010000 `ls confmdtest/courierimapsnapshots/* | sed -n 1p` + +env IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +sopen "" INBOX +CLOSE +LOGOUT +EOF +echo `ls confmdtest/courierimapsnapshots | wc -l` + +rm -rf confmdtest +../maildir/maildirmake confmdtest + + ../maildir/deliverquota confmdtest <<EOF +Received: by localhost +Subject: test message +From: nobody@localhost +Cc: nobody@localhost +Mime-Version: 1.0 +Content-Type: multipart/mixed; + boundary=abc + + +--abc +Content-Type: text/plain; charset=iso-8859-1 +X-Comment: foo + +Mary had a little lamb, its fleece was white as snow. +--abc-- + +test message +EOF + +env IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +open INBOX +FETCH 1 CONTENTS=HEADERS +FETCH 1 CONTENTS=RAWHEADERS +FETCH 1 CONTENTS=MIME +FETCH 1 CONTENTS=MIME(Content-Type) +FETCH 1 CONTENTS=HEADERS(Content-Type) +FETCH 1 CONTENTS=HEADERS(:ENVELOPE) +FETCH 1 CONTENTS=HEADERS(:ENVELOPE,Received) +FETCH 1 CONTENTS=HEADERS(Received,:ENVELOPE) +FETCH 1 CONTENTS=HEADERS(:MIME) +FETCH 1 CONTENTS=MIME(:MIME) +LOGOUT +EOF + +env IMAP_KEYWORDS=1 IMAP_BROKENUIDV=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF >testsuite.tmp +open INBOX +CREATE INBOX a +COPY 1 "" INBOX a +OPEN INBOX a +STORE 1 +FLAGS=DELETED +KEYWORDS=-Label1 +ADD FOLDER INBOX a +ADD FLAGS=SEEN +ADD KEYWORDS=-Label2 +ADD {.} +Subject: test + +test + +test +. +EXPUNGE +FETCH 1 FLAGS KEYWORDS +COPY 1 "" INBOX +OPEN INBOX +FETCH 1-2 FLAGS KEYWORDS +MOVE 2 "" INBOX a +SOPEN "" INBOX a +FETCH 1-2 FLAGS KEYWORDS +NOOP +EXPUNGE 1 +STORE 1 KEYWORDS=-Label3 +LOGOUT +EOF + +SNAPSHOT=`fgrep '* SNAPSHOT ' testsuite.tmp | cut -c12-` +sed "$SED" <testsuite.tmp +rm -f testsuite.tmp + +env IMAP_BROKENUIDV=1 IMAP_KEYWORDS=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +SOPEN $SNAPSHOT INBOX a +COPY 1 "" INBOX a +NOOP +COPY 1-2 "" INBOX a +NOOP +FETCH 1-4 FLAGS KEYWORDS +STORE 3-4 -KEYWORDS=-Label2 +STORE 3-4 -KEYWORDS=-Label3 +SEARCH UNMARKED SEEN +SEARCH UNMARKED KEYWORD -Label2 +SEARCH UNMARKED KEYWORD -Label3 +SEARCH UNMARKED NOT KEYWORD -Label3 +LOGOUT +EOF + +env IMAP_BROKENUIDV=1 IMAP_KEYWORDS=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +OPEN INBOX +STORE 1 KEYWORDS=Junk*Junk +LOGOUT +EOF + +env IMAP_BROKENUIDV=1 IMAP_KEYWORDS=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/' +a1 SELECT INBOX +a2 FETCH 1 FLAGS +a3 STORE 1 FLAGS (\Seen Abra,Cadabra) +a3 LOGOUT +EOF + +env IMAP_BROKENUIDV=1 IMAP_KEYWORDS=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +OPEN INBOX +FETCH 1 FLAGS KEYWORDS +COPY 1 "" INBOX +NOOP +STORE 2 +FLAGS=MARKED +SEARCH UNMARKED SEEN +SEARCH MARKED SEEN +SEARCH ALL SEEN +LOGOUT +EOF + +rm -rf confmdtest* +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake confmdtest2 || exit 1 +../maildir/maildirmake confmdtest3 || exit 1 +mkdir confmdtest4 || exit 1 +cat >confmdtest4/index <<EOF || exit 1 +confmdtest 1 1 `pwd` confmdtest +a * indexa +b * indexb +EOF +echo "confmdtest2 1 1 `pwd` confmdtest2" >confmdtest4/indexa || exit 1 +echo "aashared 1 1 `pwd` confmdtest5" >>confmdtest4/indexa || exit 1 +echo "confmdtest3 1 1 `pwd` confmdtest3" >confmdtest4/indexb || exit 1 + +../maildir/maildirmake -f x confmdtest2 +../maildir/maildiracl -set confmdtest2 INBOX.x user=confmdtest aceilrstwx +../maildir/maildiracl -set confmdtest3 INBOX user=confmdtest aceilrstwx + +IMAP_SHAREDINDEXFILE=`pwd`/confmdtest4/index +export IMAP_SHAREDINDEXFILE +env AUTHENTICATED=confmdtest IMAP_BROKENUIDV=1 IMAP_KEYWORDS=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +LIST public +LIST public a +LIST public b +LIST public a confmdtest2 +GETACL INBOX +SETACL INBOX "" owner -stwx +SETACL INBOX "" owner aceilrstw +SETACL INBOX "" owner +x +SETACL INBOX "" owner -l +SETACL INBOX "" -owner a +SETACL INBOX "" user=testuser1 ace +SETACL INBOX "" user=testuser1 +x +SETACL INBOX "" user=testuser1 -x +SETACL INBOX "" user=testuser1 -ace +SETACL INBOX "" user=testuser1 ace +DELETEACL INBOX "" user=testuser2 +DELETEACL INBOX "" user=testuser1 +DELETEACL INBOX "" owner +ACL INBOX +GETACL public b confmdtest3 +ACL public b confmdtest3 +GETACL public a confmdtest2 x +ACL public a confmdtest2 x +ACL public +ACL public a +OPEN INBOX +ADD FOLDER INBOX "" FLAGS=SEEN,DELETED,MARKED KEYWORDS=keyword1 +ADD {.} +Subject: test message 1 + +test message 1 +. +NOOP +FETCH 1 FLAGS KEYWORDS +SETACL INBOX "" owner -w +ADD FOLDER INBOX "" FLAGS=SEEN,DELETED,MARKED KEYWORDS=keyword1 +ADD {.} +Subject: test message 1 + +test message 1 +. +NOOP +FETCH 2 FLAGS KEYWORDS +SETACL INBOX "" owner +w owner -s +ADD FOLDER INBOX "" FLAGS=SEEN,DELETED,MARKED KEYWORDS=keyword1 +ADD {.} +Subject: test message 1 + +test message 1 +. +NOOP +FETCH 3 FLAGS KEYWORDS +SETACL INBOX "" owner +s owner -t +ADD FOLDER INBOX "" FLAGS=SEEN,DELETED,MARKED KEYWORDS=keyword1 +ADD {.} +Subject: test message 1 + +test message 1 +. +NOOP +FETCH 4 FLAGS KEYWORDS +SETACL INBOX "" owner +t owner -i +ADD FOLDER INBOX "" +SETACL INBOX "" owner +i +SETACL public b confmdtest3 "" user=confmdtest -l +STATUS CHEAP public b confmdtest3 +SETACL public b confmdtest3 "" user=confmdtest +l +STATUS CHEAP public b confmdtest3 +DELETE public b confmdtest3 +CREATE INBOX c +SETACL INBOX c "" owner -x +DELETE INBOX c +SETACL INBOX c "" owner +x +DELETE INBOX c +SETACL INBOX "" -owner r +OPEN INBOX +DELETEACL INBOX "" -owner +OPEN INBOX +SETACL INBOX "" -owner i +COPY 1-4 "" INBOX +DELETEACL INBOX "" -owner +COPY 1-4 "" INBOX +CLOSE +CREATE INBOX a +MKDIR INBOX b +SETACL INBOX b "" owner -c +RENAME INBOX a "" INBOX b a +SETACL INBOX b "" owner +c +RENAME INBOX a "" INBOX b a +SETACL INBOX b "" owner -x +RENAME INBOX b "" INBOX a +SETACL INBOX b "" owner +x +RENAME INBOX b "" INBOX a +OPEN INBOX +FETCH 1 FLAGS KEYWORDS +SETACL INBOX "" owner -w +STORE 1 FLAGS=SEEN KEYWORDS= +SETACL INBOX "" owner +w owner -t +STORE 1 FLAGS=MARKED,SEEN,DELETED KEYWORDS=keyword2 +SETACL INBOX "" owner +t owner -s +STORE 1 -FLAGS=SEEN,MARKED +SETACL INBOX "" owner +s owner -e +STORE 1 FLAGS=DELETED +EXPUNGE +SETACL INBOX "" owner +e +EXPUNGE +SETACL INBOX "" owner -t +EXPUNGE 1 +SETACL INBOX "" owner +t +EXPUNGE 1 +LOGOUT +EOF + +rm -rf confmdtest* +../maildir/maildirmake confmdtest || exit 1 + +n=0 +while test $n -lt 256 +do + filename=`expr $n + 1000` + filename="$filename,test:2,S" + if test `expr $n % 2` = 1 + then + filename="$filename""T" + fi + cat >confmdtest/cur/$filename <<EOF +Subject: test message + +test message +EOF + n=`expr $n + 1` +done +env AUTHENTICATED=confmdtest IMAP_BROKENUIDV=1 IMAP_KEYWORDS=1 MOVE_EXPUNGE_TO_TRASH=1 MAILDIR=confmdtest PROTOCOL=SMAP1 ./imapd <<EOF | sed "$SED" +OPEN INBOX +EXPUNGE +LOGOUT +EOF diff --git a/imap/smaptestsuite.txt b/imap/smaptestsuite.txt new file mode 100644 index 0000000..b4bc2c0 --- /dev/null +++ b/imap/smaptestsuite.txt @@ -0,0 +1,2063 @@ +000000 +OK SMAP1 LOGIN Ok. +000001 * EXISTS 200 +000002 +OK Folder opened +000003 * FETCH 1 FLAGS=DELETED +000004 * FETCH 2 FLAGS=DELETED +000005 * FETCH 3 FLAGS=DELETED +000006 * FETCH 4 FLAGS=DELETED +000007 * FETCH 5 FLAGS=DELETED +000008 * FETCH 6 FLAGS=DELETED +000009 * FETCH 7 FLAGS=DELETED +000010 * FETCH 8 FLAGS=DELETED +000011 * FETCH 9 FLAGS=DELETED +000012 * FETCH 10 FLAGS=DELETED +000013 * FETCH 20 FLAGS=DELETED +000014 * FETCH 21 FLAGS=DELETED +000015 * FETCH 22 FLAGS=DELETED +000016 * FETCH 23 FLAGS=DELETED +000017 * FETCH 24 FLAGS=DELETED +000018 * FETCH 25 FLAGS=DELETED +000019 * FETCH 26 FLAGS=DELETED +000020 * FETCH 27 FLAGS=DELETED +000021 * FETCH 28 FLAGS=DELETED +000022 * FETCH 29 FLAGS=DELETED +000023 * FETCH 30 FLAGS=DELETED +000024 * FETCH 31 FLAGS=DELETED +000025 * FETCH 32 FLAGS=DELETED +000026 * FETCH 33 FLAGS=DELETED +000027 * FETCH 34 FLAGS=DELETED +000028 * FETCH 35 FLAGS=DELETED +000029 * FETCH 36 FLAGS=DELETED +000030 * FETCH 37 FLAGS=DELETED +000031 * FETCH 38 FLAGS=DELETED +000032 * FETCH 39 FLAGS=DELETED +000033 * FETCH 40 FLAGS=DELETED +000034 * FETCH 41 FLAGS=DELETED +000035 * FETCH 42 FLAGS=DELETED +000036 * FETCH 43 FLAGS=DELETED +000037 * FETCH 44 FLAGS=DELETED +000038 * FETCH 45 FLAGS=DELETED +000039 * FETCH 46 FLAGS=DELETED +000040 * FETCH 47 FLAGS=DELETED +000041 * FETCH 48 FLAGS=DELETED +000042 * FETCH 49 FLAGS=DELETED +000043 * FETCH 50 FLAGS=DELETED +000044 * FETCH 51 FLAGS=DELETED +000045 * FETCH 52 FLAGS=DELETED +000046 * FETCH 53 FLAGS=DELETED +000047 * FETCH 54 FLAGS=DELETED +000048 * FETCH 55 FLAGS=DELETED +000049 * FETCH 56 FLAGS=DELETED +000050 * FETCH 57 FLAGS=DELETED +000051 * FETCH 58 FLAGS=DELETED +000052 * FETCH 59 FLAGS=DELETED +000053 * FETCH 60 FLAGS=DELETED +000054 * FETCH 61 FLAGS=DELETED +000055 * FETCH 62 FLAGS=DELETED +000056 * FETCH 63 FLAGS=DELETED +000057 * FETCH 64 FLAGS=DELETED +000058 * FETCH 65 FLAGS=DELETED +000059 * FETCH 66 FLAGS=DELETED +000060 * FETCH 67 FLAGS=DELETED +000061 * FETCH 68 FLAGS=DELETED +000062 * FETCH 69 FLAGS=DELETED +000063 * FETCH 70 FLAGS=DELETED +000064 * FETCH 71 FLAGS=DELETED +000065 * FETCH 72 FLAGS=DELETED +000066 * FETCH 73 FLAGS=DELETED +000067 * FETCH 74 FLAGS=DELETED +000068 * FETCH 75 FLAGS=DELETED +000069 * FETCH 76 FLAGS=DELETED +000070 * FETCH 77 FLAGS=DELETED +000071 * FETCH 78 FLAGS=DELETED +000072 * FETCH 79 FLAGS=DELETED +000073 * FETCH 80 FLAGS=DELETED +000074 * FETCH 90 FLAGS=DELETED +000075 * FETCH 91 FLAGS=DELETED +000076 * FETCH 92 FLAGS=DELETED +000077 * FETCH 93 FLAGS=DELETED +000078 * FETCH 94 FLAGS=DELETED +000079 * FETCH 95 FLAGS=DELETED +000080 * FETCH 96 FLAGS=DELETED +000081 * FETCH 97 FLAGS=DELETED +000082 * FETCH 98 FLAGS=DELETED +000083 * FETCH 99 FLAGS=DELETED +000084 * FETCH 100 FLAGS=DELETED +000085 * FETCH 101 FLAGS=DELETED +000086 * FETCH 102 FLAGS=DELETED +000087 * FETCH 103 FLAGS=DELETED +000088 * FETCH 104 FLAGS=DELETED +000089 * FETCH 105 FLAGS=DELETED +000090 * FETCH 106 FLAGS=DELETED +000091 * FETCH 107 FLAGS=DELETED +000092 * FETCH 108 FLAGS=DELETED +000093 * FETCH 109 FLAGS=DELETED +000094 * FETCH 110 FLAGS=DELETED +000095 * FETCH 111 FLAGS=DELETED +000096 * FETCH 112 FLAGS=DELETED +000097 * FETCH 113 FLAGS=DELETED +000098 * FETCH 114 FLAGS=DELETED +000099 * FETCH 115 FLAGS=DELETED +000100 * FETCH 116 FLAGS=DELETED +000101 * FETCH 117 FLAGS=DELETED +000102 * FETCH 118 FLAGS=DELETED +000103 * FETCH 119 FLAGS=DELETED +000104 * FETCH 120 FLAGS=DELETED +000105 * FETCH 121 FLAGS=DELETED +000106 * FETCH 122 FLAGS=DELETED +000107 * FETCH 123 FLAGS=DELETED +000108 * FETCH 124 FLAGS=DELETED +000109 * FETCH 125 FLAGS=DELETED +000110 * FETCH 126 FLAGS=DELETED +000111 * FETCH 127 FLAGS=DELETED +000112 * FETCH 128 FLAGS=DELETED +000113 * FETCH 129 FLAGS=DELETED +000114 * FETCH 130 FLAGS=DELETED +000115 +OK Folder status updated +000116 * EXPUNGE 1-10 20-80 90-119 +000117 * EXPUNGE 19-29 +000118 +OK Folder updated +000119 * BYE Courier-SMAP server shutting down +000120 +OK LOGOUT completed +000121 +OK SMAP1 LOGIN Ok. +000122 * EXISTS 200 +000123 +OK Folder opened +000124 * EXPUNGE 1-10 20-80 90-119 +000125 * EXPUNGE 19-29 +000126 +OK Folder updated +000127 * BYE Courier-SMAP server shutting down +000128 +OK LOGOUT completed +000129 +OK SMAP1 LOGIN Ok. +000130 * EXISTS 1 +000131 +OK Folder opened +000132 {.90} FETCH 1 LINES=28 SIZE=443 "MIME.ID=" +000133 Mime-Version: 1.0 +000134 Content-Type: multipart/mixed; boundary=aaa +000135 . +000136 {.51} FETCH 1 LINES=13 SIZE=206 "MIME.ID=1.1" "MIME.PARENT=" +000137 Content-Type: multipart/alternative; boundary=bbb +000138 . +000139 {.74} FETCH 1 LINES=1 SIZE=19 "MIME.ID=1.1.1" "MIME.PARENT=1.1" +000140 Content-Type: text/plain; charset=iso-8859-1 +000141 Content-Disposition: inline +000142 . +000143 {.73} FETCH 1 LINES=1 SIZE=18 "MIME.ID=1.1.2" "MIME.PARENT=1.1" +000144 Content-Type: text/html; charset=iso-8859-1 +000145 Content-Disposition: inline +000146 . +000147 {.30} FETCH 1 LINES=6 SIZE=135 "MIME.ID=1.2" "MIME.PARENT=" +000148 Content-Type: message/rfc822 +000149 . +000150 {.117} FETCH 1 LINES=1 SIZE=18 "MIME.ID=1.2.0" "MIME.PARENT=1.2" +000151 Mime-Version: 1.0 +000152 Content-Type: text/plain; charset=utf-8 +000153 . +000154 +OK Message retrieved. +000155 {.90} FETCH 1 LINES=28 SIZE=443 "MIME.ID=" +000156 Mime-Version: 1.0 +000157 Content-Type: multipart/mixed; boundary=aaa +000158 . +000159 {.51} FETCH 1 LINES=13 SIZE=206 "MIME.ID=1.1" "MIME.PARENT=" +000160 Content-Type: multipart/alternative; boundary=bbb +000161 . +000162 {.74} FETCH 1 LINES=1 SIZE=19 "MIME.ID=1.1.1" "MIME.PARENT=1.1" +000163 Content-Type: text/plain; charset=iso-8859-1 +000164 Content-Disposition: inline +000165 . +000166 {.73} FETCH 1 LINES=1 SIZE=18 "MIME.ID=1.1.2" "MIME.PARENT=1.1" +000167 Content-Type: text/html; charset=iso-8859-1 +000168 Content-Disposition: inline +000169 . +000170 {.30} FETCH 1 LINES=6 SIZE=135 "MIME.ID=1.2" "MIME.PARENT=" +000171 Content-Type: message/rfc822 +000172 . +000173 {.117} FETCH 1 LINES=1 SIZE=18 "MIME.ID=1.2.0" "MIME.PARENT=1.2" +000174 Mime-Version: 1.0 +000175 Content-Type: text/plain; charset=utf-8 +000176 . +000177 * FETCH 1 FLAGS=SEEN +000178 +OK Message retrieved. +000179 {.0} FETCH 1 HEADERS +000180 Mime-Version: 1.0 +000181 Content-Type: multipart/mixed; boundary=aaa +000182 . +000183 +OK Message retrieved. +000184 {.0} FETCH 1 HEADERS +000185 Subject: Test MIME message +000186 Mime-Version: 1.0 +000187 Content-Type: multipart/mixed; boundary=aaa +000188 . +000189 +OK Message retrieved. +000190 {.51} FETCH 1 HEADERS +000191 Content-Type: multipart/alternative; boundary=bbb +000192 . +000193 +OK Message retrieved. +000194 {.74} FETCH 1 HEADERS +000195 Content-Type: text/plain; charset=iso-8859-1 +000196 Content-Disposition: inline +000197 . +000198 +OK Message retrieved. +000199 {.73} FETCH 1 HEADERS +000200 Content-Type: text/html; charset=iso-8859-1 +000201 Content-Disposition: inline +000202 . +000203 +OK Message retrieved. +000204 {.30} FETCH 1 HEADERS +000205 Content-Type: message/rfc822 +000206 . +000207 +OK Message retrieved. +000208 {.117} FETCH 1 HEADERS +000209 Mime-Version: 1.0 +000210 Subject: Attached message +000211 Content-Type: text/plain; charset=utf-8 +000212 Content-Transfer-Encoding: 7bit +000213 . +000214 +OK Message retrieved. +000215 -ERR --error-- +000216 -ERR --error-- +000217 -ERR --error-- +000218 -ERR --error-- +000219 {443/443} FETCH 1 CONTENTS +000220 --aaa +000221 Content-Type: multipart/alternative; boundary=bbb +000222 +000223 --bbb +000224 Content-Type: text/plain; charset=iso-8859-1 +000225 Content-Disposition: inline +000226 +000227 text/plain content +000228 +000229 --bbb +000230 Content-Type: text/html; charset=iso-8859-1 +000231 Content-Disposition: inline +000232 +000233 text/html content +000234 +000235 --bbb-- +000236 +000237 --aaa +000238 Content-Type: message/rfc822 +000239 +000240 Mime-Version: 1.0 +000241 Subject: Attached message +000242 Content-Type: text/plain; charset=utf-8 +000243 Content-Transfer-Encoding: 7bit +000244 +000245 Attached message +000246 +000247 --aaa-- +000248 +000249 +OK Message retrieved. +000250 {18/18} FETCH 1 CONTENTS +000251 Attached message +000252 +000253 +000254 +OK Message retrieved. +000255 * BYE Courier-SMAP server shutting down +000256 +OK LOGOUT completed +000257 +OK SMAP1 LOGIN Ok. +000258 * EXISTS 1 +000259 +OK Folder opened +000260 {34/34} FETCH 1 CONTENTS +000261 This is a text/plain 8bit section +000262 +000263 * FETCH 1 FLAGS=SEEN +000264 +OK Message retrieved. +000265 {34/34} FETCH 1 CONTENTS +000266 This is a text/plain 8bit section +000267 +000268 +OK Message retrieved. +000269 {50/50} FETCH 1 CONTENTS +000270 This is =61 text/plain = +000271 quoted-printable section +000272 +000273 +OK Message retrieved. +000274 {46/50} FETCH 1 CONTENTS +000275 This is a text/plain quoted-printable section +000276 +000277 +OK Message retrieved. +000278 {3332/3332} FETCH 1 CONTENTS +000279 YWNsb2NhbC5tNAkJICAgIGltYXBkLm8JCSAgICAgbXNnZW52ZWxvcGUubwphdXRoZW50aWNhdGVf +000280 YXV0aC5jCSAgICBpbWFwZC5wYW0JCSAgICAgbXlzaWduYWwuYwphdXRoZW50aWNhdGVfYXV0aC5j +000281 Ln4xLjYufiAgaW1hcGQuc2dtbAkJICAgICBteXNpZ25hbC5oCmF1dGhlbnRpY2F0ZV9hdXRoLm8J +000282 ICAgIGltYXBkLXNzbC5kaXN0CSAgICAgbXlzaWduYWwubwphdXRoZW50aWNhdGVfc3R1Yi5jCSAg +000283 ICBpbWFwZC1zc2wuZGlzdC5pbgkgICAgIG91dGJveC5jCmF1dG9tNHRlLTIuNTMuY2FjaGUJICAg +000284 IGltYXBsb2dpbgkJICAgICBvdXRib3guaApCVUdTCQkJICAgIGltYXBsb2dpbi5jCQkgICAgIG91 +000285 dGJveC5vCmNhcGFiaWxpdHkuYwkJICAgIGltYXBsb2dpbi5jLn4xLjE5Ln4gICAgICBwb3AzZApj +000286 YXBhYmlsaXR5LmMufjEuNi5+CSAgICBpbWFwbG9naW4ubwkJICAgICBwb3AzZC5hdXRocGFtCmNh +000287 cGFiaWxpdHkubwkJICAgIGltYXBzY2FuY2xpZW50LmMJICAgICBwb3AzZGNhcGEuYwpDaGFuZ2VM +000288 b2cJCSAgICBpbWFwc2NhbmNsaWVudC5oCSAgICAgcG9wM2RjYXBhLm8KY29uZmlnLmgJCSAgICBp +000289 bWFwc2NhbmNsaWVudC5oLn4xLjYufiAgcG9wM2QuY25mCmNvbmZpZy5oLmluCQkgICAgaW1hcHNj +000290 YW5jbGllbnQubwkgICAgIHBvcDNkLmNuZi5pbgpjb25maWcuaC5pbn4JCSAgICBpbWFwdG9rZW4u +000291 YwkJICAgICBwb3AzZC5kaXN0CmNvbmZpZy5sb2cJCSAgICBpbWFwdG9rZW4uYy5+MS4xMy5+ICAg +000292 ICAgcG9wM2QuZGlzdC5pbgpjb25maWcuc3RhdHVzCQkgICAgaW1hcHRva2VuLmgJCSAgICAgcG9w +000293 M2QucGFtCmNvbmZpZ3VyZQkJICAgIGltYXB0b2tlbi5oLn4xLjYufgkgICAgIHBvcDNkc2VydmVy +000294 LmMKY29uZmlndXJlLmluCQkgICAgaW1hcHRva2VuLm8JCSAgICAgcG9wM2RzZXJ2ZXIubwpjb25m +000295 aWd1cmUuaW4ufjEuNzkufgkgICAgaW1hcHdyaXRlLmMJCSAgICAgcG9wM2Qtc3NsLmRpc3QKY29u +000296 Zm1kdGVzdAkJICAgIGltYXB3cml0ZS5oCQkgICAgIHBvcDNkLXNzbC5kaXN0LmluCmNvbmZtZHRl +000297 c3QyCQkgICAgaW1hcHdyaXRlLm8JCSAgICAgcG9wM2xvZ2luCmNvbmZtZHRlc3Quc3Rkb3V0CSAg +000298 ICBsaWJpbWFwZC5hCQkgICAgIHBvcDNsb2dpbi5jCmNvdXJpZXJwb3AzZC44CQkgICAgbWFpbGJv +000299 eGxpc3QuYwkgICAgIHBvcDNsb2dpbi5vCmNvdXJpZXJwb3AzZC44LmluCSAgICBtYWlsYm94bGlz +000300 dC5oCSAgICAgUkVBRE1FCmNvdXJpZXJwb3AzZC5odG1sCSAgICBtYWlsYm94bGlzdC5vCSAgICAg +000301 UkVBRE1FLmh0bWwKY291cmllcnBvcDNkLmh0bWwuaW4JICAgIG1haW5sb29wLmMJCSAgICAgc2Vh +000302 cmNoLmMKY291cmllcnBvcDNkLnNnbWwJICAgIG1haW5sb29wLmMufjEuNi5+CSAgICAgc2VhcmNo +000303 aW5mby5jCkNWUwkJCSAgICBtYWlubG9vcC5vCQkgICAgIHNlYXJjaGluZm8uaApGQVEJCQkgICAg +000304 TWFrZWZpbGUJCSAgICAgc2VhcmNoaW5mby5vCkZBUS5odG1sCQkgICAgTWFrZWZpbGV+CQkgICAg +000305 IHNlYXJjaC5vCmZldGNoLmMJCQkgICAgTWFrZWZpbGUuYW0JCSAgICAgc21hcC5jCmZldGNoLmMu +000306 fjEuMjEufgkJICAgIE1ha2VmaWxlLmFtLn4xLjMxLn4gICAgICBzbWFwLmN+CmZldGNoaW5mby5j +000307 CQkgICAgTWFrZWZpbGUuaW4JCSAgICAgc21hcC5vCmZldGNoaW5mby5oCQkgICAgbWtpbWFwZGNl +000308 cnQJCSAgICAgc21hcHRlc3RzdWl0ZQpmZXRjaGluZm8ubwkJICAgIG1raW1hcGRjZXJ0LjgJICAg +000309 ICBzbWFwdGVzdHN1aXRlfgpmZXRjaC5vCQkJICAgIG1raW1hcGRjZXJ0LjguaW4JICAgICBzbWFw +000310 dGVzdHN1aXRlLnR4dApodG1sMm1hbi5wbC5pbgkJICAgIG1raW1hcGRjZXJ0Lmh0bWwJICAgICBz +000311 dGFtcC1oMQppbWFwZAkJCSAgICBta2ltYXBkY2VydC5odG1sLmluICAgICAgc3RvcmVpbmZvLmMK +000312 aW1hcGQuOAkJCSAgICBta2ltYXBkY2VydC5pbgkgICAgIHN0b3JlaW5mby5oCmltYXBkLjguaW4J +000313 CSAgICBta2ltYXBkY2VydC5zZ21sCSAgICAgc3RvcmVpbmZvLm8KaW1hcGQuYXV0aHBhbQkJICAg +000314 IG1rcG9wM2RjZXJ0CQkgICAgIHN5c3RlbS1hdXRoLmF1dGhwYW0KaW1hcGQuYwkJCSAgICBta3Bv +000315 cDNkY2VydC44CSAgICAgdAppbWFwZC5jLn4xLjk1Ln4JCSAgICBta3BvcDNkY2VydC44LmluCSAg +000316 ICAgdGVzdHN1aXRlCmltYXBkLmNuZgkJICAgIG1rcG9wM2RjZXJ0Lmh0bWwJICAgICB0ZXN0c3Vp +000317 dGVmaXgucGwKaW1hcGQuY25mLmluCQkgICAgbWtwb3AzZGNlcnQuaHRtbC5pbiAgICAgIHRlc3Rz +000318 dWl0ZWZpeC5wbC5pbgppbWFwZC5kaXN0CQkgICAgbWtwb3AzZGNlcnQuaW4JICAgICB0ZXN0c3Vp +000319 dGUudHh0CmltYXBkLmRpc3QuaW4JCSAgICBta3BvcDNkY2VydC5zZ21sCSAgICAgdGhyZWFkLmMK +000320 aW1hcGQuaAkJCSAgICBtc2dib2R5c3RydWN0dXJlLmMJICAgICB0aHJlYWQuaAppbWFwZC5odG1s +000321 CQkgICAgbXNnYm9keXN0cnVjdHVyZS5vCSAgICAgdGhyZWFkLm8KaW1hcGQuaHRtbC5pbgkJICAg +000322 IG1zZ2VudmVsb3BlLmMK +000323 +000324 +OK Message retrieved. +000325 {2466/2499} FETCH 1 CONTENTS +000326 aclocal.m4 imapd.o msgenvelope.o +000327 authenticate_auth.c imapd.pam mysignal.c +000328 authenticate_auth.c.~1.6.~ imapd.sgml mysignal.h +000329 authenticate_auth.o imapd-ssl.dist mysignal.o +000330 authenticate_stub.c imapd-ssl.dist.in outbox.c +000331 autom4te-2.53.cache imaplogin outbox.h +000332 BUGS imaplogin.c outbox.o +000333 capability.c imaplogin.c.~1.19.~ pop3d +000334 capability.c.~1.6.~ imaplogin.o pop3d.authpam +000335 capability.o imapscanclient.c pop3dcapa.c +000336 ChangeLog imapscanclient.h pop3dcapa.o +000337 config.h imapscanclient.h.~1.6.~ pop3d.cnf +000338 config.h.in imapscanclient.o pop3d.cnf.in +000339 config.h.in~ imaptoken.c pop3d.dist +000340 config.log imaptoken.c.~1.13.~ pop3d.dist.in +000341 config.status imaptoken.h pop3d.pam +000342 configure imaptoken.h.~1.6.~ pop3dserver.c +000343 configure.in imaptoken.o pop3dserver.o +000344 configure.in.~1.79.~ imapwrite.c pop3d-ssl.dist +000345 confmdtest imapwrite.h pop3d-ssl.dist.in +000346 confmdtest2 imapwrite.o pop3login +000347 confmdtest.stdout libimapd.a pop3login.c +000348 courierpop3d.8 mailboxlist.c pop3login.o +000349 courierpop3d.8.in mailboxlist.h README +000350 courierpop3d.html mailboxlist.o README.html +000351 courierpop3d.html.in mainloop.c search.c +000352 courierpop3d.sgml mainloop.c.~1.6.~ searchinfo.c +000353 CVS mainloop.o searchinfo.h +000354 FAQ Makefile searchinfo.o +000355 FAQ.html Makefile~ search.o +000356 fetch.c Makefile.am smap.c +000357 fetch.c.~1.21.~ Makefile.am.~1.31.~ smap.c~ +000358 fetchinfo.c Makefile.in smap.o +000359 fetchinfo.h mkimapdcert smaptestsuite +000360 fetchinfo.o mkimapdcert.8 smaptestsuite~ +000361 fetch.o mkimapdcert.8.in smaptestsuite.txt +000362 html2man.pl.in mkimapdcert.html stamp-h1 +000363 imapd mkimapdcert.html.in storeinfo.c +000364 imapd.8 mkimapdcert.in storeinfo.h +000365 imapd.8.in mkimapdcert.sgml storeinfo.o +000366 imapd.authpam mkpop3dcert system-auth.authpam +000367 imapd.c mkpop3dcert.8 t +000368 imapd.c.~1.95.~ mkpop3dcert.8.in testsuite +000369 imapd.cnf mkpop3dcert.html testsuitefix.pl +000370 imapd.cnf.in mkpop3dcert.html.in testsuitefix.pl.in +000371 imapd.dist mkpop3dcert.in testsuite.txt +000372 imapd.dist.in mkpop3dcert.sgml thread.c +000373 imapd.h msgbodystructure.c thread.h +000374 imapd.html msgbodystructure.o thread.o +000375 imapd.html.in msgenvelope.c +000376 +000377 +OK Message retrieved. +000378 +OK INTERNALDATE set +000379 > Go ahead +000380 * ADD UID +000381 +OK Message saved +000382 * EXISTS 2 +000383 +OK Folder updated +000384 * COPY NEWUID +000385 * COPY NEWUID +000386 +OK Messages copied. +000387 * EXISTS 4 +000388 +OK Folder updated +000389 * COPY NEWUID +000390 * COPY NEWUID +000391 * COPY NEWUID +000392 * COPY NEWUID +000393 +OK Messages copied. +000394 * EXISTS 8 +000395 +OK Folder updated +000396 -ERR --error-- +000397 * COPY NEWUID +000398 * COPY NEWUID +000399 +OK Messages copied. +000400 * EXISTS 10 +000401 +OK Folder updated +000402 +OK INTERNALDATE set +000403 > Go ahead +000404 -ERR --error-- +000405 * EXISTS 10 +000406 +OK Folder opened +000407 {.0} FETCH 10 HEADERS +000408 Mime-Version: 1.0 +000409 . +000410 +OK Message retrieved. +000411 * BYE Courier-SMAP server shutting down +000412 +OK LOGOUT completed +000413 +OK SMAP1 LOGIN Ok. +000414 * EXISTS 10 +000415 +OK Folder opened +000416 * COPY NEWUID +000417 * COPY NEWUID +000418 * COPY NEWUID +000419 * COPY NEWUID +000420 * COPY NEWUID +000421 * COPY NEWUID +000422 * COPY NEWUID +000423 * COPY NEWUID +000424 * COPY NEWUID +000425 * COPY NEWUID +000426 +OK Messages copied. +000427 * EXISTS 20 +000428 +OK Folder updated +000429 * COPY NEWUID +000430 * COPY NEWUID +000431 * COPY NEWUID +000432 * COPY NEWUID +000433 * COPY NEWUID +000434 * COPY NEWUID +000435 * COPY NEWUID +000436 * COPY NEWUID +000437 * COPY NEWUID +000438 * COPY NEWUID +000439 * COPY NEWUID +000440 * COPY NEWUID +000441 * COPY NEWUID +000442 * COPY NEWUID +000443 * COPY NEWUID +000444 * COPY NEWUID +000445 * COPY NEWUID +000446 * COPY NEWUID +000447 * COPY NEWUID +000448 * COPY NEWUID +000449 +OK Messages copied. +000450 * EXISTS 40 +000451 +OK Folder updated +000452 * COPY NEWUID +000453 * COPY NEWUID +000454 * COPY NEWUID +000455 * COPY NEWUID +000456 * COPY NEWUID +000457 * COPY NEWUID +000458 * COPY NEWUID +000459 * COPY NEWUID +000460 * COPY NEWUID +000461 * COPY NEWUID +000462 * COPY NEWUID +000463 * COPY NEWUID +000464 * COPY NEWUID +000465 * COPY NEWUID +000466 * COPY NEWUID +000467 * COPY NEWUID +000468 * COPY NEWUID +000469 * COPY NEWUID +000470 * COPY NEWUID +000471 * COPY NEWUID +000472 * COPY NEWUID +000473 * COPY NEWUID +000474 * COPY NEWUID +000475 * COPY NEWUID +000476 * COPY NEWUID +000477 * COPY NEWUID +000478 * COPY NEWUID +000479 * COPY NEWUID +000480 * COPY NEWUID +000481 * COPY NEWUID +000482 * COPY NEWUID +000483 * COPY NEWUID +000484 * COPY NEWUID +000485 * COPY NEWUID +000486 * COPY NEWUID +000487 * COPY NEWUID +000488 * COPY NEWUID +000489 * COPY NEWUID +000490 * COPY NEWUID +000491 * COPY NEWUID +000492 +OK Messages copied. +000493 * EXISTS 80 +000494 +OK Folder updated +000495 * COPY NEWUID +000496 * COPY NEWUID +000497 * COPY NEWUID +000498 * COPY NEWUID +000499 * COPY NEWUID +000500 * COPY NEWUID +000501 * COPY NEWUID +000502 * COPY NEWUID +000503 * COPY NEWUID +000504 * COPY NEWUID +000505 * COPY NEWUID +000506 * COPY NEWUID +000507 * COPY NEWUID +000508 * COPY NEWUID +000509 * COPY NEWUID +000510 * COPY NEWUID +000511 * COPY NEWUID +000512 * COPY NEWUID +000513 * COPY NEWUID +000514 * COPY NEWUID +000515 * COPY NEWUID +000516 * COPY NEWUID +000517 * COPY NEWUID +000518 * COPY NEWUID +000519 * COPY NEWUID +000520 * COPY NEWUID +000521 * COPY NEWUID +000522 * COPY NEWUID +000523 * COPY NEWUID +000524 * COPY NEWUID +000525 * COPY NEWUID +000526 * COPY NEWUID +000527 * COPY NEWUID +000528 * COPY NEWUID +000529 * COPY NEWUID +000530 * COPY NEWUID +000531 * COPY NEWUID +000532 * COPY NEWUID +000533 * COPY NEWUID +000534 * COPY NEWUID +000535 * COPY NEWUID +000536 * COPY NEWUID +000537 * COPY NEWUID +000538 * COPY NEWUID +000539 * COPY NEWUID +000540 * COPY NEWUID +000541 * COPY NEWUID +000542 * COPY NEWUID +000543 * COPY NEWUID +000544 * COPY NEWUID +000545 * COPY NEWUID +000546 * COPY NEWUID +000547 * COPY NEWUID +000548 * COPY NEWUID +000549 * COPY NEWUID +000550 * COPY NEWUID +000551 * COPY NEWUID +000552 * COPY NEWUID +000553 * COPY NEWUID +000554 * COPY NEWUID +000555 * COPY NEWUID +000556 * COPY NEWUID +000557 * COPY NEWUID +000558 * COPY NEWUID +000559 * COPY NEWUID +000560 * COPY NEWUID +000561 * COPY NEWUID +000562 * COPY NEWUID +000563 * COPY NEWUID +000564 * COPY NEWUID +000565 * COPY NEWUID +000566 * COPY NEWUID +000567 * COPY NEWUID +000568 * COPY NEWUID +000569 * COPY NEWUID +000570 * COPY NEWUID +000571 * COPY NEWUID +000572 * COPY NEWUID +000573 * COPY NEWUID +000574 * COPY NEWUID +000575 +OK Messages copied. +000576 * EXISTS 160 +000577 +OK Folder updated +000578 * COPY NEWUID +000579 * COPY NEWUID +000580 * COPY NEWUID +000581 * COPY NEWUID +000582 * COPY NEWUID +000583 * COPY NEWUID +000584 * COPY NEWUID +000585 * COPY NEWUID +000586 * COPY NEWUID +000587 * COPY NEWUID +000588 * COPY NEWUID +000589 * COPY NEWUID +000590 * COPY NEWUID +000591 * COPY NEWUID +000592 * COPY NEWUID +000593 * COPY NEWUID +000594 * COPY NEWUID +000595 * COPY NEWUID +000596 * COPY NEWUID +000597 * COPY NEWUID +000598 * COPY NEWUID +000599 * COPY NEWUID +000600 * COPY NEWUID +000601 * COPY NEWUID +000602 * COPY NEWUID +000603 * COPY NEWUID +000604 * COPY NEWUID +000605 * COPY NEWUID +000606 * COPY NEWUID +000607 * COPY NEWUID +000608 * COPY NEWUID +000609 * COPY NEWUID +000610 * COPY NEWUID +000611 * COPY NEWUID +000612 * COPY NEWUID +000613 * COPY NEWUID +000614 * COPY NEWUID +000615 * COPY NEWUID +000616 * COPY NEWUID +000617 * COPY NEWUID +000618 * COPY NEWUID +000619 * COPY NEWUID +000620 * COPY NEWUID +000621 * COPY NEWUID +000622 * COPY NEWUID +000623 * COPY NEWUID +000624 * COPY NEWUID +000625 * COPY NEWUID +000626 * COPY NEWUID +000627 * COPY NEWUID +000628 * COPY NEWUID +000629 * COPY NEWUID +000630 * COPY NEWUID +000631 * COPY NEWUID +000632 * COPY NEWUID +000633 * COPY NEWUID +000634 * COPY NEWUID +000635 * COPY NEWUID +000636 * COPY NEWUID +000637 * COPY NEWUID +000638 * COPY NEWUID +000639 * COPY NEWUID +000640 * COPY NEWUID +000641 * COPY NEWUID +000642 * COPY NEWUID +000643 * COPY NEWUID +000644 * COPY NEWUID +000645 * COPY NEWUID +000646 * COPY NEWUID +000647 * COPY NEWUID +000648 * COPY NEWUID +000649 * COPY NEWUID +000650 * COPY NEWUID +000651 * COPY NEWUID +000652 * COPY NEWUID +000653 * COPY NEWUID +000654 * COPY NEWUID +000655 * COPY NEWUID +000656 * COPY NEWUID +000657 * COPY NEWUID +000658 * COPY NEWUID +000659 * COPY NEWUID +000660 * COPY NEWUID +000661 * COPY NEWUID +000662 * COPY NEWUID +000663 * COPY NEWUID +000664 * COPY NEWUID +000665 * COPY NEWUID +000666 * COPY NEWUID +000667 * COPY NEWUID +000668 * COPY NEWUID +000669 * COPY NEWUID +000670 * COPY NEWUID +000671 * COPY NEWUID +000672 * COPY NEWUID +000673 * COPY NEWUID +000674 * COPY NEWUID +000675 * COPY NEWUID +000676 * COPY NEWUID +000677 * COPY NEWUID +000678 * COPY NEWUID +000679 * COPY NEWUID +000680 * COPY NEWUID +000681 * COPY NEWUID +000682 * COPY NEWUID +000683 * COPY NEWUID +000684 * COPY NEWUID +000685 * COPY NEWUID +000686 * COPY NEWUID +000687 * COPY NEWUID +000688 * COPY NEWUID +000689 * COPY NEWUID +000690 * COPY NEWUID +000691 * COPY NEWUID +000692 * COPY NEWUID +000693 * COPY NEWUID +000694 * COPY NEWUID +000695 * COPY NEWUID +000696 * COPY NEWUID +000697 * COPY NEWUID +000698 * COPY NEWUID +000699 * COPY NEWUID +000700 * COPY NEWUID +000701 * COPY NEWUID +000702 * COPY NEWUID +000703 * COPY NEWUID +000704 * COPY NEWUID +000705 * COPY NEWUID +000706 * COPY NEWUID +000707 * COPY NEWUID +000708 * COPY NEWUID +000709 * COPY NEWUID +000710 * COPY NEWUID +000711 * COPY NEWUID +000712 * COPY NEWUID +000713 * COPY NEWUID +000714 * COPY NEWUID +000715 * COPY NEWUID +000716 * COPY NEWUID +000717 * COPY NEWUID +000718 * COPY NEWUID +000719 * COPY NEWUID +000720 * COPY NEWUID +000721 * COPY NEWUID +000722 * COPY NEWUID +000723 * COPY NEWUID +000724 * COPY NEWUID +000725 * COPY NEWUID +000726 * COPY NEWUID +000727 * COPY NEWUID +000728 * COPY NEWUID +000729 * COPY NEWUID +000730 * COPY NEWUID +000731 * COPY NEWUID +000732 * COPY NEWUID +000733 * COPY NEWUID +000734 * COPY NEWUID +000735 * COPY NEWUID +000736 * COPY NEWUID +000737 * COPY NEWUID +000738 +OK Messages copied. +000739 * EXISTS 320 +000740 +OK Folder updated +000741 * COPY NEWUID +000742 * COPY NEWUID +000743 * COPY NEWUID +000744 * COPY NEWUID +000745 * COPY NEWUID +000746 * COPY NEWUID +000747 * COPY NEWUID +000748 * COPY NEWUID +000749 * COPY NEWUID +000750 * COPY NEWUID +000751 * COPY NEWUID +000752 * COPY NEWUID +000753 * COPY NEWUID +000754 * COPY NEWUID +000755 * COPY NEWUID +000756 * COPY NEWUID +000757 * COPY NEWUID +000758 * COPY NEWUID +000759 * COPY NEWUID +000760 * COPY NEWUID +000761 * COPY NEWUID +000762 * COPY NEWUID +000763 * COPY NEWUID +000764 * COPY NEWUID +000765 * COPY NEWUID +000766 * COPY NEWUID +000767 * COPY NEWUID +000768 * COPY NEWUID +000769 * COPY NEWUID +000770 * COPY NEWUID +000771 * COPY NEWUID +000772 * COPY NEWUID +000773 * COPY NEWUID +000774 * COPY NEWUID +000775 * COPY NEWUID +000776 * COPY NEWUID +000777 * COPY NEWUID +000778 * COPY NEWUID +000779 * COPY NEWUID +000780 * COPY NEWUID +000781 * COPY NEWUID +000782 * COPY NEWUID +000783 * COPY NEWUID +000784 * COPY NEWUID +000785 * COPY NEWUID +000786 * COPY NEWUID +000787 * COPY NEWUID +000788 * COPY NEWUID +000789 * COPY NEWUID +000790 * COPY NEWUID +000791 * COPY NEWUID +000792 * COPY NEWUID +000793 * COPY NEWUID +000794 * COPY NEWUID +000795 * COPY NEWUID +000796 * COPY NEWUID +000797 * COPY NEWUID +000798 * COPY NEWUID +000799 * COPY NEWUID +000800 * COPY NEWUID +000801 * COPY NEWUID +000802 * COPY NEWUID +000803 * COPY NEWUID +000804 * COPY NEWUID +000805 * COPY NEWUID +000806 * COPY NEWUID +000807 * COPY NEWUID +000808 * COPY NEWUID +000809 * COPY NEWUID +000810 * COPY NEWUID +000811 * COPY NEWUID +000812 * COPY NEWUID +000813 * COPY NEWUID +000814 * COPY NEWUID +000815 * COPY NEWUID +000816 * COPY NEWUID +000817 * COPY NEWUID +000818 * COPY NEWUID +000819 * COPY NEWUID +000820 * COPY NEWUID +000821 * COPY NEWUID +000822 * COPY NEWUID +000823 * COPY NEWUID +000824 * COPY NEWUID +000825 * COPY NEWUID +000826 * COPY NEWUID +000827 * COPY NEWUID +000828 * COPY NEWUID +000829 * COPY NEWUID +000830 * COPY NEWUID +000831 * COPY NEWUID +000832 * COPY NEWUID +000833 * COPY NEWUID +000834 * COPY NEWUID +000835 * COPY NEWUID +000836 * COPY NEWUID +000837 * COPY NEWUID +000838 * COPY NEWUID +000839 * COPY NEWUID +000840 * COPY NEWUID +000841 * COPY NEWUID +000842 * COPY NEWUID +000843 * COPY NEWUID +000844 * COPY NEWUID +000845 * COPY NEWUID +000846 * COPY NEWUID +000847 * COPY NEWUID +000848 * COPY NEWUID +000849 * COPY NEWUID +000850 * COPY NEWUID +000851 * COPY NEWUID +000852 * COPY NEWUID +000853 * COPY NEWUID +000854 * COPY NEWUID +000855 * COPY NEWUID +000856 * COPY NEWUID +000857 * COPY NEWUID +000858 * COPY NEWUID +000859 * COPY NEWUID +000860 * COPY NEWUID +000861 * COPY NEWUID +000862 * COPY NEWUID +000863 * COPY NEWUID +000864 * COPY NEWUID +000865 * COPY NEWUID +000866 * COPY NEWUID +000867 * COPY NEWUID +000868 * COPY NEWUID +000869 * COPY NEWUID +000870 * COPY NEWUID +000871 * COPY NEWUID +000872 * COPY NEWUID +000873 * COPY NEWUID +000874 * COPY NEWUID +000875 * COPY NEWUID +000876 * COPY NEWUID +000877 * COPY NEWUID +000878 * COPY NEWUID +000879 * COPY NEWUID +000880 * COPY NEWUID +000881 * COPY NEWUID +000882 * COPY NEWUID +000883 * COPY NEWUID +000884 * COPY NEWUID +000885 * COPY NEWUID +000886 * COPY NEWUID +000887 * COPY NEWUID +000888 * COPY NEWUID +000889 * COPY NEWUID +000890 * COPY NEWUID +000891 * COPY NEWUID +000892 * COPY NEWUID +000893 * COPY NEWUID +000894 * COPY NEWUID +000895 * COPY NEWUID +000896 * COPY NEWUID +000897 * COPY NEWUID +000898 * COPY NEWUID +000899 * COPY NEWUID +000900 * COPY NEWUID +000901 * COPY NEWUID +000902 * COPY NEWUID +000903 * COPY NEWUID +000904 * COPY NEWUID +000905 * COPY NEWUID +000906 * COPY NEWUID +000907 * COPY NEWUID +000908 * COPY NEWUID +000909 * COPY NEWUID +000910 * COPY NEWUID +000911 * COPY NEWUID +000912 * COPY NEWUID +000913 * COPY NEWUID +000914 * COPY NEWUID +000915 * COPY NEWUID +000916 * COPY NEWUID +000917 * COPY NEWUID +000918 * COPY NEWUID +000919 * COPY NEWUID +000920 * COPY NEWUID +000921 * COPY NEWUID +000922 * COPY NEWUID +000923 * COPY NEWUID +000924 * COPY NEWUID +000925 * COPY NEWUID +000926 * COPY NEWUID +000927 * COPY NEWUID +000928 * COPY NEWUID +000929 * COPY NEWUID +000930 * COPY NEWUID +000931 * COPY NEWUID +000932 * COPY NEWUID +000933 * COPY NEWUID +000934 * COPY NEWUID +000935 * COPY NEWUID +000936 * COPY NEWUID +000937 * COPY NEWUID +000938 * COPY NEWUID +000939 * COPY NEWUID +000940 * COPY NEWUID +000941 * COPY NEWUID +000942 * COPY NEWUID +000943 * COPY NEWUID +000944 * COPY NEWUID +000945 * COPY NEWUID +000946 * COPY NEWUID +000947 * COPY NEWUID +000948 * COPY NEWUID +000949 * COPY NEWUID +000950 * COPY NEWUID +000951 * COPY NEWUID +000952 * COPY NEWUID +000953 * COPY NEWUID +000954 * COPY NEWUID +000955 * COPY NEWUID +000956 * COPY NEWUID +000957 * COPY NEWUID +000958 * COPY NEWUID +000959 * COPY NEWUID +000960 * COPY NEWUID +000961 * COPY NEWUID +000962 * COPY NEWUID +000963 * COPY NEWUID +000964 * COPY NEWUID +000965 * COPY NEWUID +000966 * COPY NEWUID +000967 * COPY NEWUID +000968 * COPY NEWUID +000969 * COPY NEWUID +000970 * COPY NEWUID +000971 * COPY NEWUID +000972 * COPY NEWUID +000973 * COPY NEWUID +000974 * COPY NEWUID +000975 * COPY NEWUID +000976 * COPY NEWUID +000977 * COPY NEWUID +000978 * COPY NEWUID +000979 * COPY NEWUID +000980 * COPY NEWUID +000981 * COPY NEWUID +000982 * COPY NEWUID +000983 * COPY NEWUID +000984 * COPY NEWUID +000985 * COPY NEWUID +000986 * COPY NEWUID +000987 * COPY NEWUID +000988 * COPY NEWUID +000989 * COPY NEWUID +000990 * COPY NEWUID +000991 * COPY NEWUID +000992 * COPY NEWUID +000993 * COPY NEWUID +000994 * COPY NEWUID +000995 * COPY NEWUID +000996 * COPY NEWUID +000997 * COPY NEWUID +000998 * COPY NEWUID +000999 * COPY NEWUID +001000 * COPY NEWUID +001001 * COPY NEWUID +001002 * COPY NEWUID +001003 * COPY NEWUID +001004 * COPY NEWUID +001005 * COPY NEWUID +001006 * COPY NEWUID +001007 * COPY NEWUID +001008 * COPY NEWUID +001009 * COPY NEWUID +001010 * COPY NEWUID +001011 * COPY NEWUID +001012 * COPY NEWUID +001013 * COPY NEWUID +001014 * COPY NEWUID +001015 * COPY NEWUID +001016 * COPY NEWUID +001017 * COPY NEWUID +001018 * COPY NEWUID +001019 * COPY NEWUID +001020 * COPY NEWUID +001021 * COPY NEWUID +001022 * COPY NEWUID +001023 * COPY NEWUID +001024 * COPY NEWUID +001025 * COPY NEWUID +001026 * COPY NEWUID +001027 * COPY NEWUID +001028 * COPY NEWUID +001029 * COPY NEWUID +001030 * COPY NEWUID +001031 * COPY NEWUID +001032 * COPY NEWUID +001033 * COPY NEWUID +001034 * COPY NEWUID +001035 * COPY NEWUID +001036 * COPY NEWUID +001037 * COPY NEWUID +001038 * COPY NEWUID +001039 * COPY NEWUID +001040 * COPY NEWUID +001041 * COPY NEWUID +001042 * COPY NEWUID +001043 * COPY NEWUID +001044 * COPY NEWUID +001045 * COPY NEWUID +001046 * COPY NEWUID +001047 * COPY NEWUID +001048 * COPY NEWUID +001049 * COPY NEWUID +001050 * COPY NEWUID +001051 * COPY NEWUID +001052 * COPY NEWUID +001053 * COPY NEWUID +001054 * COPY NEWUID +001055 * COPY NEWUID +001056 * COPY NEWUID +001057 * COPY NEWUID +001058 * COPY NEWUID +001059 * COPY NEWUID +001060 * COPY NEWUID +001061 +OK Messages copied. +001062 * EXISTS 640 +001063 +OK Folder updated +001064 * BYE Courier-SMAP server shutting down +001065 +OK LOGOUT completed +001066 +OK SMAP1 LOGIN Ok. +001067 * EXISTS 640 +001068 +OK Folder opened +001069 * FETCH 3 FLAGS=SEEN,DELETED +001070 * FETCH 4 FLAGS=SEEN,DELETED +001071 * FETCH 5 FLAGS=SEEN,DELETED +001072 * FETCH 7 FLAGS=SEEN,DELETED +001073 * FETCH 9 FLAGS=SEEN,DELETED +001074 +OK Folder status updated +001075 * FETCH 13 FLAGS=SEEN,DELETED +001076 * FETCH 14 FLAGS=SEEN,DELETED +001077 * FETCH 15 FLAGS=SEEN,DELETED +001078 * FETCH 17 FLAGS=SEEN,DELETED +001079 * FETCH 19 FLAGS=SEEN,DELETED +001080 +OK Folder status updated +001081 * FETCH 23 FLAGS=SEEN,DELETED +001082 * FETCH 24 FLAGS=SEEN,DELETED +001083 * FETCH 25 FLAGS=SEEN,DELETED +001084 * FETCH 27 FLAGS=SEEN,DELETED +001085 * FETCH 29 FLAGS=SEEN,DELETED +001086 +OK Folder status updated +001087 * FETCH 33 FLAGS=SEEN,DELETED +001088 * FETCH 34 FLAGS=SEEN,DELETED +001089 * FETCH 35 FLAGS=SEEN,DELETED +001090 * FETCH 37 FLAGS=SEEN,DELETED +001091 * FETCH 39 FLAGS=SEEN,DELETED +001092 +OK Folder status updated +001093 * FETCH 43 FLAGS=SEEN,DELETED +001094 * FETCH 44 FLAGS=SEEN,DELETED +001095 * FETCH 45 FLAGS=SEEN,DELETED +001096 * FETCH 47 FLAGS=SEEN,DELETED +001097 * FETCH 49 FLAGS=SEEN,DELETED +001098 +OK Folder status updated +001099 * FETCH 53 FLAGS=SEEN,DELETED +001100 * FETCH 54 FLAGS=SEEN,DELETED +001101 * FETCH 55 FLAGS=SEEN,DELETED +001102 * FETCH 57 FLAGS=SEEN,DELETED +001103 * FETCH 59 FLAGS=SEEN,DELETED +001104 +OK Folder status updated +001105 * FETCH 63 FLAGS=SEEN,DELETED +001106 * FETCH 64 FLAGS=SEEN,DELETED +001107 * FETCH 65 FLAGS=SEEN,DELETED +001108 * FETCH 67 FLAGS=SEEN,DELETED +001109 * FETCH 69 FLAGS=SEEN,DELETED +001110 +OK Folder status updated +001111 * FETCH 73 FLAGS=SEEN,DELETED +001112 * FETCH 74 FLAGS=SEEN,DELETED +001113 * FETCH 75 FLAGS=SEEN,DELETED +001114 * FETCH 77 FLAGS=SEEN,DELETED +001115 * FETCH 79 FLAGS=SEEN,DELETED +001116 +OK Folder status updated +001117 * FETCH 83 FLAGS=SEEN,DELETED +001118 * FETCH 84 FLAGS=SEEN,DELETED +001119 * FETCH 85 FLAGS=SEEN,DELETED +001120 * FETCH 87 FLAGS=SEEN,DELETED +001121 * FETCH 89 FLAGS=SEEN,DELETED +001122 +OK Folder status updated +001123 * FETCH 93 FLAGS=SEEN,DELETED +001124 * FETCH 94 FLAGS=SEEN,DELETED +001125 * FETCH 95 FLAGS=SEEN,DELETED +001126 * FETCH 97 FLAGS=SEEN,DELETED +001127 * FETCH 99 FLAGS=SEEN,DELETED +001128 +OK Folder status updated +001129 * FETCH 103 FLAGS=SEEN,DELETED +001130 * FETCH 104 FLAGS=SEEN,DELETED +001131 * FETCH 105 FLAGS=SEEN,DELETED +001132 * FETCH 107 FLAGS=SEEN,DELETED +001133 * FETCH 109 FLAGS=SEEN,DELETED +001134 +OK Folder status updated +001135 * FETCH 113 FLAGS=SEEN,DELETED +001136 * FETCH 114 FLAGS=SEEN,DELETED +001137 * FETCH 115 FLAGS=SEEN,DELETED +001138 * FETCH 117 FLAGS=SEEN,DELETED +001139 * FETCH 119 FLAGS=SEEN,DELETED +001140 +OK Folder status updated +001141 * FETCH 123 FLAGS=SEEN,DELETED +001142 * FETCH 124 FLAGS=SEEN,DELETED +001143 * FETCH 125 FLAGS=SEEN,DELETED +001144 * FETCH 127 FLAGS=SEEN,DELETED +001145 * FETCH 129 FLAGS=SEEN,DELETED +001146 +OK Folder status updated +001147 * FETCH 133 FLAGS=SEEN,DELETED +001148 * FETCH 134 FLAGS=SEEN,DELETED +001149 * FETCH 135 FLAGS=SEEN,DELETED +001150 * FETCH 137 FLAGS=SEEN,DELETED +001151 * FETCH 139 FLAGS=SEEN,DELETED +001152 +OK Folder status updated +001153 * FETCH 143 FLAGS=SEEN,DELETED +001154 * FETCH 144 FLAGS=SEEN,DELETED +001155 * FETCH 145 FLAGS=SEEN,DELETED +001156 * FETCH 147 FLAGS=SEEN,DELETED +001157 * FETCH 149 FLAGS=SEEN,DELETED +001158 +OK Folder status updated +001159 * FETCH 153 FLAGS=SEEN,DELETED +001160 * FETCH 154 FLAGS=SEEN,DELETED +001161 * FETCH 155 FLAGS=SEEN,DELETED +001162 * FETCH 157 FLAGS=SEEN,DELETED +001163 * FETCH 159 FLAGS=SEEN,DELETED +001164 +OK Folder status updated +001165 * FETCH 163 FLAGS=SEEN,DELETED +001166 * FETCH 164 FLAGS=SEEN,DELETED +001167 * FETCH 165 FLAGS=SEEN,DELETED +001168 * FETCH 167 FLAGS=SEEN,DELETED +001169 * FETCH 169 FLAGS=SEEN,DELETED +001170 +OK Folder status updated +001171 * FETCH 173 FLAGS=SEEN,DELETED +001172 * FETCH 174 FLAGS=SEEN,DELETED +001173 * FETCH 175 FLAGS=SEEN,DELETED +001174 * FETCH 177 FLAGS=SEEN,DELETED +001175 * FETCH 179 FLAGS=SEEN,DELETED +001176 +OK Folder status updated +001177 * FETCH 183 FLAGS=SEEN,DELETED +001178 * FETCH 184 FLAGS=SEEN,DELETED +001179 * FETCH 185 FLAGS=SEEN,DELETED +001180 * FETCH 187 FLAGS=SEEN,DELETED +001181 * FETCH 189 FLAGS=SEEN,DELETED +001182 +OK Folder status updated +001183 * FETCH 193 FLAGS=SEEN,DELETED +001184 * FETCH 194 FLAGS=SEEN,DELETED +001185 * FETCH 195 FLAGS=SEEN,DELETED +001186 * FETCH 197 FLAGS=SEEN,DELETED +001187 * FETCH 199 FLAGS=SEEN,DELETED +001188 +OK Folder status updated +001189 * FETCH 203 FLAGS=SEEN,DELETED +001190 * FETCH 204 FLAGS=SEEN,DELETED +001191 * FETCH 205 FLAGS=SEEN,DELETED +001192 * FETCH 207 FLAGS=SEEN,DELETED +001193 * FETCH 209 FLAGS=SEEN,DELETED +001194 +OK Folder status updated +001195 * FETCH 213 FLAGS=SEEN,DELETED +001196 * FETCH 214 FLAGS=SEEN,DELETED +001197 * FETCH 215 FLAGS=SEEN,DELETED +001198 * FETCH 217 FLAGS=SEEN,DELETED +001199 * FETCH 219 FLAGS=SEEN,DELETED +001200 +OK Folder status updated +001201 * FETCH 223 FLAGS=SEEN,DELETED +001202 * FETCH 224 FLAGS=SEEN,DELETED +001203 * FETCH 225 FLAGS=SEEN,DELETED +001204 * FETCH 227 FLAGS=SEEN,DELETED +001205 * FETCH 229 FLAGS=SEEN,DELETED +001206 +OK Folder status updated +001207 * FETCH 233 FLAGS=SEEN,DELETED +001208 * FETCH 234 FLAGS=SEEN,DELETED +001209 * FETCH 235 FLAGS=SEEN,DELETED +001210 * FETCH 237 FLAGS=SEEN,DELETED +001211 * FETCH 239 FLAGS=SEEN,DELETED +001212 +OK Folder status updated +001213 * FETCH 243 FLAGS=SEEN,DELETED +001214 * FETCH 244 FLAGS=SEEN,DELETED +001215 * FETCH 245 FLAGS=SEEN,DELETED +001216 * FETCH 247 FLAGS=SEEN,DELETED +001217 * FETCH 249 FLAGS=SEEN,DELETED +001218 +OK Folder status updated +001219 * FETCH 253 FLAGS=SEEN,DELETED +001220 * FETCH 254 FLAGS=SEEN,DELETED +001221 * FETCH 255 FLAGS=SEEN,DELETED +001222 * FETCH 257 FLAGS=SEEN,DELETED +001223 * FETCH 259 FLAGS=SEEN,DELETED +001224 +OK Folder status updated +001225 * FETCH 263 FLAGS=SEEN,DELETED +001226 * FETCH 264 FLAGS=SEEN,DELETED +001227 * FETCH 265 FLAGS=SEEN,DELETED +001228 * FETCH 267 FLAGS=SEEN,DELETED +001229 * FETCH 269 FLAGS=SEEN,DELETED +001230 +OK Folder status updated +001231 * FETCH 273 FLAGS=SEEN,DELETED +001232 * FETCH 274 FLAGS=SEEN,DELETED +001233 * FETCH 275 FLAGS=SEEN,DELETED +001234 * FETCH 277 FLAGS=SEEN,DELETED +001235 * FETCH 279 FLAGS=SEEN,DELETED +001236 +OK Folder status updated +001237 * FETCH 283 FLAGS=SEEN,DELETED +001238 * FETCH 284 FLAGS=SEEN,DELETED +001239 * FETCH 285 FLAGS=SEEN,DELETED +001240 * FETCH 287 FLAGS=SEEN,DELETED +001241 * FETCH 289 FLAGS=SEEN,DELETED +001242 +OK Folder status updated +001243 * FETCH 293 FLAGS=SEEN,DELETED +001244 * FETCH 294 FLAGS=SEEN,DELETED +001245 * FETCH 295 FLAGS=SEEN,DELETED +001246 * FETCH 297 FLAGS=SEEN,DELETED +001247 * FETCH 299 FLAGS=SEEN,DELETED +001248 +OK Folder status updated +001249 * FETCH 303 FLAGS=SEEN,DELETED +001250 * FETCH 304 FLAGS=SEEN,DELETED +001251 * FETCH 305 FLAGS=SEEN,DELETED +001252 * FETCH 307 FLAGS=SEEN,DELETED +001253 * FETCH 309 FLAGS=SEEN,DELETED +001254 +OK Folder status updated +001255 * FETCH 313 FLAGS=SEEN,DELETED +001256 * FETCH 314 FLAGS=SEEN,DELETED +001257 * FETCH 315 FLAGS=SEEN,DELETED +001258 * FETCH 317 FLAGS=SEEN,DELETED +001259 * FETCH 319 FLAGS=SEEN,DELETED +001260 +OK Folder status updated +001261 * FETCH 323 FLAGS=SEEN,DELETED +001262 * FETCH 324 FLAGS=SEEN,DELETED +001263 * FETCH 325 FLAGS=SEEN,DELETED +001264 * FETCH 327 FLAGS=SEEN,DELETED +001265 * FETCH 329 FLAGS=SEEN,DELETED +001266 +OK Folder status updated +001267 * FETCH 333 FLAGS=SEEN,DELETED +001268 * FETCH 334 FLAGS=SEEN,DELETED +001269 * FETCH 335 FLAGS=SEEN,DELETED +001270 * FETCH 337 FLAGS=SEEN,DELETED +001271 * FETCH 339 FLAGS=SEEN,DELETED +001272 +OK Folder status updated +001273 * FETCH 343 FLAGS=SEEN,DELETED +001274 * FETCH 344 FLAGS=SEEN,DELETED +001275 * FETCH 345 FLAGS=SEEN,DELETED +001276 * FETCH 347 FLAGS=SEEN,DELETED +001277 * FETCH 349 FLAGS=SEEN,DELETED +001278 +OK Folder status updated +001279 * FETCH 353 FLAGS=SEEN,DELETED +001280 * FETCH 354 FLAGS=SEEN,DELETED +001281 * FETCH 355 FLAGS=SEEN,DELETED +001282 * FETCH 357 FLAGS=SEEN,DELETED +001283 * FETCH 359 FLAGS=SEEN,DELETED +001284 +OK Folder status updated +001285 * FETCH 363 FLAGS=SEEN,DELETED +001286 * FETCH 364 FLAGS=SEEN,DELETED +001287 * FETCH 365 FLAGS=SEEN,DELETED +001288 * FETCH 367 FLAGS=SEEN,DELETED +001289 * FETCH 369 FLAGS=SEEN,DELETED +001290 +OK Folder status updated +001291 * FETCH 373 FLAGS=SEEN,DELETED +001292 * FETCH 374 FLAGS=SEEN,DELETED +001293 * FETCH 375 FLAGS=SEEN,DELETED +001294 * FETCH 377 FLAGS=SEEN,DELETED +001295 * FETCH 379 FLAGS=SEEN,DELETED +001296 +OK Folder status updated +001297 * FETCH 383 FLAGS=SEEN,DELETED +001298 * FETCH 384 FLAGS=SEEN,DELETED +001299 * FETCH 385 FLAGS=SEEN,DELETED +001300 * FETCH 387 FLAGS=SEEN,DELETED +001301 * FETCH 389 FLAGS=SEEN,DELETED +001302 +OK Folder status updated +001303 * FETCH 393 FLAGS=SEEN,DELETED +001304 * FETCH 394 FLAGS=SEEN,DELETED +001305 * FETCH 395 FLAGS=SEEN,DELETED +001306 * FETCH 397 FLAGS=SEEN,DELETED +001307 * FETCH 399 FLAGS=SEEN,DELETED +001308 +OK Folder status updated +001309 * FETCH 403 FLAGS=SEEN,DELETED +001310 * FETCH 404 FLAGS=SEEN,DELETED +001311 * FETCH 405 FLAGS=SEEN,DELETED +001312 * FETCH 407 FLAGS=SEEN,DELETED +001313 * FETCH 409 FLAGS=SEEN,DELETED +001314 +OK Folder status updated +001315 * FETCH 413 FLAGS=SEEN,DELETED +001316 * FETCH 414 FLAGS=SEEN,DELETED +001317 * FETCH 415 FLAGS=SEEN,DELETED +001318 * FETCH 417 FLAGS=SEEN,DELETED +001319 * FETCH 419 FLAGS=SEEN,DELETED +001320 +OK Folder status updated +001321 * FETCH 423 FLAGS=SEEN,DELETED +001322 * FETCH 424 FLAGS=SEEN,DELETED +001323 * FETCH 425 FLAGS=SEEN,DELETED +001324 * FETCH 427 FLAGS=SEEN,DELETED +001325 * FETCH 429 FLAGS=SEEN,DELETED +001326 +OK Folder status updated +001327 * FETCH 433 FLAGS=SEEN,DELETED +001328 * FETCH 434 FLAGS=SEEN,DELETED +001329 * FETCH 435 FLAGS=SEEN,DELETED +001330 * FETCH 437 FLAGS=SEEN,DELETED +001331 * FETCH 439 FLAGS=SEEN,DELETED +001332 +OK Folder status updated +001333 * FETCH 443 FLAGS=SEEN,DELETED +001334 * FETCH 444 FLAGS=SEEN,DELETED +001335 * FETCH 445 FLAGS=SEEN,DELETED +001336 * FETCH 447 FLAGS=SEEN,DELETED +001337 * FETCH 449 FLAGS=SEEN,DELETED +001338 +OK Folder status updated +001339 * FETCH 453 FLAGS=SEEN,DELETED +001340 * FETCH 454 FLAGS=SEEN,DELETED +001341 * FETCH 455 FLAGS=SEEN,DELETED +001342 * FETCH 457 FLAGS=SEEN,DELETED +001343 * FETCH 459 FLAGS=SEEN,DELETED +001344 +OK Folder status updated +001345 * FETCH 463 FLAGS=SEEN,DELETED +001346 * FETCH 464 FLAGS=SEEN,DELETED +001347 * FETCH 465 FLAGS=SEEN,DELETED +001348 * FETCH 467 FLAGS=SEEN,DELETED +001349 * FETCH 469 FLAGS=SEEN,DELETED +001350 +OK Folder status updated +001351 * FETCH 473 FLAGS=SEEN,DELETED +001352 * FETCH 474 FLAGS=SEEN,DELETED +001353 * FETCH 475 FLAGS=SEEN,DELETED +001354 * FETCH 477 FLAGS=SEEN,DELETED +001355 * FETCH 479 FLAGS=SEEN,DELETED +001356 +OK Folder status updated +001357 * FETCH 483 FLAGS=SEEN,DELETED +001358 * FETCH 484 FLAGS=SEEN,DELETED +001359 * FETCH 485 FLAGS=SEEN,DELETED +001360 * FETCH 487 FLAGS=SEEN,DELETED +001361 * FETCH 489 FLAGS=SEEN,DELETED +001362 +OK Folder status updated +001363 * FETCH 493 FLAGS=SEEN,DELETED +001364 * FETCH 494 FLAGS=SEEN,DELETED +001365 * FETCH 495 FLAGS=SEEN,DELETED +001366 * FETCH 497 FLAGS=SEEN,DELETED +001367 * FETCH 499 FLAGS=SEEN,DELETED +001368 +OK Folder status updated +001369 * FETCH 503 FLAGS=SEEN,DELETED +001370 * FETCH 504 FLAGS=SEEN,DELETED +001371 * FETCH 505 FLAGS=SEEN,DELETED +001372 * FETCH 507 FLAGS=SEEN,DELETED +001373 * FETCH 509 FLAGS=SEEN,DELETED +001374 +OK Folder status updated +001375 * FETCH 513 FLAGS=SEEN,DELETED +001376 * FETCH 514 FLAGS=SEEN,DELETED +001377 * FETCH 515 FLAGS=SEEN,DELETED +001378 * FETCH 517 FLAGS=SEEN,DELETED +001379 * FETCH 519 FLAGS=SEEN,DELETED +001380 +OK Folder status updated +001381 * FETCH 523 FLAGS=SEEN,DELETED +001382 * FETCH 524 FLAGS=SEEN,DELETED +001383 * FETCH 525 FLAGS=SEEN,DELETED +001384 * FETCH 527 FLAGS=SEEN,DELETED +001385 * FETCH 529 FLAGS=SEEN,DELETED +001386 +OK Folder status updated +001387 * FETCH 533 FLAGS=SEEN,DELETED +001388 * FETCH 534 FLAGS=SEEN,DELETED +001389 * FETCH 535 FLAGS=SEEN,DELETED +001390 * FETCH 537 FLAGS=SEEN,DELETED +001391 * FETCH 539 FLAGS=SEEN,DELETED +001392 +OK Folder status updated +001393 * FETCH 543 FLAGS=SEEN,DELETED +001394 * FETCH 544 FLAGS=SEEN,DELETED +001395 * FETCH 545 FLAGS=SEEN,DELETED +001396 * FETCH 547 FLAGS=SEEN,DELETED +001397 * FETCH 549 FLAGS=SEEN,DELETED +001398 +OK Folder status updated +001399 * FETCH 553 FLAGS=SEEN,DELETED +001400 * FETCH 554 FLAGS=SEEN,DELETED +001401 * FETCH 555 FLAGS=SEEN,DELETED +001402 * FETCH 557 FLAGS=SEEN,DELETED +001403 * FETCH 559 FLAGS=SEEN,DELETED +001404 +OK Folder status updated +001405 * FETCH 563 FLAGS=SEEN,DELETED +001406 * FETCH 564 FLAGS=SEEN,DELETED +001407 * FETCH 565 FLAGS=SEEN,DELETED +001408 * FETCH 567 FLAGS=SEEN,DELETED +001409 * FETCH 569 FLAGS=SEEN,DELETED +001410 +OK Folder status updated +001411 * FETCH 573 FLAGS=SEEN,DELETED +001412 * FETCH 574 FLAGS=SEEN,DELETED +001413 * FETCH 575 FLAGS=SEEN,DELETED +001414 * FETCH 577 FLAGS=SEEN,DELETED +001415 * FETCH 579 FLAGS=SEEN,DELETED +001416 +OK Folder status updated +001417 * FETCH 583 FLAGS=SEEN,DELETED +001418 * FETCH 584 FLAGS=SEEN,DELETED +001419 * FETCH 585 FLAGS=SEEN,DELETED +001420 * FETCH 587 FLAGS=SEEN,DELETED +001421 * FETCH 589 FLAGS=SEEN,DELETED +001422 +OK Folder status updated +001423 * FETCH 593 FLAGS=SEEN,DELETED +001424 * FETCH 594 FLAGS=SEEN,DELETED +001425 * FETCH 595 FLAGS=SEEN,DELETED +001426 * FETCH 597 FLAGS=SEEN,DELETED +001427 * FETCH 599 FLAGS=SEEN,DELETED +001428 +OK Folder status updated +001429 * FETCH 603 FLAGS=SEEN,DELETED +001430 * FETCH 604 FLAGS=SEEN,DELETED +001431 * FETCH 605 FLAGS=SEEN,DELETED +001432 * FETCH 607 FLAGS=SEEN,DELETED +001433 * FETCH 609 FLAGS=SEEN,DELETED +001434 +OK Folder status updated +001435 * FETCH 613 FLAGS=SEEN,DELETED +001436 * FETCH 614 FLAGS=SEEN,DELETED +001437 * FETCH 615 FLAGS=SEEN,DELETED +001438 * FETCH 617 FLAGS=SEEN,DELETED +001439 * FETCH 619 FLAGS=SEEN,DELETED +001440 +OK Folder status updated +001441 * FETCH 623 FLAGS=SEEN,DELETED +001442 * FETCH 624 FLAGS=SEEN,DELETED +001443 * FETCH 625 FLAGS=SEEN,DELETED +001444 * FETCH 627 FLAGS=SEEN,DELETED +001445 * FETCH 629 FLAGS=SEEN,DELETED +001446 +OK Folder status updated +001447 * FETCH 633 FLAGS=SEEN,DELETED +001448 * FETCH 634 FLAGS=SEEN,DELETED +001449 * FETCH 635 FLAGS=SEEN,DELETED +001450 * FETCH 637 FLAGS=SEEN,DELETED +001451 * FETCH 639 FLAGS=SEEN,DELETED +001452 +OK Folder status updated +001453 * BYE Courier-SMAP server shutting down +001454 +OK LOGOUT completed +001455 +OK SMAP1 LOGIN Ok. +001456 * EXISTS 640 +001457 +OK Folder opened +001458 * SEARCH 1-2 6 8 10-12 16 18 20-22 26 28 30-32 36 38 40-42 46 48 50-52 56 58 60-62 66 68 70-72 76 78 80-82 86 88 90-92 96 98 100-102 106 108 110-112 116 118 120-122 126 128 130-132 136 138 140-142 146 148 150-152 156 158 160-162 166 168 170-172 176 178 180-182 186 188 190-192 196 198 200-202 206 208 210-212 216 218 220-222 226 228 230-232 236 238 240-242 246 248 250-252 256 258 260-262 266 268 270-272 276 278 280-282 286 288 290-292 296 298 300-302 306 308 310-312 316 318 320-322 326 328 330-332 336 +001459 * SEARCH 338 340-342 346 348 350-352 356 358 360-362 366 368 370-372 376 378 380-382 386 388 390-392 396 398 400-402 406 408 410-412 416 418 420-422 426 428 430-432 436 438 440-442 446 448 450-452 456 458 460-462 466 468 470-472 476 478 480-482 486 488 490-492 496 498 500-502 506 508 510-512 516 518 520-522 526 528 530-532 536 538 540-542 546 548 550-552 556 558 560-562 566 568 570-572 576 578 580-582 586 588 590-592 596 598 600-602 606 608 610-612 616 618 620-622 626 628 630-632 636 638 640 +001460 +OK Search completed. +001461 +OK Search completed. +001462 * SEARCH 3-5 7 9 13-15 17 19 23-25 27 29 33-35 37 39 43-45 47 49 53-55 57 59 63-65 67 69 73-75 77 79 83-85 87 89 93-95 97 99 103-105 107 109 113-115 117 119 123-125 127 129 133-135 137 139 143-145 147 149 153-155 157 159 163-165 167 169 173-175 177 179 183-185 187 189 193-195 197 199 203-205 207 209 213-215 217 219 223-225 227 229 233-235 237 239 243-245 247 249 253-255 257 259 263-265 267 269 273-275 277 279 283-285 287 289 293-295 297 299 303-305 307 309 313-315 317 319 323-325 327 329 333-335 337 +001463 * SEARCH 339 343-345 347 349 353-355 357 359 363-365 367 369 373-375 377 379 383-385 387 389 393-395 397 399 403-405 407 409 413-415 417 419 423-425 427 429 433-435 437 439 443-445 447 449 453-455 457 459 463-465 467 469 473-475 477 479 483-485 487 489 493-495 497 499 503-505 507 509 513-515 517 519 523-525 527 529 533-535 537 539 543-545 547 549 553-555 557 559 563-565 567 569 573-575 577 579 583-585 587 589 593-595 597 599 603-605 607 609 613-615 617 619 623-625 627 629 633-635 637 639 +001464 +OK Search completed. +001465 * FETCH 1 FLAGS=DRAFT,SEEN +001466 * FETCH 2 FLAGS=DRAFT,SEEN +001467 * FETCH 3 FLAGS=DRAFT,SEEN,DELETED +001468 * FETCH 4 FLAGS=DRAFT,SEEN,DELETED +001469 * FETCH 5 FLAGS=DRAFT,SEEN,DELETED +001470 * FETCH 6 FLAGS=DRAFT,SEEN +001471 * FETCH 7 FLAGS=DRAFT,SEEN,DELETED +001472 * FETCH 8 FLAGS=DRAFT,SEEN +001473 * FETCH 9 FLAGS=DRAFT,SEEN,DELETED +001474 * FETCH 10 FLAGS=DRAFT,SEEN +001475 +OK Folder status updated +001476 * FETCH 15 FLAGS=SEEN,DELETED +001477 * FETCH 16 FLAGS=SEEN +001478 * FETCH 17 FLAGS=SEEN,DELETED +001479 * FETCH 18 FLAGS=SEEN +001480 * FETCH 19 FLAGS=SEEN,DELETED +001481 * FETCH 20 FLAGS=SEEN +001482 * FETCH 21 FLAGS=SEEN +001483 * FETCH 22 FLAGS=SEEN +001484 * FETCH 23 FLAGS=SEEN,DELETED +001485 * FETCH 24 FLAGS=SEEN,DELETED +001486 * FETCH 25 FLAGS=SEEN,DELETED +001487 * FETCH 30 FLAGS=SEEN +001488 +OK Folder status updated +001489 * SEARCH 1-10 +001490 +OK Search completed. +001491 * SEARCH 1-640 +001492 +OK Search completed. +001493 * EXPUNGE 1-101 +001494 * EXPUNGE 1-101 +001495 * EXPUNGE 1-101 +001496 * EXPUNGE 1-101 +001497 * EXPUNGE 1-101 +001498 * EXPUNGE 1-101 +001499 * EXPUNGE 1-34 +001500 +OK Folder updated +001501 +OK INTERNALDATE set +001502 > Go ahead +001503 * ADD UID +001504 +OK Message saved +001505 * EXISTS 1 +001506 +OK Folder updated +001507 +OK INTERNALDATE set +001508 > Go ahead +001509 * ADD UID +001510 +OK Message saved +001511 * EXISTS 2 +001512 +OK Folder updated +001513 * SEARCH 1-2 +001514 +OK Search completed. +001515 * SEARCH 2 +001516 +OK Search completed. +001517 * SEARCH 2 +001518 +OK Search completed. +001519 * SEARCH 2 +001520 +OK Search completed. +001521 * SEARCH 1-2 +001522 +OK Search completed. +001523 * SEARCH 2 +001524 +OK Search completed. +001525 * SEARCH 1 +001526 +OK Search completed. +001527 * SEARCH 2 +001528 +OK Search completed. +001529 * SEARCH 1 +001530 +OK Search completed. +001531 * SEARCH 2 +001532 +OK Search completed. +001533 -ERR --error-- +001534 * BYE Courier-SMAP server shutting down +001535 +OK LOGOUT completed +001536 +OK SMAP1 LOGIN Ok. +001537 * EXISTS 1 +001538 +OK Folder opened +001539 * COPY NEWUID +001540 +OK Messages copied. +001541 * EXISTS 2 +001542 +OK Folder updated +001543 * EXISTS 2 +001544 +OK Folder opened +001545 +OK Folder created +001546 * COPY NEWUID +001547 +OK Messages copied. +001548 +OK Folder created +001549 * COPY NEWUID +001550 * EXPUNGE 2 +001551 +OK Messages copied. +001552 * BYE Courier-SMAP server shutting down +001553 +OK LOGOUT completed +001554 20C +001555 0 0 +001556 79 1 +001557 79 1 +001558 79 1 +001559 +OK SMAP1 LOGIN Ok. +001560 * EXISTS 1 +001561 +OK Folder opened +001562 * BYE Courier-SMAP server shutting down +001563 +OK LOGOUT completed +001564 20C +001565 0 0 +001566 79 1 +001567 79 1 +001568 79 1 +001569 +OK SMAP1 LOGIN Ok. +001570 * EXISTS 1 +001571 +OK Folder opened +001572 * SNAPSHOT -SNAPSHOT- +001573 +OK Folder updated +001574 +OK Folder updated +001575 * FETCH 1 FLAGS=REPLIED +001576 +OK Folder status updated +001577 * SNAPSHOT -SNAPSHOT- +001578 +OK Folder updated +001579 +OK Folder updated +001580 * FETCH 1 FLAGS=REPLIED +001581 +OK Folder status updated +001582 +OK Folder updated +001583 +OK Folder updated +001584 * FETCH 1 FLAGS=DELETED +001585 +OK Folder status updated +001586 * EXPUNGE 1 +001587 +OK Folder updated +001588 +OK Folder updated +001589 * SNAPSHOT -SNAPSHOT- +001590 +OK Folder updated +001591 +OK Folder updated +001592 +OK Folder updated +001593 +OK Folder updated +001594 * BYE Courier-SMAP server shutting down +001595 +OK LOGOUT completed +001596 2 +001597 +OK SMAP1 LOGIN Ok. +001598 +OK Folder deleted +001599 * EXISTS 1 +001600 +OK Folder opened +001601 * SNAPSHOT -SNAPSHOT- +001602 +OK Folder updated +001603 +OK Folder closed +001604 * EXISTS 1 +001605 +OK Folder opened +001606 * SNAPSHOT -SNAPSHOT- +001607 +OK Folder updated +001608 +OK Folder closed +001609 * EXISTS 1 +001610 +OK Folder opened +001611 * SNAPSHOT -SNAPSHOT- +001612 +OK Folder updated +001613 +OK Folder closed +001614 * EXISTS 1 +001615 +OK Folder opened +001616 * SNAPSHOT -SNAPSHOT- +001617 +OK Folder updated +001618 +OK Folder closed +001619 * EXISTS 1 +001620 +OK Folder opened +001621 * SNAPSHOT -SNAPSHOT- +001622 +OK Folder updated +001623 +OK Folder closed +001624 * EXISTS 1 +001625 +OK Folder opened +001626 * SNAPSHOT -SNAPSHOT- +001627 +OK Folder updated +001628 +OK Folder closed +001629 * EXISTS 1 +001630 +OK Folder opened +001631 * SNAPSHOT -SNAPSHOT- +001632 +OK Folder updated +001633 +OK Folder closed +001634 * EXISTS 1 +001635 +OK Folder opened +001636 * SNAPSHOT -SNAPSHOT- +001637 +OK Folder updated +001638 +OK Folder closed +001639 * EXISTS 1 +001640 +OK Folder opened +001641 * SNAPSHOT -SNAPSHOT- +001642 +OK Folder updated +001643 +OK Folder closed +001644 * EXISTS 1 +001645 +OK Folder opened +001646 * SNAPSHOT -SNAPSHOT- +001647 +OK Folder updated +001648 +OK Folder closed +001649 * EXISTS 1 +001650 +OK Folder opened +001651 * SNAPSHOT -SNAPSHOT- +001652 +OK Folder updated +001653 +OK Folder closed +001654 * BYE Courier-SMAP server shutting down +001655 +OK LOGOUT completed +001656 11 +001657 +OK SMAP1 LOGIN Ok. +001658 * EXISTS 1 +001659 +OK Folder opened +001660 +OK Folder closed +001661 * BYE Courier-SMAP server shutting down +001662 +OK LOGOUT completed +001663 10 +001664 +OK SMAP1 LOGIN Ok. +001665 * EXISTS 1 +001666 +OK Folder opened +001667 {.0} FETCH 1 HEADERS +001668 Received: by localhost +001669 Subject: test message +001670 From: nobody@localhost +001671 Cc: nobody@localhost +001672 Mime-Version: 1.0 +001673 Content-Type: multipart/mixed; boundary=abc +001674 . +001675 * FETCH 1 FLAGS=SEEN +001676 +OK Message retrieved. +001677 {.0} FETCH 1 RAWHEADERS +001678 Received: by localhost +001679 Subject: test message +001680 From: nobody@localhost +001681 Cc: nobody@localhost +001682 Mime-Version: 1.0 +001683 Content-Type: multipart/mixed; +001684 boundary=abc +001685 . +001686 +OK Message retrieved. +001687 {.156} FETCH 1 LINES=9 SIZE=144 "MIME.ID=" +001688 Received: by localhost +001689 Subject: test message +001690 From: nobody@localhost +001691 Cc: nobody@localhost +001692 Mime-Version: 1.0 +001693 Content-Type: multipart/mixed; boundary=abc +001694 . +001695 {.61} FETCH 1 LINES=0 SIZE=53 "MIME.ID=1.1" "MIME.PARENT=" +001696 Content-Type: text/plain; charset=iso-8859-1 +001697 X-Comment: foo +001698 . +001699 +OK Message retrieved. +001700 {.156} FETCH 1 LINES=9 SIZE=144 "MIME.ID=" +001701 Content-Type: multipart/mixed; boundary=abc +001702 . +001703 {.61} FETCH 1 LINES=0 SIZE=53 "MIME.ID=1.1" "MIME.PARENT=" +001704 Content-Type: text/plain; charset=iso-8859-1 +001705 . +001706 +OK Message retrieved. +001707 {.0} FETCH 1 HEADERS +001708 Content-Type: multipart/mixed; boundary=abc +001709 . +001710 +OK Message retrieved. +001711 {.0} FETCH 1 HEADERS +001712 Subject: test message +001713 From: nobody@localhost +001714 Cc: nobody@localhost +001715 . +001716 +OK Message retrieved. +001717 {.0} FETCH 1 HEADERS +001718 Received: by localhost +001719 Subject: test message +001720 From: nobody@localhost +001721 Cc: nobody@localhost +001722 . +001723 +OK Message retrieved. +001724 {.0} FETCH 1 HEADERS +001725 Received: by localhost +001726 Subject: test message +001727 From: nobody@localhost +001728 Cc: nobody@localhost +001729 . +001730 +OK Message retrieved. +001731 {.0} FETCH 1 HEADERS +001732 Subject: test message +001733 From: nobody@localhost +001734 Cc: nobody@localhost +001735 Mime-Version: 1.0 +001736 Content-Type: multipart/mixed; boundary=abc +001737 . +001738 +OK Message retrieved. +001739 {.156} FETCH 1 LINES=9 SIZE=144 "MIME.ID=" +001740 Subject: test message +001741 From: nobody@localhost +001742 Cc: nobody@localhost +001743 Mime-Version: 1.0 +001744 Content-Type: multipart/mixed; boundary=abc +001745 . +001746 {.61} FETCH 1 LINES=0 SIZE=53 "MIME.ID=1.1" "MIME.PARENT=" +001747 Content-Type: text/plain; charset=iso-8859-1 +001748 . +001749 +OK Message retrieved. +001750 * BYE Courier-SMAP server shutting down +001751 +OK LOGOUT completed +001752 +OK SMAP1 LOGIN Ok. +001753 * EXISTS 1 +001754 +OK Folder opened +001755 +OK Folder created +001756 * COPY NEWUID +001757 +OK Messages copied. +001758 * EXISTS 1 +001759 +OK Folder opened +001760 * FETCH 1 FLAGS=SEEN,DELETED +001761 * FETCH 1 "KEYWORDS=-Label1" +001762 +OK Folder status updated +001763 +OK Will add to this folder +001764 +OK FLAGS set +001765 +OK KEYWORDS set +001766 > Go ahead +001767 * ADD UID +001768 +OK Message saved +001769 * EXPUNGE 1 +001770 * EXISTS 1 +001771 +OK Folder updated +001772 * FETCH 1 FLAGS=SEEN "KEYWORDS=-Label2" +001773 +OK Message retrieved. +001774 * COPY NEWUID +001775 +OK Messages copied. +001776 * EXISTS 2 +001777 +OK Folder opened +001778 * FETCH 1 FLAGS=SEEN "KEYWORDS=" +001779 * FETCH 2 FLAGS=SEEN "KEYWORDS=-Label2" +001780 +OK Message retrieved. +001781 * COPY NEWUID +001782 * EXPUNGE 2 +001783 +OK Messages copied. +001784 * EXISTS 2 +001785 +OK Folder opened +001786 * FETCH 1 FLAGS=SEEN "KEYWORDS=-Label2" +001787 * FETCH 2 FLAGS=SEEN "KEYWORDS=-Label2" +001788 +OK Message retrieved. +001789 * SNAPSHOT -SNAPSHOT- +001790 +OK Folder updated +001791 * EXPUNGE 1 +001792 +OK Folder updated +001793 * FETCH 1 "KEYWORDS=-Label3" +001794 +OK Folder status updated +001795 * BYE Courier-SMAP server shutting down +001796 +OK LOGOUT completed +001797 +OK SMAP1 LOGIN Ok. +001798 * SNAPSHOTEXISTS -SNAPSHOT- +001799 * EXPUNGE 1 +001800 * FETCH 1 FLAGS=SEEN "KEYWORDS=-Label3" +001801 +OK Folder updated +001802 * COPY NEWUID +001803 +OK Messages copied. +001804 * EXISTS 2 +001805 +OK Folder updated +001806 * COPY NEWUID +001807 * COPY NEWUID +001808 +OK Messages copied. +001809 * EXISTS 4 +001810 +OK Folder updated +001811 * FETCH 1 FLAGS=SEEN "KEYWORDS=-Label3" +001812 * FETCH 2 FLAGS=SEEN "KEYWORDS=-Label3" +001813 * FETCH 3 FLAGS=SEEN "KEYWORDS=-Label3" +001814 * FETCH 4 FLAGS=SEEN "KEYWORDS=-Label3" +001815 +OK Message retrieved. +001816 * FETCH 3 "KEYWORDS=-Label3" +001817 * FETCH 4 "KEYWORDS=-Label3" +001818 +OK Folder status updated +001819 * FETCH 3 "KEYWORDS=" +001820 * FETCH 4 "KEYWORDS=" +001821 +OK Folder status updated +001822 * SEARCH 1-4 +001823 +OK Search completed. +001824 +OK Search completed. +001825 * SEARCH 1-2 +001826 +OK Search completed. +001827 * SEARCH 3-4 +001828 +OK Search completed. +001829 * BYE Courier-SMAP server shutting down +001830 +OK LOGOUT completed +001831 +OK SMAP1 LOGIN Ok. +001832 * EXISTS 1 +001833 +OK Folder opened +001834 * FETCH 1 "KEYWORDS=Junk*Junk" +001835 +OK Folder status updated +001836 * BYE Courier-SMAP server shutting down +001837 +OK LOGOUT completed +001838 * PREAUTH Ready.
+001839 * FLAGS (Junk_Junk \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001840 * OK [PERMANENTFLAGS (Junk_Junk \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001841 * 1 EXISTS
+001842 * 0 RECENT
+001843 * OK [UIDVALIDITY] Ok
+001844 * OK [MYRIGHTS "acdilrsw"] ACL
+001845 a1 OK [READ-WRITE] Ok
+001846 * 1 FETCH (FLAGS (\Seen Junk_Junk))
+001847 a2 OK FETCH completed.
+001848 * FLAGS (Abra,Cadabra Junk_Junk \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001849 * OK [PERMANENTFLAGS (Abra,Cadabra Junk_Junk \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001850 * 1 FETCH (FLAGS (\Seen Abra,Cadabra))
+001851 * FLAGS (Abra,Cadabra \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001852 * OK [PERMANENTFLAGS (Abra,Cadabra \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001853 a3 OK STORE completed.
+001854 * BYE Courier-IMAP server shutting down
+001855 a3 OK LOGOUT completed
+001856 +OK SMAP1 LOGIN Ok. +001857 * EXISTS 1 +001858 +OK Folder opened +001859 * FETCH 1 FLAGS=SEEN "KEYWORDS=Abra_Cadabra" +001860 +OK Message retrieved. +001861 * COPY NEWUID +001862 +OK Messages copied. +001863 * EXISTS 2 +001864 +OK Folder updated +001865 * FETCH 2 FLAGS=MARKED,SEEN +001866 +OK Folder status updated +001867 * SEARCH 1 +001868 +OK Search completed. +001869 * SEARCH 2 +001870 +OK Search completed. +001871 * SEARCH 1-2 +001872 +OK Search completed. +001873 * BYE Courier-SMAP server shutting down +001874 +OK LOGOUT completed +001875 +OK SMAP1 LOGIN Ok. +001876 * LIST "a" "a" DIRECTORY +001876 * LIST "b" "b" DIRECTORY +001876 +OK LIST completed +001877 * LIST "confmdtest2" "confmdtest2" DIRECTORY +001877 +OK LIST completed +001878 * LIST "confmdtest3" "confmdtest3" FOLDER +001878 +OK LIST completed +001879 * LIST "x" "x" FOLDER +001879 +OK LIST completed +001880 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001881 +OK ACLs retrieved +001882 * GETACL "owner" "aceilr" "administrators" "aceilrstwx" +001883 +OK Updated ACLs +001884 * GETACL "owner" "aceilrstw" "administrators" "aceilrstwx" +001885 +OK Updated ACLs +001886 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001887 +OK Updated ACLs +001888 * ACLMINIMUM owner al c e i p r s t w x +001889 -ERR --error-- +001890 * ACLMINIMUM owner al c e i p r s t w x +001891 -ERR --error-- +001892 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=testuser1" "ace" +001893 +OK Updated ACLs +001894 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=testuser1" "acex" +001895 +OK Updated ACLs +001896 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=testuser1" "ace" +001897 +OK Updated ACLs +001898 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001899 +OK Updated ACLs +001900 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=testuser1" "ace" +001901 +OK Updated ACLs +001902 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=testuser1" "ace" +001903 +OK Updated ACLs +001904 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001905 +OK Updated ACLs +001906 * ACLMINIMUM owner al c e i p r s t w x +001907 -ERR --error-- +001908 * ACL "aceilrstwx" +001909 +OK ACLs retrieved +001910 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=confmdtest" "aceilrstwx" +001911 +OK ACLs retrieved +001912 * ACL "aceilrstwx" +001913 +OK ACLs retrieved +001914 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=confmdtest" "aceilrstwx" +001915 +OK ACLs retrieved +001916 * ACL "aceilrstwx" +001917 +OK ACLs retrieved +001918 * ACL "l" +001919 +OK ACLs retrieved +001920 * ACL "l" +001921 +OK ACLs retrieved +001922 * EXISTS 0 +001923 +OK Folder opened +001924 +OK KEYWORDS set +001925 > Go ahead +001926 * ADD UID +001927 +OK Message saved +001928 * EXISTS 1 +001929 +OK Folder updated +001930 * FETCH 1 FLAGS=MARKED,SEEN,DELETED "KEYWORDS=keyword1" +001931 +OK Message retrieved. +001932 * GETACL "owner" "aceilrstx" "administrators" "aceilrstwx" +001933 +OK Updated ACLs +001934 +OK FLAGS set +001935 > Go ahead +001936 * ADD UID +001937 +OK Message saved +001938 * EXISTS 2 +001939 +OK Folder updated +001940 * FETCH 2 FLAGS=SEEN,DELETED "KEYWORDS=" +001941 +OK Message retrieved. +001942 * GETACL "owner" "aceilrtwx" "administrators" "aceilrstwx" +001943 +OK Updated ACLs +001944 +OK KEYWORDS set +001945 > Go ahead +001946 * ADD UID +001947 +OK Message saved +001948 * EXISTS 3 +001949 +OK Folder updated +001950 * FETCH 3 FLAGS=MARKED,DELETED "KEYWORDS=keyword1" +001951 +OK Message retrieved. +001952 * GETACL "owner" "aceilrswx" "administrators" "aceilrstwx" +001953 +OK Updated ACLs +001954 +OK KEYWORDS set +001955 > Go ahead +001956 * ADD UID +001957 +OK Message saved +001958 * EXISTS 4 +001959 +OK Folder updated +001960 * FETCH 4 FLAGS=MARKED,SEEN "KEYWORDS=keyword1" +001961 +OK Message retrieved. +001962 * GETACL "owner" "acelrstwx" "administrators" "aceilrstwx" +001963 +OK Updated ACLs +001964 -ERR --error-- +001965 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001966 +OK Updated ACLs +001967 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=confmdtest" "aceirstwx" +001968 +OK Updated ACLs +001969 -ERR --error-- +001970 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "user=confmdtest" "aceilrstwx" +001971 +OK Updated ACLs +001972 * STATUS EXISTS=0 UNSEEN=0 +001973 +OK Folder status retrieved +001974 -ERR --error-- +001975 +OK Folder created +001976 * GETACL "owner" "aceilrstw" "administrators" "aceilrstwx" +001977 +OK Updated ACLs +001978 -ERR --error-- +001979 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001980 +OK Updated ACLs +001981 +OK Folder deleted +001982 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "-owner" "r" +001983 +OK Updated ACLs +001984 -ERR --error-- +001985 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001986 +OK Updated ACLs +001987 * EXISTS 4 +001988 +OK Folder opened +001989 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" "-owner" "i" +001990 +OK Updated ACLs +001991 -ERR --error-- +001992 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +001993 +OK Updated ACLs +001994 * COPY NEWUID +001995 * COPY NEWUID +001996 * COPY NEWUID +001997 * COPY NEWUID +001998 +OK Messages copied. +001999 +OK Folder closed +002000 +OK Folder created +002001 +OK Folder created +002002 * GETACL "owner" "aeilrstwx" "administrators" "aceilrstwx" +002003 +OK Updated ACLs +002004 -ERR --error-- +002005 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +002006 +OK Updated ACLs +002007 +OK Folder renamed. +002008 * GETACL "owner" "aceilrstw" "administrators" "aceilrstwx" +002009 +OK Updated ACLs +002010 -ERR --error-- +002011 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +002012 +OK Updated ACLs +002013 +OK Folder renamed. +002014 * EXISTS 8 +002015 +OK Folder opened +002016 * FETCH 1 FLAGS=MARKED,SEEN,DELETED "KEYWORDS=keyword1" +002017 +OK Message retrieved. +002018 * GETACL "owner" "aceilrstx" "administrators" "aceilrstwx" +002019 +OK Updated ACLs +002020 * FETCH 1 FLAGS=MARKED,SEEN +002021 * FETCH 1 "KEYWORDS=keyword1" +002022 +OK Folder status updated +002023 * GETACL "owner" "aceilrswx" "administrators" "aceilrstwx" +002024 +OK Updated ACLs +002025 * FETCH 1 FLAGS=MARKED,SEEN +002026 * FETCH 1 "KEYWORDS=keyword2" +002027 +OK Folder status updated +002028 * GETACL "owner" "aceilrtwx" "administrators" "aceilrstwx" +002029 +OK Updated ACLs +002030 * FETCH 1 FLAGS=SEEN +002031 +OK Folder status updated +002032 * GETACL "owner" "acilrstwx" "administrators" "aceilrstwx" +002033 +OK Updated ACLs +002034 * FETCH 1 FLAGS=DELETED +002035 +OK Folder status updated +002036 -ERR --error-- +002037 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +002038 +OK Updated ACLs +002039 * EXPUNGE 1-3 5-7 +002040 +OK Folder updated +002041 * GETACL "owner" "aceilrswx" "administrators" "aceilrstwx" +002042 +OK Updated ACLs +002043 -ERR --error-- +002044 * GETACL "owner" "aceilrstwx" "administrators" "aceilrstwx" +002045 +OK Updated ACLs +002046 * EXPUNGE 1 +002047 +OK Folder updated +002048 * BYE Courier-SMAP server shutting down +002049 +OK LOGOUT completed +002050 +OK SMAP1 LOGIN Ok. +002051 * EXISTS 256 +002052 +OK Folder opened +002053 * EXPUNGE 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44 46 48 50 52 54 56 58 60 62 64 66 68 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 104 106 108 110 112 114 116 118 120 122 124 126 128 130 132 134 136 138 140 142 144 146 148 150 152 154 156 158 160 162 164 166 168 170 172 174 176 178 180 182 184 186 188 190 192 194 196 198 200 202 +002054 * EXPUNGE 103 105 107 109 111 113 115 117 119 121 123 125 127 129 131 133 135 137 139 141 143 145 147 149 151 153 155 +002055 +OK Folder updated +002056 * BYE Courier-SMAP server shutting down +002057 +OK LOGOUT completed diff --git a/imap/storeinfo.c b/imap/storeinfo.c new file mode 100644 index 0000000..de885e9 --- /dev/null +++ b/imap/storeinfo.c @@ -0,0 +1,495 @@ +/* +** Copyright 1998 - 2003 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#if HAVE_CONFIG_H +#include "config.h" +#endif +#if HAVE_UNISTD_H +#include <unistd.h> +#endif +#include <errno.h> +#include "imaptoken.h" +#include "imapscanclient.h" +#include "imapwrite.h" +#include "storeinfo.h" +#include "maildir/maildirquota.h" +#include "maildir/maildirmisc.h" +#include "maildir/maildircreate.h" +#include "maildir/maildiraclt.h" +#include "outbox.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <fcntl.h> +#include <sys/stat.h> +#if HAVE_UTIME_H +#include <utime.h> +#endif +#if TIME_WITH_SYS_TIME +#include <sys/time.h> +#include <time.h> +#else +#if HAVE_SYS_TIME_H +#include <sys/time.h> +#else +#include <time.h> +#endif +#endif + + +#if SMAP +extern int smapflag; +#endif + +extern char *get_reflagged_filename(const char *fn, struct imapflags *newfl); +extern int is_trash(const char *); +extern int get_flagname(const char *, struct imapflags *); +extern int get_flagsAndKeywords(struct imapflags *flags, + struct libmail_kwMessage **kwPtr); +extern void get_message_flags( struct imapscanmessageinfo *, + char *, struct imapflags *); +extern int reflag_filename(struct imapscanmessageinfo *, struct imapflags *, + int); +extern void fetchflags(unsigned long); +extern void fetchflags_byuid(unsigned long); +extern FILE *maildir_mkfilename(const char *, struct imapflags *, + unsigned long, char **, char **); +extern int acl_flags_adjust(const char *access_rights, + struct imapflags *flags); + +extern struct imapscaninfo current_maildir_info; +extern char *current_mailbox; +extern char *current_mailbox_acl; +extern int fastkeywords(); + +int storeinfo_init(struct storeinfo *si) +{ +struct imaptoken *t=currenttoken(); +const char *p; + + if (t->tokentype != IT_ATOM) return (-1); + si->plusminus=0; + si->silent=0; + + p=t->tokenbuf; + if (*p == '+' || *p == '-') + si->plusminus= *p++; + if (strncmp(p, "FLAGS", 5)) return (-1); + p += 5; + if (*p) + { + if (strcmp(p, ".SILENT")) return (-1); + si->silent=1; + } + + memset(&si->flags, 0, sizeof(si->flags)); + + if ((si->keywords=libmail_kwmCreate()) == NULL) + write_error_exit(0); + + t=nexttoken_noparseliteral(); + if (t->tokentype == IT_LPAREN) + { + if (get_flagsAndKeywords(&si->flags, &si->keywords)) + { + libmail_kwmDestroy(si->keywords); + si->keywords=NULL; + return (-1); + } + nexttoken(); + } + else if (t->tokentype == IT_NIL) + nexttoken(); + else if (t->tokentype == IT_ATOM) + { + if (get_flagname(t->tokenbuf, &si->flags)) + libmail_kwmSetName(current_maildir_info + .keywordList, + si->keywords, + t->tokenbuf); + nexttoken(); + } + return (0); +} + +int do_store(unsigned long n, int byuid, void *voidptr) +{ +struct storeinfo *si=(struct storeinfo *)voidptr; +int fd; + struct imapflags new_flags, old_flags; +int changedKeywords; +struct libmail_kwMessageEntry *kme; +int kwAllowed=1; + + --n; + fd=imapscan_openfile(current_mailbox, ¤t_maildir_info, n); + if (fd < 0) return (0); + + changedKeywords=0; + get_message_flags(current_maildir_info.msgs+n, 0, &new_flags); + + old_flags=new_flags; + + if (current_mailbox_acl) + { + if (strchr(current_mailbox_acl, ACL_WRITE[0]) == NULL) + kwAllowed=0; + } + + + if (si->plusminus == '+') + { + if (si->flags.drafts) new_flags.drafts=1; + if (si->flags.seen) new_flags.seen=1; + if (si->flags.answered) new_flags.answered=1; + if (si->flags.deleted) new_flags.deleted=1; + if (si->flags.flagged) new_flags.flagged=1; + + for (kme=si->keywords ? si->keywords->firstEntry:NULL; + kme; kme=kme->next) + { + int rc; + + if (!kwAllowed) + { + current_maildir_info.msgs[n].changedflags=1; + continue; + } + + imapscan_createKeyword(¤t_maildir_info, n); + + if ((rc=libmail_kwmSet(current_maildir_info.msgs[n] + .keywordMsg, + kme->libmail_keywordEntryPtr)) + < 0) + { + write_error_exit(0); + return 0; + } + + if (rc == 0) + { + if (fastkeywords()) + changedKeywords=1; + current_maildir_info.msgs[n].changedflags=1; + } + } + } + else if (si->plusminus == '-') + { + if (si->flags.drafts) new_flags.drafts=0; + if (si->flags.seen) new_flags.seen=0; + if (si->flags.answered) new_flags.answered=0; + if (si->flags.deleted) new_flags.deleted=0; + if (si->flags.flagged) new_flags.flagged=0; + + if (current_maildir_info.msgs[n].keywordMsg && kwAllowed) + for (kme=si->keywords ? + si->keywords->firstEntry:NULL; + kme; kme=kme->next) + { + if (!kwAllowed) + { + current_maildir_info.msgs[n] + .changedflags=1; + continue; + } + + if (libmail_kwmClear(current_maildir_info.msgs[n] + .keywordMsg, + kme->libmail_keywordEntryPtr)==0) + { + if (fastkeywords()) + changedKeywords=1; + current_maildir_info.msgs[n] + .changedflags=1; + } + } + + if (current_maildir_info.msgs[n].keywordMsg && + !current_maildir_info.msgs[n].keywordMsg->firstEntry) + { + libmail_kwmDestroy(current_maildir_info.msgs[n] + .keywordMsg); + current_maildir_info.msgs[n].keywordMsg=NULL; + } + } + else + { + struct libmail_kwMessage *kw; + + new_flags=si->flags; + + kw=current_maildir_info.msgs[n].keywordMsg; + + if (kw && kw->firstEntry == NULL) + kw=NULL; + + if (si->keywords && si->keywords->firstEntry == NULL) + si->keywords=NULL; + + if ((si->keywords && !kw) || + (!si->keywords && kw) || + (si->keywords && kw && libmail_kwmCmp(si->keywords, kw))) + { + if (kwAllowed) + { + kw=current_maildir_info.msgs[n].keywordMsg; + + if (kw) + libmail_kwmDestroy(kw); + + current_maildir_info.msgs[n].keywordMsg=NULL; + + if (si->keywords && si->keywords->firstEntry) + { + struct libmail_kwMessageEntry *kme; + + kw=imapscan_createKeyword(¤t_maildir_info, + n); + + for (kme=si->keywords->lastEntry; kme; + kme=kme->prev) + if (libmail_kwmSet(kw, + kme->libmail_keywordEntryPtr) + < 0) + write_error_exit(0); + current_maildir_info.msgs[n].keywordMsg=kw; + } + } + + changedKeywords=1; + } + } + + if (current_mailbox_acl) + { + if (strchr(current_mailbox_acl, ACL_WRITE[0]) == NULL) + { + new_flags.drafts=old_flags.drafts; + new_flags.answered=old_flags.answered; + new_flags.flagged=old_flags.flagged; + } + + if (strchr(current_mailbox_acl, ACL_SEEN[0]) == NULL) + { + new_flags.seen=old_flags.seen; + } + + if (strchr(current_mailbox_acl, ACL_DELETEMSGS[0]) + == NULL) + { + new_flags.deleted=old_flags.deleted; + } + } + + if (changedKeywords) + { + current_maildir_info.msgs[n].changedflags=1; + if (imapscan_updateKeywords(current_maildir_info.msgs[n] + .filename, + current_maildir_info.msgs[n] + .keywordMsg)) + { + close(fd); + return -1; + } + } + + if (reflag_filename(current_maildir_info.msgs+n, &new_flags, fd)) + { + close(fd); + return (-1); + } + close(fd); + if (si->silent) + current_maildir_info.msgs[n].changedflags=0; + else + { +#if SMAP + /* SMAP flag notification is handled elsewhere */ + + if (!smapflag) +#endif + { + if (byuid) + fetchflags_byuid(n); + else + fetchflags(n); + } + } + + return (0); +} + +static int copy_message(int fd, + struct do_copy_info *cpy_info, + struct imapflags *flags, + struct libmail_kwMessage *keywords, + unsigned long old_uid) +{ +char *tmpname; +char *newname; +FILE *fp; +struct stat stat_buf; +char buf[BUFSIZ]; +struct uidplus_info *new_uidplus_info; + + if (fstat(fd, &stat_buf) < 0 + || (new_uidplus_info=(struct uidplus_info *) + malloc(sizeof(struct uidplus_info))) == NULL) + { + return (-1); + } + memset(new_uidplus_info, 0, sizeof(*new_uidplus_info)); + + fp=maildir_mkfilename(cpy_info->mailbox, flags, stat_buf.st_size, + &tmpname, &newname); + + if (!fp) + { + free(new_uidplus_info); + return (-1); + } + + while (stat_buf.st_size) + { + int n=sizeof(buf); + + if (n > stat_buf.st_size) + n=stat_buf.st_size; + + n=read(fd, buf, n); + + if (n <= 0 || fwrite(buf, 1, n, fp) != n) + { + fprintf(stderr, + "ERR: error copying a message, user=%s, errno=%d\n", + getenv("AUTHENTICATED"), errno); + + fclose(fp); + unlink(tmpname); + free(tmpname); + free(newname); + free(new_uidplus_info); + return (-1); + } + stat_buf.st_size -= n; + } + + if (fflush(fp) || ferror(fp)) + { + fclose(fp); + free(tmpname); + free(newname); + free(new_uidplus_info); + return (-1); + } + fclose(fp); + + new_uidplus_info->mtime = stat_buf.st_mtime; + + if (check_outbox(tmpname, cpy_info->mailbox)) + { + unlink(tmpname); + free(tmpname); + free(newname); + free(new_uidplus_info); + return (-1); + } + + if (keywords && keywords->firstEntry && + maildir_kwSave(cpy_info->mailbox, + strrchr(newname, '/')+1, + keywords, + &new_uidplus_info->tmpkeywords, + &new_uidplus_info->newkeywords, 0)) + { + unlink(tmpname); + free(tmpname); + free(newname); + free(new_uidplus_info); + perror("maildir_kwSave"); + return (-1); + } + + new_uidplus_info->tmpfilename=tmpname; + new_uidplus_info->curfilename=newname; + new_uidplus_info->next=NULL; + new_uidplus_info->old_uid=old_uid; + *cpy_info->uidplus_tail=new_uidplus_info; + cpy_info->uidplus_tail=&new_uidplus_info->next; + return (0); +} + +int do_copy_message(unsigned long n, int byuid, void *voidptr) +{ + struct do_copy_info *cpy_info=(struct do_copy_info *)voidptr; + int fd; + struct imapflags new_flags; + + --n; + fd=imapscan_openfile(current_mailbox, ¤t_maildir_info, n); + if (fd < 0) return (0); + get_message_flags(current_maildir_info.msgs+n, 0, &new_flags); + + if (copy_message(fd, cpy_info, &new_flags, + + acl_flags_adjust(cpy_info->acls, + &new_flags) ? NULL + : current_maildir_info.msgs[n].keywordMsg, + current_maildir_info.msgs[n].uid)) + { + close(fd); + return (-1); + } + close(fd); + current_maildir_info.msgs[n].copiedflag=1; + return (0); +} + +int do_copy_quota_calc(unsigned long n, int byuid, void *voidptr) +{ +struct copyquotainfo *info=(struct copyquotainfo *)voidptr; +const char *filename; +unsigned long nbytes; +struct stat stat_buf; +int fd; +struct imapflags flags; +char *ff; + + --n; + + fd=imapscan_openfile(current_mailbox, ¤t_maildir_info, n); + if (fd < 0) return (0); + filename=current_maildir_info.msgs[n].filename; + + get_message_flags(¤t_maildir_info.msgs[n], NULL, &flags); + + (void)acl_flags_adjust(info->acls, &flags); + + ff=get_reflagged_filename(filename, &flags); + + if (maildirquota_countfile(ff)) + { + if (maildir_parsequota(ff, &nbytes)) + { + if (fstat(fd, &stat_buf) < 0) + { + close(fd); + free(ff); + return (0); + } + nbytes=stat_buf.st_size; + } + info->nbytes += nbytes; + info->nfiles += 1; + } + close(fd); + free(ff); + return (0); +} diff --git a/imap/storeinfo.h b/imap/storeinfo.h new file mode 100644 index 0000000..dd1ba90 --- /dev/null +++ b/imap/storeinfo.h @@ -0,0 +1,49 @@ +#ifndef storeinfo_h +#define storeinfo_h + +/* +** Copyright 1998 - 2010 Double Precision, Inc. +** See COPYING for distribution information. +*/ + +#include "imaptoken.h" +#include "numlib/numlib.h" + + +struct storeinfo { + int plusminus; + int silent; + struct imapflags flags; + struct libmail_kwMessage *keywords; + } ; + +int storeinfo_init(struct storeinfo *); +int do_store(unsigned long, int, void *); + +int do_copy_message(unsigned long, int, void *); +int do_copy_quota_calc(unsigned long, int, void *); + +struct uidplus_info; + +struct do_copy_info { + const char *mailbox; + const char *acls; + + struct uidplus_info *uidplus_list; + struct uidplus_info **uidplus_tail; +}; + +/* +** maildir quota calculation for copying messages. +*/ + +struct copyquotainfo { + char *destmailbox; + int64_t nbytes; + int nfiles; + + const char *acls; + + } ; + +#endif diff --git a/imap/system-auth.authpam b/imap/system-auth.authpam new file mode 100644 index 0000000..bc915e8 --- /dev/null +++ b/imap/system-auth.authpam @@ -0,0 +1,13 @@ +#%PAM-1.0 +# +# +# Copyright 1998-2001 Double Precision, Inc. See COPYING for +# distribution information. +# +# This is a sample authpam configuration file that uses pam_stack +# (circa linux-pam 0.72). + +auth required pam_nologin.so +auth required pam_stack.so service=system-auth +account required pam_stack.so service=system-auth +session required pam_stack.so service=system-auth diff --git a/imap/system-auth2.authpam b/imap/system-auth2.authpam new file mode 100644 index 0000000..d0d7c09 --- /dev/null +++ b/imap/system-auth2.authpam @@ -0,0 +1,13 @@ +#%PAM-1.0 +# +# +# Copyright 1998-2007 Double Precision, Inc. See COPYING for +# distribution information. +# +# This is a sample authpam configuration file that uses system-auth +# (required starting with pam 0.99). + +auth required pam_nologin.so +auth include system-auth +account include system-auth +session include system-auth diff --git a/imap/testsuite b/imap/testsuite new file mode 100755 index 0000000..53671c0 --- /dev/null +++ b/imap/testsuite @@ -0,0 +1,1084 @@ +#!/bin/sh + +# Maintainer's sanity check + +OPTIONS="" +export OPTIONS +TZ=EST5EDT +export TZ + +echo '****************************' >&2 +echo '* Sanity check in progress *' >&2 +echo '****************************' >&2 +#test ! -d confmdtest || find confmdtest -exec chmod u+rwx {} \; +#test ! -d confmdtest2 || find confmdtest2 -exec chmod u+rwx {} \; +test ! -d confmdtest || chmod -R u+rwx confmdtest +test ! -d confmdtest2 || chmod -R u+rwx confmdtest2 +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 + +cat >confmdtest/cur/msg1:2,S <<EOF || exit 1 +From: John <john@example.com> +To: Steve <steve@example.com>, Tom <tom@example.com> +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="b1" +Subject: This is the message + +foobar +--b1 +Content-Type: text/plain + +This is section 1 + +--b1 +Content-Type: text/plain + +This is section 2 + +--b1 +Content-Type: message/rfc822 + +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="b2" +Subject: This is message part 3 + +foobar +--b2 +Content-Type: text/plain + +This is section 3.1 + +--b2 +Content-Type: text/plain + +This is section 3.2 + +--b2-- +--b1 +Content-Type: multipart/mixed; boundary="b3" + +foobar +--b3 +Content-Type: text/plain + +This is section 4.1 + +--b3 +Content-Type: message/rfc822 + +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="b4" +Subject: This is message part 4.2 + +foobar +--b4 +Content-Type: text/plain + +This is section 4.2.1 + +--b4 +Content-Type: multipart/alternative; boundary="b5" + +foobar +--b5 +Content-Type: text/plain + +This is section 4.2.2.1 + +--b5 +Content-Type: text/plain + +This is section 4.2.2.2 + +--b5-- + +--b4-- + +--b3-- + +--b1-- +EOF + +inituid() { + +initdir="$1" + +if test "$initdir" = "" +then + initdir="confmdtest" +fi + +initinbox="$2" +if test "$initinbox" = "" +then + initinbox="inbox" +else + initinbox="inbox.$initinbox" +fi + +env IMAP_BROKENUIDV=1 MAILDIR=$initdir ./imapd >confmdtest.stdout <<EOF +a001 select $initinbox +a logout +EOF + +} + +inituid + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +a001 select inbox +a002 fetch 1 (bodystructure) +a003 fetch 1 (body[]) +a004 fetch 1 (body[]<500.100>) +a005 fetch 1 (body[1]) +a006 fetch 1 (body[1.text]) +a007 fetch 1 (body[2]) +a008 fetch 1 (body[2.text]) +a009 fetch 1 (body[3.header]) +a010 fetch 1 (body[3.mime]) +a011 fetch 1 (body[3.mime]<10,50>) +a012 fetch 1 (body[3.1]) +a013 fetch 1 (body[3.2]) +a014 fetch 1 (body[4.1]) +a015 fetch 1 (body[4.2.header]) +a016 fetch 1 (body[4.2.1]) +a017 fetch 1 (body[4.2.2.1]) +a018 fetch 1 (body[4.2.2.2]) +a019 fetch 1 (envelope) +a020 fetch 1 (body) +a021 fetch 1 (envelope body) +a022 fetch 1 (bodystructure) +a023 fetch 1 (rfc822.size) +a024 fetch 1 (all) +a025 fetch 1 (fast) +a026 fetch 1 (full) +a027 fetch 1 (rfc822.text) +a028 fetch 1 (body[header.fields(content-type)]) +adone logout +EOF + +rm -f confmdtest/cur/msg1:2,S + +cat >confmdtest/new/msg2 <<EOF || exit 1 +From: John <john@example.com> +To: Steve <steve@example.com>, + Tom <tom@example.com> +Mime-Version: 1.0 +Date: Wed, 22 Sep 1999 15:41:09 -0200 +Content-Type: multipart/mixed; boundary="b1" +Subject: This is the message + +foobar +--b1 +Content-Type: text/plain + +This is section 1 + +--b1 +Content-Type: message/rfc822 + +Subject: This is message part 4.2 +From: dave@example.org +To: tom@example.org +Cc: steve@example.org +Mime-Version: 1.0 +content-type: text/plain +content-transfer-encoding: quoted-printable + + +M=41ry had a little lamb, it's fleece was white as snow. And everywhere +Mary went, the lamb was sure to go. + +--b1-- +EOF + +inituid + +cat >confmdtest/new/msg3 <<EOF || exit 1 +From: todd@example.org +To: kevin@example.org +Subject: today's meeting + +Today's meeting has been cancelled +EOF +cat >confmdtest/new/msg4 <<EOF || exit 1 +Subject: New MIME headers test +Mime-Version: 1.0 +Content-Type: multipart/mixed; boundary="c1" +Content-Language: en + + +--c1 +Content-Type: text/plain +Content-ID: <foo@bar> +Content-Description: MIME test message +Content-MD5: aaaabbbb + +test + +--c1-- +EOF +touch -t 199901010000 confmdtest/new/msg3 + +inituid + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +b000 status inbox ( MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN) +b001 select inbox +b001a status inbox ( MESSAGES RECENT UIDNEXT UIDVALIDITY UNSEEN) +b002 fetch 1:2 (flags uid) +b003 search header "content-type" "multipart" +b004 search 2 header "content-type" "multipart" +b005 search to tom@example +b006 search subject "message part 4.2" +b007 search before "1-Feb-1999" +b008 search on "1-Jan-1999" +b009 search senton "22-Sep-1999" +b010 search recent +b011 fetch 1 (rfc822.header) +b012 fetch 1:2 (flags) +b013 search seen +b014 search body "mary had a little lamb" +b015 uid search body "mary had a little lamb" +b016 store 1:2 +flags(\flagged) +b017 store 1 flags(\seen \deleted) +b018 expunge +b019 fetch 1 (flags uid) +b020 create inbox.bozo +b021 uid copy 3 inbox.bozo +b022 select inbox.bozo +b023 status inbox.bozo (uidnext) +b024 fetch 1 (flags) +b025 append inbox.bozo \Seen {11} +test + +test + +b026 append inbox.bozo (\Seen \Flagged) {11} +test + +test + +b027 noop +b028 fetch 1 flags +b029 fetch 1 flags +b030 select inbox +b031 fetch 2 (bodystructure) +adone logout +EOF +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +foo select inbox.bozo +foo store 1 +flags \Deleted +foo expunge +foo logout +EOF +rm -rf confmdtest +mkdir confmdtest || exit 1 +mkdir confmdtest/tmp || exit 1 +mkdir confmdtest/cur || exit 1 +mkdir confmdtest/new || exit 1 +echo "10000S,2C" >confmdtest/maildirsize +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +c001 select inbox +c002 append inbox {5} +test + +c003 append inbox {5} +test + +c004 append inbox {5} +test + +c005 noop +c006 store 1 +flags \Deleted +c007 append inbox {5} +test + +c008 noop +c009 append inbox {5} +test + +c010 store 1 -flags \Deleted +cdone logout +EOF +cat confmdtest/maildirsize +rm -rf confmdtest +mkdir confmdtest || exit 1 +mkdir confmdtest/tmp || exit 1 +mkdir confmdtest/cur || exit 1 +mkdir confmdtest/new || exit 1 +echo "10000S,5C" >confmdtest/maildirsize +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +d001 select inbox +d002 append inbox {5} +test + +d003 noop +d004 copy 1 inbox +d005 noop +ddone logout +EOF +cat confmdtest/maildirsize +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +e001 select inbox +e002 copy 1:2 inbox +e003 noop +edone logout +EOF +cat confmdtest/maildirsize +../maildir/maildirmake -f Trash confmdtest || exit 1 + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +f001 select inbox +f002 copy 1:2 inbox +f002 copy 1:2 inbox.Trash +f003 noop +f004 select inbox.Trash +f005 copy 1:2 inbox +f006 copy 1:2 inbox.Trash +f007 noop +f008 copy 1 inbox +f009 select inbox +fdone logout +EOF +cat confmdtest/maildirsize +echo "Counts:" `ls confmdtest/cur | wc -l` `ls confmdtest/.Trash/cur | wc -l` +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +g001 select inbox +g002 select Trash +g003 select INBOX.Trash +g004 delete INBOX.Trash +g005 close +g006 delete INBOX.Trash +g007 create INBOX.a +g008 delete INBOX.a/ +g009 delete INBOX.a +g010 create inbox.a/ +gdone logout +EOF + +rm -rf confmdtest2 +../maildir/maildirmake confmdtest2 +../maildir/maildirmake -f a confmdtest2 +../maildir/maildirmake -f b confmdtest2 +chmod u-rwx confmdtest2/.b/tmp +chmod u-rwx confmdtest2/.b/new +chmod u-rwx confmdtest2/.b/cur + +echo "test `pwd`/confmdtest2" >confmdtest/shared-maildirs + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +h001 list "" "*" +h002 list "" "%" +h003 list "" "%.%" +h004 list "" "%.%.%" +h005 list "shared" "*" +h006 list "shared.test" "*" +h007 list "INBOX" "" +h008 list "shared" "" +h009 list "shared.test" "" +h010 subscribe inbox +h011 list "" "*" +h012 lsub "" "*" +h013 subscribe shared.test.a +h014 list "" "*" +h015 lsub "" "*" +h016 subscribe shared.test.b +h017 list "" "*" +h018 lsub "" "*" +h019 unsubscribe shared.test.a +h020 list "" "*" +h021 lsub "" "*" +h022 subscribe shared.test.a +h023 list "" "*" +h024 lsub "" "*" +hdone logout +EOF + +cat >confmdtest2/.a/new/msg1 <<EOF +Subject: message 1 + +message 1 +EOF + +inituid confmdtest2 a + +cat >confmdtest2/.a/new/msg2 <<EOF +Subject: message 2 + +message 2 +EOF + +inituid confmdtest2 a + +cat >confmdtest/new/msg1 <<EOF +Subject: message 1 +EOF + +cat >confmdtest/.Trash/new/msg2 <<EOF +Subject: message 2 +EOF + +../maildir/maildirmake -f c confmdtest +../maildir/maildirmake -f Trash confmdtest2 + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +ii001 list "" "*" +ii002 list "" "*" +iidone logout +EOF + +env IMAP_BROKENUIDV=1 IMAP_CHECK_ALL_FOLDERS=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +ii003 list "" "*" +ii004 list "" "*" +iidone logout +EOF + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +i001 select shared.test.b +i002 select shared.test.a +i003 close +i004 select shared.test.a +i005 append shared.test.a {5} +test + +i006 noop +i007 fetch 1:* (flags) +i008 select inbox +i009 copy 1:2 shared.test.a +i010 select shared.test.a +i011 fetch 1:* (flags) +i012 append shared.test.b {5} +i013 copy 1 shared.test.b +i014 store 1:2 +flags (\Deleted) +i015 expunge +idone logout +EOF +chmod -R u+rwx confmdtest +chmod -R u+rwx confmdtest2 + +rm -f confmdtest/new/* +rm -f confmdtest/cur/* + +cat >confmdtest/new/msg1 <<EOF +From: John <john1@example.com> +To: John <cca4@example.com>, <ccb1@example.com> +Cc: John <toa1@example.com>, <tob4@example.com> +Subject: Re[2]: [foo] message 1 +Date: Wed, 22 Sep 1999 15:41:09 -0200 + +message 1 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +EOF + +inituid + +cat >confmdtest/new/msg2 <<EOF +From: John <john2@example.com> +To: John <cca3@example.com>, <ccb2@example.com> +Cc: John <toa2@example.com>, <tob3@example.com> +Subject: message 2 +Date: Wed, 22 Sep 1999 15:41:00 -0200 + +message 2 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +EOF + +inituid + +cat >confmdtest/new/msg3 <<EOF +From: John <john3@example.com> +To: John <cca2@example.com>, <ccb3@example.com> +Cc: John <toa3@example.com>, <tob2@example.com> +Subject: message 1 +Date: Wed, 22 Sep 1999 15:41:00 -0200 + +message 3 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +EOF + +inituid + +cat >confmdtest/new/msg4 <<EOF +From: John <john4@example.com> +To: John <cca1@example.com>, <ccb4@example.com> +Cc: John <toa4@example.com>, <tob1@example.com> +Subject: [fwd:message 1] (fwd) +Date: Wed, 15 Sep 1999 15:41:00 -0200 + +message 3 +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +EOF + +inituid + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T001 select INBOX +T002 fetch 1:* (uid body[header.fields(date)]) +T003 THREAD ORDEREDSUBJECT US-ASCII ALL +T004 THREAD ORDEREDSUBJECT US-ASCII SENTSINCE 19-SEP-1999 +S001 SORT (SUBJECT DATE) US-ASCII ALL +S002 SORT (SUBJECT REVERSE DATE) US-ASCII ALL +S003 SORT (REVERSE SUBJECT DATE) US-ASCII ALL +S004 SORT (FROM) US-ASCII ALL +S005 SORT (REVERSE FROM) US-ASCII ALL +S006 SORT (FROM) US-ASCII SENTSINCE 19-SEP-1999 +S007 SORT (REVERSE FROM) US-ASCII SENTSINCE 19-SEP-1999 +S008 SORT (TO) US-ASCII ALL +S009 SORT (CC) US-ASCII ALL +S010 SORT (REVERSE SIZE) US-ASCII ALL +TDONE logout +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake -f Trash confmdtest || exit 1 +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +J001 list "" "*" +J002 lsub "" "*" +J003 SUBSCRIBE INBOX +J004 SUBSCRIBE INBOX.Trash +J005 list "" "*" +J006 lsub "" "*" +J007 UNSUBSCRIBE INBOX +J008 LIST "" "*" +J009 LSUB "" "*" +JDONE logout +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 + +cat >confmdtest/new/msg1 <<EOF +From: postmaster +Subject: thread 1 +Message-ID: <mid1> +Date: Sat, 05 Feb 2000 13:34:02 -0800 + +message 1 +EOF + +inituid + +cat >confmdtest/new/msg2 <<EOF +From: postmaster +Subject: thread 1.1 +Date: Sat, 05 Feb 2000 13:34:02 -0800 +Message-ID: <mid2> +References: <mid1> + +message 2 +EOF + +inituid + +cat >confmdtest/new/msg3 <<EOF +From: postmaster +Date: Sat, 05 Feb 2000 13:34:02 -0800 +Subject: thread 1.2 +In-Reply-To: <mid2> + +message 3 +EOF + +inituid + +cat >confmdtest/new/msg4 <<EOF +From: postmaster +Date: Sat, 05 Feb 2000 13:34:02 -0800 +Subject: thread 1.2 +References: <mid1> + +message 4 +EOF + +inituid + +cat >confmdtest/new/msg5 <<EOF +From: postmaster +Date: Sat, 05 Feb 2000 13:34:02 -0800 +Subject: thread 1.2 +Message-ID: <mid11> +References: <mid10> + +message 5 +EOF + +inituid + +cat >confmdtest/new/msg6 <<EOF +From: postmaster +Date: Sat, 05 Feb 2000 13:34:01 -0800 +Subject: thread 1.2 +Message-ID: <mid12> +References: <mid10> + +message 6 +EOF + +inituid + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +K001 select INBOX +K002 THREAD REFERENCES US-ASCII ALL +KDONE logout +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake -f Trash confmdtest || exit 1 + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +L001 CREATE INBOX.a +L002 CREATE INBOX.a.a +L003 CREATE INBOX.aa +L004 RENAME INBOX.a INBOX.b +L005 LIST "" "*" +L006 RENAME INBOX.b INBOX.a +L007 LIST "" "*" +L008 RENAME INBOX.a. INBOX.b. +L009 LIST "" "*" +L010 RENAME INBOX.b. INBOX.a. +L011 LIST "" "*" +L012 CREATE INBOX.b.a +L013 DELETE INBOX.a +L014 CREATE INBOX.a.a +L015 LIST "" "*" +L016 RENAME INBOX.a INBOX.b +L017 RENAME INBOX.a. INBOX.b. +L018 SELECT INBOX +L019 APPEND INBOX \Seen {5} +test + +L020 NOOP +L021 COPY 1 INBOX +L022 COPY 1 INBOX +L023 COPY 1 INBOX +L024 COPY 1 INBOX +L025 COPY 1 INBOX +L026 COPY 1 INBOX +L027 NOOP +L028 STORE 2:3,5:7 +FLAGS \Deleted +L029 FETCH 1:* (UID FLAGS) +L030 UID EXPUNGE 2,3:5 +L031 FETCH 1:* (UID FLAGS) +LDONE logout +EOF + +rm -rf confmdtest + +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake -q 10C confmdtest || exit 1 + +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +M001 CREATE INBOX.a +M002 APPEND INBOX \Seen {5} +test + +M003 SELECT INBOX +M004 COPY 1 INBOX +M005 COPY 1 INBOX +M006 NOOP +M007 COPY 1:3 INBOX.a +M008 RENAME INBOX.a INBOX.b +MDONE logout +EOF +cat confmdtest/maildirsize +env IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +M009 DELETE INBOX.b +MDONE logout +EOF +cat confmdtest/maildirsize + +rm -rf confmdtest + +../maildir/maildirmake confmdtest || exit 1 +env IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +N001 LIST "" "*" +N002 append inbox \Deleted {11} +test + +test + +N003 LIST "" "*" +N004 SELECT inbox +N005 EXPUNGE +N006 LIST "" "*" +NDONE logout +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake -q10C confmdtest +env IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +O001 append inbox NIL {11} +test + +test + +o002 SELECT inbox +o003 COPY 1 inbox +o004 NOOP +o005 STORE 1 +FLAGS \Deleted +o006 COPY 1:2 inbox +o007 NOOP +o008 logout +EOF +cat confmdtest/maildirsize +rm -f confmdtest/maildirsize + +env IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +p001 SELECT INBOX +p002 CREATE INBOX.a +p003 COPY 1 INBOX.a +p004 SELECT INBOX.a +p005 STORE 1 +FLAGS(-Label1 \Deleted) +p006 append INBOX.a (-Label2) {11} +test + +test + +p007 EXPUNGE +p008 FETCH 1:* FLAGS +p009 COPY 1 INBOX.a +p010 NOOP +P011 FETCH 1:* FLAGS +P012 STORE 1 -FLAGS (-Label2) +P013 STORE 1 +FLAGS (\SEEN) +P013 FETCH 1:* FLAGS +P014 SEARCH SEEN +P015 SEARCH KEYWORD -LABEL2 +P016 SEARCH UNKEYWORD -Label2 +P017 SEARCH KEYWORD -Label1 +P018 COPY 1:* INBOX.a +P019 COPY 1:* INBOX.a +P020 NOOP +P021 COPY 1:* INBOX.a +P022 COPY 1:* INBOX.a +P023 NOOP +P025 STORE 4 +FLAGS -Label1 +P024 FETCH 1:* FLAGS +P025 SEARCH KEYWORD -Label2 +P026 SEARCH KEYWORD -Label2 KEYWORD -Label1 +pDONE LOGOUT +EOF + +env IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +Q001 select INBOX +Q002 fetch 1:* FLAGS +Q003 status INBOX.Trash MESSAGES +Q004 expunge +Q005 status INBOX.Trash MESSAGES +Q006 COPY 1 INBOX.a +Q007 STORE 1:* +FLAGS \Deleted +Q008 NOOP +Q009 EXPUNGE +Q005 status INBOX.Trash MESSAGES +qDONE LOGOUT +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 + +env IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +R001 CREATE INBOX.a +R002 LIST (ACL MYRIGHTS POSTADDRESS) "" "*" +R003 ACL STORE INBOX owner -t +R004 ACL STORE (INBOX *) owner +d +R005 ACL STORE INBOX.a user=systemuser1 alr +R006 ACL STORE INBOX.a user=systemuser1 +w +R007 ACL STORE INBOX.a user=systemuser1 -w +R008 ACL STORE INBOX.a user=systemuser1 -alr +R009 ACL STORE INBOX.a user=systemuser1 alr +R010 ACL STORE INBOX.a user=systemuser1 "" +R011 ACL STORE INBOX.a user=systemuser1 alr +R012 ACL SET (INBOX *) "owner" "acdilrsw" administrators "acdilrsw" user=systemuser2 alr +R013 ACL DELETE INBOX.a user=systemuser1 +R014 ACL DELETE INBOX.a user=systemuser2 +R015 ACL STORE INBOX.a owner -a +R016 ACL STORE INBOX.a -owner +wt +R017 ACL STORE INBOX.a -owner -w +R018 ACL DELETE INBOX.a -owner +R019 ACL STORE INBOX.a -owner +a +R020 ACL STORE INBOX.a owner -ci +R021 CREATE INBOX.a.b +R022 ACL STORE INBOX.a owner +c +R023 CREATE INBOX.a.b +R024 LIST (ACL) "" INBOX.a.b +R025 CREATE INBOX.c.d +R026 LIST (ACL) "" "INBOX.c*" +R027 ACL STORE (INBOX.c*) -owner x +R028 DELETE INBOX.c.d +R029 ACL DELETE INBOX.c.d -owner +R030 DELETE INBOX.c.d +R031 DELETE INBOX.c. +R032 LIST (ACL) "" "*" +R033 DELETE INBOX.a +R034 LIST (ACL) "" "*" +R035 RENAME INBOX.a INBOX.f +R036 LIST (ACL) "" "*" +R037 CREATE INBOX.f +R038 LIST (ACL) "" "*" +R039 ACL STORE INBOX owner -i +R040 SELECT INBOX +R041 APPEND INBOX (\Seen \Deleted \Answered Foo) {0} +R042 ACL STORE INBOX owner +i +R043 APPEND INBOX (\Seen \Deleted \Answered Foo) {11} +test + +test + +R043 NOOP +R044 FETCH 1 (FLAGS) +R045 ACL STORE INBOX owner -tw +R046 APPEND INBOX (\Seen \Deleted \Answered Foo) {11} +test + +test + +R047 NOOP +R048 FETCH 2 (FLAGS) +R049 ACL STORE INBOX owner +tw +R050 ACL STORE INBOX owner -s +R051 APPEND INBOX (\Seen \Deleted \Answered Foo) {11} +test + +test + +R052 NOOP +R053 FETCH 3 (FLAGS) +R054 STORE 2:3 +FLAGS (\Deleted) +R055 STORE 1 -FLAGS (\Deleted) +R056 EXPUNGE +R057 STORE 1 +FLAGS (\Deleted) +R058 ACL STORE INBOX owner +s +R059 CREATE INBOX.x +R060 COPY 1 INBOX.x +R061 SELECT INBOX.x +R062 FETCH 1 (FLAGS) +R063 SELECT INBOX +R064 ACL STORE INBOX.x owner -s +R065 COPY 1 INBOX.x +R066 SELECT INBOX.x +R067 FETCH 2 (FLAGS) +R068 SELECT INBOX +R069 ACL STORE INBOX.x owner +s +R070 ACL STORE INBOX.x owner -w +R071 COPY 1 INBOX.x +R072 SELECT INBOX.x +R073 FETCH 3 (FLAGS) +R074 SELECT INBOX +R075 ACL STORE INBOX.x owner +w +R076 ACL STORE INBOX.x owner -t +R077 COPY 1 INBOX.x +R078 SELECT INBOX.x +R079 FETCH 4 (FLAGS) +R080 SELECT INBOX +RDONE LOGOUT +EOF + +rm -rf confmdtest +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake -f a confmdtest || exit 1 +cat >confmdtest/.a/cur/msg1:2,S <<EOF || exit 1 +From: John <john@example.com> +To: john <john@example.com> +Subject: test + +test +EOF +env IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +S001 SELECT INBOX.a +S002 COPY 1 INBOX.a +S003 NOOP +S004 COPY 1:2 INBOX.a +S005 NOOP +S006 ACL STORE INBOX.a owner -e +S007 STORE 1 +FLAGS(\Deleted) +S008 EXPUNGE +S009 CLOSE +S010 SELECT INBOX.a +S011 ACL STORE INBOX.a owner +e +S012 EXPUNGE +S013 STORE 1 +FLAGS(\Deleted) +S014 CLOSE +S015 SELECT INBOX.a +S016 CLOSE +S017 ACL STORE INBOX.a owner -r +S018 SELECT INBOX.a +S019 EXAMINE INBOX.a +S020 STATUS INBOX.a (UIDVALIDITY) +S021 ACL STORE INBOX.a owner +r +S022 SELECT INBOX.a +S023 APPEND INBOX.a NIL {11} +test + +test + +S024 NOOP +S025 STORE 1:* FLAGS () +S027 ACL STORE INBOX.a owner -s +S028 FETCH 1 (BODY[]) +S029 ACL STORE INBOX.a owner +s +S030 FETCH 1 (BODY[]) +S031 ACL STORE INBOX.a owner -s +S032 STORE 1:2 FLAGS(\Seen \Deleted Foo) +S033 STORE 1:2 -FLAGS(\Seen \Deleted Foo) +S034 STORE 2 +FLAGS(\Seen \Deleted \Answered Foo) +S035 FETCH 1:2 (FLAGS) +S036 ACL STORE INBOX.a owner +s +S037 ACL STORE INBOX.a owner -w +S038 STORE 1:2 FLAGS(\Seen \Deleted Bar) +S039 STORE 1:2 -FLAGS(\Seen \Deleted Bar) +S040 STORE 1:2 +FLAGS(\Seen \Deleted Bar) +S041 ACL STORE INBOX.a owner +w +S042 STORE 2 -FLAGS (\Deleted) +S043 ACL STORE INBOX.a owner -t +S044 STORE 1:2 FLAGS (\Deleted \Seen Bar) +S045 STORE 1:2 -FLAGS (\Deleted \Seen Bar) +S046 STORE 1:2 +FLAGS (\Deleted \Seen Bar) +S047 CLOSE +S048 ACL STORE INBOX user=courierimaptestuser1 alr +S049 ACL STORE INBOX user=courierimaptestuser2 lr +S050 LIST (ACL) "" INBOX +SDONE LOGOUT +EOF + +rm -rf confmdtest* +../maildir/maildirmake confmdtest || exit 1 +../maildir/maildirmake confmdtest2 || exit 1 +../maildir/maildirmake confmdtest3 || exit 1 +mkdir confmdtest4 || exit 1 +cat >confmdtest4/index <<EOF || exit 1 +user0 1 1 `pwd` confmdtest +a * indexa +b * indexb +EOF +echo "user1 1 1 `pwd` confmdtest2" >confmdtest4/indexa || exit 1 +echo "aashared 1 1 `pwd` confmdtest5" >>confmdtest4/indexa || exit 1 +echo "user2 1 1 `pwd` confmdtest3" >confmdtest4/indexb || exit 1 + +IMAP_SHAREDINDEXFILE=`pwd`/confmdtest4/index +export IMAP_SHAREDINDEXFILE + +env AUTHENTICATED=user1 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest2 ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T001 ACL STORE INBOX user=user0 +l +T002 CREATE INBOX.a +T003 ACL STORE INBOX.a anyone +lr +T004 append INBOX.a \Seen {22} +Subject: user1 + +user1 + +T005 LOGOUT +EOF + +env AUTHENTICATED=user2 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest3 ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T010 ACL STORE INBOX anyone +l +T011 CREATE INBOX.a +T012 append INBOX.a \Seen {22} +Subject: user2 + +user2 + +T013 LOGOUT +EOF + +env AUTHENTICATED=user0 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T020 list(acl) "" "#shared.*" +T021 ACL STORE INBOX anyone acdilrsw +T022 list(acl) "" "#shared.%" +T023 LOGOUT +EOF + +env AUTHENTICATED=user2 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest3 ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T030 LIST "" "#shared.*" +T031 LIST "" "#shared.%" +T032 LIST "" "#shared.a.%" +T033 LIST "" "#shared.c.%" +T034 SELECT "#shared.a.user1" +T035 SELECT "#shared.a.user1.a" +T036 FETCH 1:* (ENVELOPE) +T037 STATUS #shared.a.user1 (UIDVALIDITY) +T038 COPY 1 #shared.user0 +T039 SELECT #shared.user0 +T040 CREATE #shared.a.user1.b +T041 CREATE #shared.c +T042 CREATE #shared.user0.foo +T043 RENAME #shared.user0.foo #shared.user0.bar +T044 LIST(ACL) "" "#shared.user0*" +T045 ACL STORE #shared.a.user1.a anyone lr +T046 CREATE #shared.user0.foo +T047 ACL STORE #shared.user0.foo anyone alr +T048 ACL STORE #shared.user0.bar anyone -x +T049 RENAME #shared.user0.bar #shared.user0.foo.bar +T050 ACL STORE #shared.user0.bar anyone +x +T051 RENAME #shared.user0.bar #shared.user0.foo.bar +T052 ACL STORE #shared.user0.foo anyone +c +T053 RENAME #shared.user0.bar #shared.user0.foo.bar +T054 LIST "" "#shared.user0*" +TDONE LOGOUT +EOF + +cat >confmdtest4/indexb <<EOF +use&2 1 1 `pwd` confmdtest3 +user.2 1 1 `pwd` confmdtest3 +usér/3 1 1 `pwd` confmdtest3 +EOF + +env AUTHENTICATED=user1 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest2 ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T055 LIST "" "#shared.b.*" +T056 LIST "" "#shared.b.%" +T057 LIST "" "#shared.b.user 2.*" +T058 LIST "" "#shared.b.use&-2.*" +TDONE LOGOUT +EOF + +env IMAP_SHAREDMUNGENAMES=1 AUTHENTICATED=user1 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest2 ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T055 LIST "" "#shared.b.*" +T056 LIST "" "#shared.b.%" +T057 LIST "" "#shared.b.user\\\\:2.*" +T058 LIST "" "#shared.b.use&-2.*" +T059 CREATE INBOX.a.b +T060 SELECT INBOX.a.b +T061 RENAME INBOX.a INBOX.aa +T062 CLOSE +T063 RENAME INBOX.a INBOX.aa +T064 RENAME INBOX INBOX.c +T065 RENAME INBOX.aa #shared.user0.aa +T066 LIST "" #shared.* +T067 LIST "" #shared.% +T068 LIST "" #shared.% +T069 LIST "" #shared.%.% +T070 LIST "" #shared.%.%.% +T071 LIST "" #shared.%.%.%.% +TDONE LOGOUT +EOF + +echo "usergroup1 1 1 `pwd` confmdtest" >confmdtest4/indexgroup1 || exit 1 + +env IMAP_SHAREDMUNGENAMES=1 AUTHENTICATED=user1 OPTIONS=sharedgroup=group1 IMAP_KEYWORDS=1 IMAP_MOVE_EXPUNGE_TO_TRASH=1 IMAP_BROKENUIDV=1 MAILDIR=confmdtest2 ./imapd <<EOF | sed 's/UIDVALIDITY [0-9]*/UIDVALIDITY/;s/INTERNALDATE "[^"]*"/INTERNALDATE -DATE-/g' +T072 LIST "" #shared.* +T073 LIST(ACL) "" INBOX +T074 ACL STORE INBOX user=fred lr +T075 ACL STORE INBOX -user=john lr +T076 ACL STORE INBOX -authuser lr +T077 GETACL INBOX +T078 SETACL INBOX -authuser lcr +T079 SETACL INBOX -john lcr +T080 SETACL INBOX fred cr +T081 GETACL INBOX +T082 LIST(ACL) "" INBOX +T083 DELETEACL INBOX -john +T084 DELETEACL INBOX fred +T085 GETACL INBOX +T086 LIST(ACL) "" INBOX +TDONE LOGOUT +EOF + diff --git a/imap/testsuite.txt b/imap/testsuite.txt new file mode 100644 index 0000000..5a341cd --- /dev/null +++ b/imap/testsuite.txt @@ -0,0 +1,1697 @@ +000000 * PREAUTH Ready.
+000001 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000002 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000003 * 1 EXISTS
+000004 * 0 RECENT
+000005 * OK [UIDVALIDITY] Ok
+000006 * OK [MYRIGHTS "acdilrsw"] ACL
+000007 a001 OK [READ-WRITE] Ok
+000008 * 1 FETCH (BODYSTRUCTURE (("text" "plain" NIL NIL NIL "8bit" 19 1 NIL NIL NIL)("text" "plain" NIL NIL NIL "8bit" 19 1 NIL NIL NIL)("message" "rfc822" NIL NIL NIL "8bit" 229 (NIL "This is message part 3" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 21 1 NIL NIL NIL)("text" "plain" NIL NIL NIL "8bit" 21 1 NIL NIL NIL) "mixed" ("boundary" "b2") NIL NIL) 15 NIL NIL NIL)(("text" "plain" NIL NIL NIL "8bit" 21 1 NIL NIL NIL)("message" "rfc822" NIL NIL NIL "8bit" 377 (NIL "This is message part 4.2" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 23 1 NIL NIL NIL)(("text" "plain" NIL NIL NIL "8bit" 25 1 NIL NIL NIL)("text" "plain" NIL NIL NIL "8bit" 25 1 NIL NIL NIL) "alternative" ("boundary" "b5") NIL NIL) "mixed" ("boundary" "b4") NIL NIL) 27 NIL NIL NIL) "mixed" ("boundary" "b3") NIL NIL) "mixed" ("boundary" "b1") NIL NIL))
+000009 a002 OK FETCH completed.
+000010 * 1 FETCH (BODY[] {1122}
+000011 From: John <john@example.com>
+000012 To: Steve <steve@example.com>, Tom <tom@example.com>
+000013 Mime-Version: 1.0
+000014 Content-Type: multipart/mixed; boundary="b1"
+000015 Subject: This is the message
+000016
+000017 foobar
+000018 --b1
+000019 Content-Type: text/plain
+000020
+000021 This is section 1
+000022
+000023 --b1
+000024 Content-Type: text/plain
+000025
+000026 This is section 2
+000027
+000028 --b1
+000029 Content-Type: message/rfc822
+000030
+000031 Mime-Version: 1.0
+000032 Content-Type: multipart/mixed; boundary="b2"
+000033 Subject: This is message part 3
+000034
+000035 foobar
+000036 --b2
+000037 Content-Type: text/plain
+000038
+000039 This is section 3.1
+000040
+000041 --b2
+000042 Content-Type: text/plain
+000043
+000044 This is section 3.2
+000045
+000046 --b2--
+000047 --b1
+000048 Content-Type: multipart/mixed; boundary="b3"
+000049
+000050 foobar
+000051 --b3
+000052 Content-Type: text/plain
+000053
+000054 This is section 4.1
+000055
+000056 --b3
+000057 Content-Type: message/rfc822
+000058
+000059 Mime-Version: 1.0
+000060 Content-Type: multipart/mixed; boundary="b4"
+000061 Subject: This is message part 4.2
+000062
+000063 foobar
+000064 --b4
+000065 Content-Type: text/plain
+000066
+000067 This is section 4.2.1
+000068
+000069 --b4
+000070 Content-Type: multipart/alternative; boundary="b5"
+000071
+000072 foobar
+000073 --b5
+000074 Content-Type: text/plain
+000075
+000076 This is section 4.2.2.1
+000077
+000078 --b5
+000079 Content-Type: text/plain
+000080
+000081 This is section 4.2.2.2
+000082
+000083 --b5--
+000084
+000085 --b4--
+000086
+000087 --b3--
+000088
+000089 --b1--
+000090 )
+000091 a003 OK FETCH completed.
+000092 * 1 FETCH (BODY[]<500> {100}
+000093 +000094
+000095 --b2
+000096 Content-Type: text/plain
+000097
+000098 This is section 3.2
+000099
+000100 --b2--
+000101 --b1
+000102 Content-Type: multipart/mi)
+000103 a004 OK FETCH completed.
+000104 * 1 FETCH (BODY[1] {19}
+000105 This is section 1
+000106 )
+000107 a005 OK FETCH completed.
+000108 * 1 FETCH (BODY[1.TEXT] {19}
+000109 This is section 1
+000110 )
+000111 a006 OK FETCH completed.
+000112 * 1 FETCH (BODY[2] {19}
+000113 This is section 2
+000114 )
+000115 a007 OK FETCH completed.
+000116 * 1 FETCH (BODY[2.TEXT] {19}
+000117 This is section 2
+000118 )
+000119 a008 OK FETCH completed.
+000120 * 1 FETCH (BODY[3.HEADER] {100}
+000121 Mime-Version: 1.0
+000122 Content-Type: multipart/mixed; boundary="b2"
+000123 Subject: This is message part 3
+000124
+000125 )
+000126 a009 OK FETCH completed.
+000127 * 1 FETCH (BODY[3.MIME] {32}
+000128 Content-Type: message/rfc822
+000129
+000130 )
+000131 a010 OK FETCH completed.
+000132 * 1 FETCH (BODY[3.MIME]<10> {22}
+000133 pe: message/rfc822
+000134
+000135 )
+000136 a011 OK FETCH completed.
+000137 * 1 FETCH (BODY[3.1] {21}
+000138 This is section 3.1
+000139 )
+000140 a012 OK FETCH completed.
+000141 * 1 FETCH (BODY[3.2] {21}
+000142 This is section 3.2
+000143 )
+000144 a013 OK FETCH completed.
+000145 * 1 FETCH (BODY[4.1] {21}
+000146 This is section 4.1
+000147 )
+000148 a014 OK FETCH completed.
+000149 * 1 FETCH (BODY[4.2.HEADER] {102}
+000150 Mime-Version: 1.0
+000151 Content-Type: multipart/mixed; boundary="b4"
+000152 Subject: This is message part 4.2
+000153
+000154 )
+000155 a015 OK FETCH completed.
+000156 * 1 FETCH (BODY[4.2.1] {23}
+000157 This is section 4.2.1
+000158 )
+000159 a016 OK FETCH completed.
+000160 * 1 FETCH (BODY[4.2.2.1] {25}
+000161 This is section 4.2.2.1
+000162 )
+000163 a017 OK FETCH completed.
+000164 * 1 FETCH (BODY[4.2.2.2] {25}
+000165 This is section 4.2.2.2
+000166 )
+000167 a018 OK FETCH completed.
+000168 * 1 FETCH (ENVELOPE (NIL "This is the message" (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("Steve" NIL "steve" "example.com")("Tom" NIL "tom" "example.com")) NIL NIL NIL NIL))
+000169 a019 OK FETCH completed.
+000170 * 1 FETCH (BODY (("text" "plain" NIL NIL NIL "8bit" 19 1)("text" "plain" NIL NIL NIL "8bit" 19 1)("message" "rfc822" NIL NIL NIL "8bit" 229 (NIL "This is message part 3" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 21 1)("text" "plain" NIL NIL NIL "8bit" 21 1) "mixed") 15)(("text" "plain" NIL NIL NIL "8bit" 21 1)("message" "rfc822" NIL NIL NIL "8bit" 377 (NIL "This is message part 4.2" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 23 1)(("text" "plain" NIL NIL NIL "8bit" 25 1)("text" "plain" NIL NIL NIL "8bit" 25 1) "alternative") "mixed") 27) "mixed") "mixed"))
+000171 a020 OK FETCH completed.
+000172 * 1 FETCH (ENVELOPE (NIL "This is the message" (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("Steve" NIL "steve" "example.com")("Tom" NIL "tom" "example.com")) NIL NIL NIL NIL) BODY (("text" "plain" NIL NIL NIL "8bit" 19 1)("text" "plain" NIL NIL NIL "8bit" 19 1)("message" "rfc822" NIL NIL NIL "8bit" 229 (NIL "This is message part 3" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 21 1)("text" "plain" NIL NIL NIL "8bit" 21 1) "mixed") 15)(("text" "plain" NIL NIL NIL "8bit" 21 1)("message" "rfc822" NIL NIL NIL "8bit" 377 (NIL "This is message part 4.2" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 23 1)(("text" "plain" NIL NIL NIL "8bit" 25 1)("text" "plain" NIL NIL NIL "8bit" 25 1) "alternative") "mixed") 27) "mixed") "mixed"))
+000173 a021 OK FETCH completed.
+000174 * 1 FETCH (BODYSTRUCTURE (("text" "plain" NIL NIL NIL "8bit" 19 1 NIL NIL NIL)("text" "plain" NIL NIL NIL "8bit" 19 1 NIL NIL NIL)("message" "rfc822" NIL NIL NIL "8bit" 229 (NIL "This is message part 3" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 21 1 NIL NIL NIL)("text" "plain" NIL NIL NIL "8bit" 21 1 NIL NIL NIL) "mixed" ("boundary" "b2") NIL NIL) 15 NIL NIL NIL)(("text" "plain" NIL NIL NIL "8bit" 21 1 NIL NIL NIL)("message" "rfc822" NIL NIL NIL "8bit" 377 (NIL "This is message part 4.2" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 23 1 NIL NIL NIL)(("text" "plain" NIL NIL NIL "8bit" 25 1 NIL NIL NIL)("text" "plain" NIL NIL NIL "8bit" 25 1 NIL NIL NIL) "alternative" ("boundary" "b5") NIL NIL) "mixed" ("boundary" "b4") NIL NIL) 27 NIL NIL NIL) "mixed" ("boundary" "b3") NIL NIL) "mixed" ("boundary" "b1") NIL NIL))
+000175 a022 OK FETCH completed.
+000176 * 1 FETCH (RFC822.SIZE 1122)
+000177 a023 OK FETCH completed.
+000178 * 1 FETCH (FLAGS (\Seen) INTERNALDATE -DATE- RFC822.SIZE 1122 ENVELOPE (NIL "This is the message" (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("Steve" NIL "steve" "example.com")("Tom" NIL "tom" "example.com")) NIL NIL NIL NIL))
+000179 a024 OK FETCH completed.
+000180 * 1 FETCH (FLAGS (\Seen) INTERNALDATE -DATE- RFC822.SIZE 1122)
+000181 a025 OK FETCH completed.
+000182 * 1 FETCH (FLAGS (\Seen) INTERNALDATE -DATE- RFC822.SIZE 1122 ENVELOPE (NIL "This is the message" (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("John" NIL "john" "example.com")) (("Steve" NIL "steve" "example.com")("Tom" NIL "tom" "example.com")) NIL NIL NIL NIL) BODY (("text" "plain" NIL NIL NIL "8bit" 19 1)("text" "plain" NIL NIL NIL "8bit" 19 1)("message" "rfc822" NIL NIL NIL "8bit" 229 (NIL "This is message part 3" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 21 1)("text" "plain" NIL NIL NIL "8bit" 21 1) "mixed") 15)(("text" "plain" NIL NIL NIL "8bit" 21 1)("message" "rfc822" NIL NIL NIL "8bit" 377 (NIL "This is message part 4.2" NIL NIL NIL NIL NIL NIL NIL NIL) (("text" "plain" NIL NIL NIL "8bit" 23 1)(("text" "plain" NIL NIL NIL "8bit" 25 1)("text" "plain" NIL NIL NIL "8bit" 25 1) "alternative") "mixed") 27) "mixed") "mixed"))
+000183 a026 OK FETCH completed.
+000184 * 1 FETCH (RFC822.TEXT {940}
+000185 foobar
+000186 --b1
+000187 Content-Type: text/plain
+000188
+000189 This is section 1
+000190
+000191 --b1
+000192 Content-Type: text/plain
+000193
+000194 This is section 2
+000195
+000196 --b1
+000197 Content-Type: message/rfc822
+000198
+000199 Mime-Version: 1.0
+000200 Content-Type: multipart/mixed; boundary="b2"
+000201 Subject: This is message part 3
+000202
+000203 foobar
+000204 --b2
+000205 Content-Type: text/plain
+000206
+000207 This is section 3.1
+000208
+000209 --b2
+000210 Content-Type: text/plain
+000211
+000212 This is section 3.2
+000213
+000214 --b2--
+000215 --b1
+000216 Content-Type: multipart/mixed; boundary="b3"
+000217
+000218 foobar
+000219 --b3
+000220 Content-Type: text/plain
+000221
+000222 This is section 4.1
+000223
+000224 --b3
+000225 Content-Type: message/rfc822
+000226
+000227 Mime-Version: 1.0
+000228 Content-Type: multipart/mixed; boundary="b4"
+000229 Subject: This is message part 4.2
+000230
+000231 foobar
+000232 --b4
+000233 Content-Type: text/plain
+000234
+000235 This is section 4.2.1
+000236
+000237 --b4
+000238 Content-Type: multipart/alternative; boundary="b5"
+000239
+000240 foobar
+000241 --b5
+000242 Content-Type: text/plain
+000243
+000244 This is section 4.2.2.1
+000245
+000246 --b5
+000247 Content-Type: text/plain
+000248
+000249 This is section 4.2.2.2
+000250
+000251 --b5--
+000252
+000253 --b4--
+000254
+000255 --b3--
+000256
+000257 --b1--
+000258 )
+000259 a027 OK FETCH completed.
+000260 * 1 FETCH (BODY[HEADER.FIELDS ("content-type")] {48}
+000261 Content-Type: multipart/mixed; boundary="b1"
+000262
+000263 )
+000264 a028 OK FETCH completed.
+000265 * BYE Courier-IMAP server shutting down
+000266 adone OK LOGOUT completed
+000267 * PREAUTH Ready.
+000268 * STATUS "inbox" (MESSAGES 3 RECENT 0 UIDNEXT 5 UIDVALIDITY UNSEEN 3)
+000269 b000 OK STATUS Completed.
+000270 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000271 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000272 * 3 EXISTS
+000273 * 0 RECENT
+000274 * OK [UIDVALIDITY] Ok
+000275 * OK [MYRIGHTS "acdilrsw"] ACL
+000276 b001 OK [READ-WRITE] Ok
+000277 * STATUS "inbox" (MESSAGES 3 RECENT 0 UIDNEXT 5 UIDVALIDITY UNSEEN 3)
+000278 b001a OK STATUS Completed.
+000279 * 1 FETCH (FLAGS () UID 2)
+000280 * 2 FETCH (FLAGS () UID 3)
+000281 b002 OK FETCH completed.
+000282 * SEARCH 1 3
+000283 b003 OK SEARCH done.
+000284 * SEARCH
+000285 b004 OK SEARCH done.
+000286 * SEARCH 1
+000287 b005 OK SEARCH done.
+000288 * SEARCH 1
+000289 b006 OK SEARCH done.
+000290 * SEARCH 2
+000291 b007 OK SEARCH done.
+000292 * SEARCH 2
+000293 b008 OK SEARCH done.
+000294 * SEARCH 1
+000295 b009 OK SEARCH done.
+000296 * SEARCH
+000297 b010 OK SEARCH done.
+000298 * 1 FETCH (RFC822.HEADER {226}
+000299 From: John <john@example.com>
+000300 To: Steve <steve@example.com>,
+000301 Tom <tom@example.com>
+000302 Mime-Version: 1.0
+000303 Date: Wed, 22 Sep 1999 15:41:09 -0200
+000304 Content-Type: multipart/mixed; boundary="b1"
+000305 Subject: This is the message
+000306
+000307 )
+000308 b011 OK FETCH completed.
+000309 * 1 FETCH (FLAGS ())
+000310 * 2 FETCH (FLAGS ())
+000311 b012 OK FETCH completed.
+000312 * SEARCH
+000313 b013 OK SEARCH done.
+000314 * SEARCH 1
+000315 b014 OK SEARCH done.
+000316 * SEARCH 2
+000317 b015 OK SEARCH done.
+000318 * 1 FETCH (FLAGS (\Flagged))
+000319 * 2 FETCH (FLAGS (\Flagged))
+000320 b016 OK STORE completed.
+000321 * 1 FETCH (FLAGS (\Seen \Deleted))
+000322 b017 OK STORE completed.
+000323 * 1 EXPUNGE
+000324 * 2 EXISTS
+000325 * 0 RECENT
+000326 b018 OK EXPUNGE completed
+000327 * 1 FETCH (FLAGS (\Flagged) UID 3)
+000328 b019 OK FETCH completed.
+000329 b020 OK "inbox.bozo" created.
+000330 b021 OK COPY completed.
+000331 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000332 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000333 * 1 EXISTS
+000334 * 0 RECENT
+000335 * OK [UIDVALIDITY] Ok
+000336 * OK [MYRIGHTS "acdilrsw"] ACL
+000337 b022 OK [READ-WRITE] Ok
+000338 * STATUS "inbox.bozo" (UIDNEXT 2)
+000339 b023 OK STATUS Completed.
+000340 * 1 FETCH (FLAGS (\Flagged))
+000341 b024 OK FETCH completed.
+000342 + OK
+000343 b025 OK APPEND Ok.
+000344 + OK
+000345 b026 OK APPEND Ok.
+000346 * 3 EXISTS
+000347 * 0 RECENT
+000348 b027 OK NOOP completed
+000349 * 1 FETCH (FLAGS (\Flagged))
+000350 b028 OK FETCH completed.
+000351 * 1 FETCH (FLAGS (\Flagged))
+000352 b029 OK FETCH completed.
+000353 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000354 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000355 * 2 EXISTS
+000356 * 0 RECENT
+000357 * OK [UIDVALIDITY] Ok
+000358 * OK [MYRIGHTS "acdilrsw"] ACL
+000359 b030 OK [READ-WRITE] Ok
+000360 * 2 FETCH (BODYSTRUCTURE (("text" "plain" NIL "<foo@bar>" "MIME test message" "8bit" 6 1 "aaaabbbb" NIL NIL) "mixed" ("boundary" "c1") NIL "en"))
+000361 b031 OK FETCH completed.
+000362 * BYE Courier-IMAP server shutting down
+000363 adone OK LOGOUT completed
+000364 * PREAUTH Ready.
+000365 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000366 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000367 * 3 EXISTS
+000368 * 0 RECENT
+000369 * OK [UIDVALIDITY] Ok
+000370 * OK [MYRIGHTS "acdilrsw"] ACL
+000371 foo OK [READ-WRITE] Ok
+000372 * 1 FETCH (FLAGS (\Flagged \Deleted))
+000373 foo OK STORE completed.
+000374 * 1 EXPUNGE
+000375 * 2 EXISTS
+000376 * 0 RECENT
+000377 foo OK EXPUNGE completed
+000378 * BYE Courier-IMAP server shutting down
+000379 foo OK LOGOUT completed
+000380 * PREAUTH Ready.
+000381 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000382 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000383 * 0 EXISTS
+000384 * 0 RECENT
+000385 * OK [UIDVALIDITY] Ok
+000386 * OK [MYRIGHTS "acdilrsw"] ACL
+000387 c001 OK [READ-WRITE] Ok
+000388 + OK
+000389 c002 OK APPEND Ok.
+000390 + OK
+000391 c003 OK APPEND Ok.
+000392 + OK
+000393 c004 NO [ALERT] You exceeded your mail quota.
+000394 * NO Error in IMAP command received by server.
+000395 * 2 EXISTS
+000396 * 0 RECENT
+000397 c005 OK NOOP completed
+000398 * 1 FETCH (FLAGS (\Deleted))
+000399 c006 OK STORE completed.
+000400 + OK
+000401 c007 OK APPEND Ok.
+000402 * 3 EXISTS
+000403 * 0 RECENT
+000404 c008 OK NOOP completed
+000405 + OK
+000406 c009 NO [ALERT] You exceeded your mail quota.
+000407 * NO Error in IMAP command received by server.
+000408 c010 NO [ALERT] You exceeded your mail quota.
+000409 * BYE Courier-IMAP server shutting down
+000410 cdone OK LOGOUT completed
+000411 10000S,2C +000412 10 2 +000413 * PREAUTH Ready.
+000414 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000415 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000416 * 0 EXISTS
+000417 * 0 RECENT
+000418 * OK [UIDVALIDITY] Ok
+000419 * OK [MYRIGHTS "acdilrsw"] ACL
+000420 d001 OK [READ-WRITE] Ok
+000421 + OK
+000422 d002 OK APPEND Ok.
+000423 * 1 EXISTS
+000424 * 0 RECENT
+000425 d003 OK NOOP completed
+000426 d004 OK COPY completed.
+000427 * 2 EXISTS
+000428 * 0 RECENT
+000429 d005 OK NOOP completed
+000430 * BYE Courier-IMAP server shutting down
+000431 ddone OK LOGOUT completed
+000432 10000S,5C +000433 5 1 +000434 5 1 +000435 * PREAUTH Ready.
+000436 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000437 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000438 * 2 EXISTS
+000439 * 0 RECENT
+000440 * OK [UIDVALIDITY] Ok
+000441 * OK [MYRIGHTS "acdilrsw"] ACL
+000442 e001 OK [READ-WRITE] Ok
+000443 e002 OK COPY completed.
+000444 * 4 EXISTS
+000445 * 0 RECENT
+000446 e003 OK NOOP completed
+000447 * BYE Courier-IMAP server shutting down
+000448 edone OK LOGOUT completed
+000449 10000S,5C +000450 5 1 +000451 5 1 +000452 10 2 +000453 * PREAUTH Ready.
+000454 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000455 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000456 * 4 EXISTS
+000457 * 0 RECENT
+000458 * OK [UIDVALIDITY] Ok
+000459 * OK [MYRIGHTS "acdilrsw"] ACL
+000460 f001 OK [READ-WRITE] Ok
+000461 f002 NO [ALERT] You exceeded your mail quota.
+000462 f002 OK COPY completed.
+000463 f003 OK NOOP completed
+000464 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000465 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000466 * 2 EXISTS
+000467 * 0 RECENT
+000468 * OK [UIDVALIDITY] Ok
+000469 * OK [MYRIGHTS "acdilrsw"] ACL
+000470 f004 OK [READ-WRITE] Ok
+000471 f005 NO [ALERT] You exceeded your mail quota.
+000472 f006 OK COPY completed.
+000473 * 4 EXISTS
+000474 * 0 RECENT
+000475 f007 OK NOOP completed
+000476 f008 OK COPY completed.
+000477 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000478 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000479 * 5 EXISTS
+000480 * 0 RECENT
+000481 * OK [UIDVALIDITY] Ok
+000482 * OK [MYRIGHTS "acdilrsw"] ACL
+000483 f009 OK [READ-WRITE] Ok
+000484 * BYE Courier-IMAP server shutting down
+000485 fdone OK LOGOUT completed
+000486 10000S,5C +000487 20 4 +000488 5 1 +000489 Counts: 5 4 +000490 * PREAUTH Ready.
+000491 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000492 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000493 * 5 EXISTS
+000494 * 0 RECENT
+000495 * OK [UIDVALIDITY] Ok
+000496 * OK [MYRIGHTS "acdilrsw"] ACL
+000497 g001 OK [READ-WRITE] Ok
+000498 g002 NO Mailbox does not exist, or must be subscribed to.
+000499 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000500 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000501 * 4 EXISTS
+000502 * 0 RECENT
+000503 * OK [UIDVALIDITY] Ok
+000504 * OK [MYRIGHTS "acdilrsw"] ACL
+000505 g003 OK [READ-WRITE] Ok
+000506 g004 NO Cannot delete currently-open folder.
+000507 g005 OK mailbox closed.
+000508 g006 NO Cannot delete this folder.
+000509 g007 OK "INBOX.a" created.
+000510 g008 NO Mailbox does not exist, or must be subscribed to.
+000511 g009 OK Folder deleted.
+000512 g010 NO Invalid mailbox name.
+000513 * BYE Courier-IMAP server shutting down
+000514 gdone OK LOGOUT completed
+000515 * PREAUTH Ready.
+000516 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000516 * LIST (\HasNoChildren) "." "shared.test.a"
+000516 * LIST (\Marked \HasChildren) "." "INBOX"
+000516 * LIST (\Noselect \HasChildren) "." "shared"
+000516 * LIST (\Noselect \HasChildren) "." "shared.test"
+000516 h001 OK LIST completed
+000517 * LIST (\Marked \HasChildren) "." "INBOX"
+000517 * LIST (\Noselect \HasChildren) "." "shared"
+000517 h002 OK LIST completed
+000518 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000518 * LIST (\Noselect \HasChildren) "." "shared.test"
+000518 h003 OK LIST completed
+000519 * LIST (\HasNoChildren) "." "shared.test.a"
+000519 h004 OK LIST completed
+000520 * LIST (\HasNoChildren) "." "shared.test.a"
+000520 * LIST (\Noselect \HasChildren) "." "shared.test"
+000520 h005 OK LIST completed
+000521 * LIST (\HasNoChildren) "." "shared.test.a"
+000521 h006 OK LIST completed
+000522 * LIST (\Marked \HasChildren) "." ""
+000522 h007 OK LIST completed
+000523 * LIST (\Noselect \HasChildren) "." ""
+000523 h008 OK LIST completed
+000524 * LIST (\Noselect \HasChildren) "." ""
+000524 h009 OK LIST completed
+000525 h010 OK Folder subscribed.
+000526 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000526 * LIST (\HasNoChildren) "." "shared.test.a"
+000526 * LIST (\Marked \HasChildren) "." "INBOX"
+000526 * LIST (\Noselect \HasChildren) "." "shared"
+000526 * LIST (\Noselect \HasChildren) "." "shared.test"
+000526 h011 OK LIST completed
+000527 * LSUB (\Marked \HasNoChildren) "." "INBOX"
+000527 h012 OK LSUB completed
+000528 h013 OK SUBSCRIBE completed.
+000529 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000529 * LIST (\HasNoChildren) "." "shared.test.a"
+000529 * LIST (\Marked \HasChildren) "." "INBOX"
+000529 * LIST (\Noselect \HasChildren) "." "shared"
+000529 * LIST (\Noselect \HasChildren) "." "shared.test"
+000529 h014 OK LIST completed
+000530 * LSUB (\HasNoChildren) "." "shared.test.a"
+000530 * LSUB (\Marked \HasNoChildren) "." "INBOX"
+000530 * LSUB (\Noselect \HasChildren) "." "shared"
+000530 * LSUB (\Noselect \HasChildren) "." "shared.test"
+000530 h015 OK LSUB completed
+000531 h016 OK SUBSCRIBE completed.
+000532 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000532 * LIST (\HasNoChildren) "." "shared.test.a"
+000532 * LIST (\Marked \HasChildren) "." "INBOX"
+000532 * LIST (\Noselect \HasChildren) "." "shared"
+000532 * LIST (\Noselect \HasChildren) "." "shared.test"
+000532 h017 OK LIST completed
+000533 * LSUB (\HasNoChildren) "." "shared.test.a"
+000533 * LSUB (\HasNoChildren) "." "shared.test.b"
+000533 * LSUB (\Marked \HasNoChildren) "." "INBOX"
+000533 * LSUB (\Noselect \HasChildren) "." "shared"
+000533 * LSUB (\Noselect \HasChildren) "." "shared.test"
+000533 h018 OK LSUB completed
+000534 h019 OK UNSUBSCRIBE completed.
+000535 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000535 * LIST (\HasNoChildren) "." "shared.test.a"
+000535 * LIST (\Marked \HasChildren) "." "INBOX"
+000535 * LIST (\Noselect \HasChildren) "." "shared"
+000535 * LIST (\Noselect \HasChildren) "." "shared.test"
+000535 h020 OK LIST completed
+000536 * LSUB (\HasNoChildren) "." "shared.test.b"
+000536 * LSUB (\Marked \HasNoChildren) "." "INBOX"
+000536 * LSUB (\Noselect \HasChildren) "." "shared"
+000536 * LSUB (\Noselect \HasChildren) "." "shared.test"
+000536 h021 OK LSUB completed
+000537 h022 OK SUBSCRIBE completed.
+000538 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000538 * LIST (\HasNoChildren) "." "shared.test.a"
+000538 * LIST (\Marked \HasChildren) "." "INBOX"
+000538 * LIST (\Noselect \HasChildren) "." "shared"
+000538 * LIST (\Noselect \HasChildren) "." "shared.test"
+000538 h023 OK LIST completed
+000539 * LSUB (\HasNoChildren) "." "shared.test.a"
+000539 * LSUB (\HasNoChildren) "." "shared.test.b"
+000539 * LSUB (\Marked \HasNoChildren) "." "INBOX"
+000539 * LSUB (\Noselect \HasChildren) "." "shared"
+000539 * LSUB (\Noselect \HasChildren) "." "shared.test"
+000539 h024 OK LSUB completed
+000540 * BYE Courier-IMAP server shutting down
+000541 hdone OK LOGOUT completed
+000542 * PREAUTH Ready.
+000543 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000543 * LIST (\HasNoChildren) "." "INBOX.c"
+000543 * LIST (\HasNoChildren) "." "shared.test.Trash"
+000543 * LIST (\HasNoChildren) "." "shared.test.a"
+000543 * LIST (\Marked \HasChildren) "." "INBOX"
+000543 * LIST (\Noselect \HasChildren) "." "shared"
+000543 * LIST (\Noselect \HasChildren) "." "shared.test"
+000543 ii001 OK LIST completed
+000544 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000544 * LIST (\HasNoChildren) "." "INBOX.c"
+000544 * LIST (\HasNoChildren) "." "shared.test.Trash"
+000544 * LIST (\HasNoChildren) "." "shared.test.a"
+000544 * LIST (\Marked \HasChildren) "." "INBOX"
+000544 * LIST (\Noselect \HasChildren) "." "shared"
+000544 * LIST (\Noselect \HasChildren) "." "shared.test"
+000544 ii002 OK LIST completed
+000545 * BYE Courier-IMAP server shutting down
+000546 iidone OK LOGOUT completed
+000547 * PREAUTH Ready.
+000548 * LIST (\Marked \HasChildren) "." "INBOX"
+000548 * LIST (\Marked \HasNoChildren) "." "INBOX.Trash"
+000548 * LIST (\Marked \HasNoChildren) "." "shared.test.a"
+000548 * LIST (\Noselect \HasChildren) "." "shared"
+000548 * LIST (\Noselect \HasChildren) "." "shared.test"
+000548 * LIST (\Unmarked \HasNoChildren) "." "INBOX.c"
+000548 * LIST (\Unmarked \HasNoChildren) "." "shared.test.Trash"
+000548 ii003 OK LIST completed
+000549 * LIST (\Marked \HasChildren) "." "INBOX"
+000549 * LIST (\Marked \HasNoChildren) "." "INBOX.Trash"
+000549 * LIST (\Marked \HasNoChildren) "." "shared.test.a"
+000549 * LIST (\Noselect \HasChildren) "." "shared"
+000549 * LIST (\Noselect \HasChildren) "." "shared.test"
+000549 * LIST (\Unmarked \HasNoChildren) "." "INBOX.c"
+000549 * LIST (\Unmarked \HasNoChildren) "." "shared.test.Trash"
+000549 ii004 OK LIST completed
+000550 * BYE Courier-IMAP server shutting down
+000551 iidone OK LOGOUT completed
+000552 * PREAUTH Ready.
+000553 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000554 * OK [PERMANENTFLAGS ()] No permanent flags permitted
+000555 * 0 EXISTS
+000556 * 0 RECENT
+000557 * OK [UIDVALIDITY] Ok
+000558 * OK [MYRIGHTS "dilrsw"] ACL
+000559 i001 OK [READ-ONLY] Ok
+000560 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000561 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000562 * 2 EXISTS
+000563 * 2 RECENT
+000564 * OK [UIDVALIDITY] Ok
+000565 * OK [MYRIGHTS "dilrsw"] ACL
+000566 i002 OK [READ-WRITE] Ok
+000567 i003 OK mailbox closed.
+000568 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000569 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000570 * 2 EXISTS
+000571 * 0 RECENT
+000572 * OK [UIDVALIDITY] Ok
+000573 * OK [MYRIGHTS "dilrsw"] ACL
+000574 i004 OK [READ-WRITE] Ok
+000575 + OK
+000576 i005 OK APPEND Ok.
+000577 * 3 EXISTS
+000578 * 1 RECENT
+000579 i006 OK NOOP completed
+000580 * 1 FETCH (FLAGS ())
+000581 * 2 FETCH (FLAGS ())
+000582 * 3 FETCH (FLAGS (\Recent))
+000583 i007 OK FETCH completed.
+000584 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000585 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000586 * 6 EXISTS
+000587 * 1 RECENT
+000588 * OK [UIDVALIDITY] Ok
+000589 * OK [MYRIGHTS "acdilrsw"] ACL
+000590 i008 OK [READ-WRITE] Ok
+000591 i009 OK COPY completed.
+000592 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000593 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000594 * 5 EXISTS
+000595 * 2 RECENT
+000596 * OK [UIDVALIDITY] Ok
+000597 * OK [MYRIGHTS "dilrsw"] ACL
+000598 i010 OK [READ-WRITE] Ok
+000599 * 1 FETCH (FLAGS ())
+000600 * 2 FETCH (FLAGS ())
+000601 * 3 FETCH (FLAGS ())
+000602 * 4 FETCH (FLAGS (\Recent))
+000603 * 5 FETCH (FLAGS (\Recent))
+000604 i011 OK FETCH completed.
+000605 i012 NO [ALERT] Cannot create message - no write permission or out of disk space.
+000606 i013 NO [ALERT] COPY failed - no write permission or out of disk space.
+000607 * 1 FETCH (FLAGS (\Deleted))
+000608 * 2 FETCH (FLAGS (\Deleted))
+000609 i014 OK STORE completed.
+000610 * 1 EXPUNGE
+000611 * 1 EXPUNGE
+000612 * 3 EXISTS
+000613 * 2 RECENT
+000614 i015 OK EXPUNGE completed
+000615 * BYE Courier-IMAP server shutting down
+000616 idone OK LOGOUT completed
+000617 * PREAUTH Ready.
+000618 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000619 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000620 * 4 EXISTS
+000621 * 0 RECENT
+000622 * OK [UIDVALIDITY] Ok
+000623 * OK [MYRIGHTS "acdilrsw"] ACL
+000624 T001 OK [READ-WRITE] Ok
+000625 * 1 FETCH (UID 7 BODY[HEADER.FIELDS ("date")] {41}
+000626 Date: Wed, 22 Sep 1999 15:41:09 -0200
+000627
+000628 )
+000629 * 1 FETCH (FLAGS (\Seen))
+000630 * 2 FETCH (UID 8 BODY[HEADER.FIELDS ("date")] {41}
+000631 Date: Wed, 22 Sep 1999 15:41:00 -0200
+000632
+000633 )
+000634 * 2 FETCH (FLAGS (\Seen))
+000635 * 3 FETCH (UID 9 BODY[HEADER.FIELDS ("date")] {41}
+000636 Date: Wed, 22 Sep 1999 15:41:00 -0200
+000637
+000638 )
+000639 * 3 FETCH (FLAGS (\Seen))
+000640 * 4 FETCH (UID 10 BODY[HEADER.FIELDS ("date")] {41}
+000641 Date: Wed, 15 Sep 1999 15:41:00 -0200
+000642
+000643 )
+000644 * 4 FETCH (FLAGS (\Seen))
+000645 T002 OK FETCH completed.
+000646 * THREAD (4 3 1)(2)
+000647 T003 OK THREAD done.
+000648 * THREAD (3 1)(2)
+000649 T004 OK THREAD done.
+000650 * SORT 4 3 1 2
+000651 S001 OK SORT done.
+000652 * SORT 1 3 4 2
+000653 S002 OK SORT done.
+000654 * SORT 2 4 3 1
+000655 S003 OK SORT done.
+000656 * SORT 1 2 3 4
+000657 S004 OK SORT done.
+000658 * SORT 4 3 2 1
+000659 S005 OK SORT done.
+000660 * SORT 1 2 3
+000661 S006 OK SORT done.
+000662 * SORT 3 2 1
+000663 S007 OK SORT done.
+000664 * SORT 4 3 2 1
+000665 S008 OK SORT done.
+000666 * SORT 1 2 3 4
+000667 S009 OK SORT done.
+000668 * SORT 4 3 2 1
+000669 S010 OK SORT done.
+000670 * BYE Courier-IMAP server shutting down
+000671 TDONE OK LOGOUT completed
+000672 * PREAUTH Ready.
+000673 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000673 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000673 J001 OK LIST completed
+000674 J002 OK LSUB completed
+000675 J003 OK Folder subscribed.
+000676 J004 OK Folder subscribed.
+000677 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000677 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000677 J005 OK LIST completed
+000678 * LSUB (\HasNoChildren) "." "INBOX.Trash"
+000678 * LSUB (\Unmarked \HasChildren) "." "INBOX"
+000678 J006 OK LSUB completed
+000679 J007 OK Folder unsubscribed.
+000680 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000680 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000680 J008 OK LIST completed
+000681 * LSUB (\HasNoChildren) "." "INBOX.Trash"
+000681 * LSUB (\Noselect \HasChildren) "." "INBOX"
+000681 J009 OK LSUB completed
+000682 * BYE Courier-IMAP server shutting down
+000683 JDONE OK LOGOUT completed
+000684 * PREAUTH Ready.
+000685 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000686 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000687 * 6 EXISTS
+000688 * 0 RECENT
+000689 * OK [UIDVALIDITY] Ok
+000690 * OK [MYRIGHTS "acdilrsw"] ACL
+000691 K001 OK [READ-WRITE] Ok
+000692 * THREAD ((6)(5))(1 (2 3)(4))
+000693 K002 OK THREAD done.
+000694 * BYE Courier-IMAP server shutting down
+000695 KDONE OK LOGOUT completed
+000696 * PREAUTH Ready.
+000697 L001 OK "INBOX.a" created.
+000698 L002 OK "INBOX.a.a" created.
+000699 L003 OK "INBOX.aa" created.
+000700 L004 OK Folder renamed.
+000701 * LIST (\HasChildren) "." "INBOX.b"
+000701 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000701 * LIST (\HasNoChildren) "." "INBOX.aa"
+000701 * LIST (\HasNoChildren) "." "INBOX.b.a"
+000701 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000701 L005 OK LIST completed
+000702 L006 OK Folder renamed.
+000703 * LIST (\HasChildren) "." "INBOX.a"
+000703 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000703 * LIST (\HasNoChildren) "." "INBOX.a.a"
+000703 * LIST (\HasNoChildren) "." "INBOX.aa"
+000703 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000703 L007 OK LIST completed
+000704 L008 OK Folder renamed.
+000705 * LIST (\HasChildren) "." "INBOX.b"
+000705 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000705 * LIST (\HasNoChildren) "." "INBOX.aa"
+000705 * LIST (\HasNoChildren) "." "INBOX.b.a"
+000705 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000705 L009 OK LIST completed
+000706 L010 OK Folder renamed.
+000707 * LIST (\HasChildren) "." "INBOX.a"
+000707 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000707 * LIST (\HasNoChildren) "." "INBOX.a.a"
+000707 * LIST (\HasNoChildren) "." "INBOX.aa"
+000707 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000707 L011 OK LIST completed
+000708 L012 OK "INBOX.b.a" created.
+000709 L013 OK Folder deleted.
+000710 L014 NO Cannot create this folder.
+000711 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000711 * LIST (\HasNoChildren) "." "INBOX.a.a"
+000711 * LIST (\HasNoChildren) "." "INBOX.aa"
+000711 * LIST (\HasNoChildren) "." "INBOX.b.a"
+000711 * LIST (\Noselect \HasChildren) "." "INBOX.a"
+000711 * LIST (\Noselect \HasChildren) "." "INBOX.b"
+000711 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000711 L015 OK LIST completed
+000712 L016 NO RENAME failed: File exists
+000713 L017 NO RENAME failed: File exists
+000714 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000715 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000716 * 0 EXISTS
+000717 * 0 RECENT
+000718 * OK [UIDVALIDITY] Ok
+000719 * OK [MYRIGHTS "acdilrsw"] ACL
+000720 L018 OK [READ-WRITE] Ok
+000721 + OK
+000722 L019 OK APPEND Ok.
+000723 * 1 EXISTS
+000724 * 0 RECENT
+000725 L020 OK NOOP completed
+000726 L021 OK COPY completed.
+000727 L022 OK COPY completed.
+000728 L023 OK COPY completed.
+000729 L024 OK COPY completed.
+000730 L025 OK COPY completed.
+000731 L026 OK COPY completed.
+000732 * 7 EXISTS
+000733 * 0 RECENT
+000734 L027 OK NOOP completed
+000735 * 2 FETCH (FLAGS (\Seen \Deleted))
+000736 * 3 FETCH (FLAGS (\Seen \Deleted))
+000737 * 5 FETCH (FLAGS (\Seen \Deleted))
+000738 * 6 FETCH (FLAGS (\Seen \Deleted))
+000739 * 7 FETCH (FLAGS (\Seen \Deleted))
+000740 L028 OK STORE completed.
+000741 * 1 FETCH (UID 1 FLAGS (\Seen))
+000742 * 2 FETCH (UID 2 FLAGS (\Seen \Deleted))
+000743 * 3 FETCH (UID 3 FLAGS (\Seen \Deleted))
+000744 * 4 FETCH (UID 4 FLAGS (\Seen))
+000745 * 5 FETCH (UID 5 FLAGS (\Seen \Deleted))
+000746 * 6 FETCH (UID 6 FLAGS (\Seen \Deleted))
+000747 * 7 FETCH (UID 7 FLAGS (\Seen \Deleted))
+000748 L029 OK FETCH completed.
+000749 * 2 EXPUNGE
+000750 * 2 EXPUNGE
+000751 * 3 EXPUNGE
+000752 * 4 EXISTS
+000753 * 0 RECENT
+000754 L030 OK EXPUNGE completed
+000755 * 1 FETCH (UID 1 FLAGS (\Seen))
+000756 * 2 FETCH (UID 4 FLAGS (\Seen))
+000757 * 3 FETCH (UID 6 FLAGS (\Seen \Deleted))
+000758 * 4 FETCH (UID 7 FLAGS (\Seen \Deleted))
+000759 L031 OK FETCH completed.
+000760 * BYE Courier-IMAP server shutting down
+000761 LDONE OK LOGOUT completed
+000762 * PREAUTH Ready.
+000763 M001 OK "INBOX.a" created.
+000764 + OK
+000765 M002 OK APPEND Ok.
+000766 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000767 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000768 * 1 EXISTS
+000769 * 0 RECENT
+000770 * OK [UIDVALIDITY] Ok
+000771 * OK [MYRIGHTS "acdilrsw"] ACL
+000772 M003 OK [READ-WRITE] Ok
+000773 M004 OK COPY completed.
+000774 M005 OK COPY completed.
+000775 * 3 EXISTS
+000776 * 0 RECENT
+000777 M006 OK NOOP completed
+000778 M007 OK COPY completed.
+000779 M008 OK Folder renamed.
+000780 * BYE Courier-IMAP server shutting down
+000781 MDONE OK LOGOUT completed
+000782 10C +000783 30 6 +000784 * PREAUTH Ready.
+000785 M009 OK Folder deleted.
+000786 * BYE Courier-IMAP server shutting down
+000787 MDONE OK LOGOUT completed
+000788 10C +000789 15 3 +000790 * PREAUTH Ready.
+000791 * LIST (\Unmarked \HasNoChildren) "." "INBOX"
+000791 N001 OK LIST completed
+000792 + OK
+000793 N002 OK APPEND Ok.
+000794 * LIST (\Marked \HasNoChildren) "." "INBOX"
+000794 N003 OK LIST completed
+000795 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000796 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000797 * 1 EXISTS
+000798 * 0 RECENT
+000799 * OK [UIDVALIDITY] Ok
+000800 * OK [MYRIGHTS "acdilrsw"] ACL
+000801 N004 OK [READ-WRITE] Ok
+000802 * 1 EXPUNGE
+000803 * 0 EXISTS
+000804 * 0 RECENT
+000805 N005 OK EXPUNGE completed
+000806 * LIST (\HasNoChildren) "." "INBOX.Trash"
+000806 * LIST (\Unmarked \HasChildren) "." "INBOX"
+000806 N006 OK LIST completed
+000807 * BYE Courier-IMAP server shutting down
+000808 NDONE OK LOGOUT completed
+000809 * PREAUTH Ready.
+000810 + OK
+000811 O001 OK APPEND Ok.
+000812 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000813 * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited
+000814 * 1 EXISTS
+000815 * 0 RECENT
+000816 * OK [UIDVALIDITY] Ok
+000817 * OK [MYRIGHTS "acdilrsw"] ACL
+000818 o002 OK [READ-WRITE] Ok
+000819 o003 OK COPY completed.
+000820 * 2 EXISTS
+000821 * 0 RECENT
+000822 o004 OK NOOP completed
+000823 * 1 FETCH (FLAGS (\Deleted))
+000824 o005 OK STORE completed.
+000825 o006 OK COPY completed.
+000826 * 4 EXISTS
+000827 * 0 RECENT
+000828 o007 OK NOOP completed
+000829 * BYE Courier-IMAP server shutting down
+000830 o008 OK LOGOUT completed
+000831 10C +000832 0 0 +000833 11 1 +000834 11 1 +000835 -11 -1 +000836 11 1 +000837 * PREAUTH Ready.
+000838 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000839 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000840 * 4 EXISTS
+000841 * 0 RECENT
+000842 * OK [UIDVALIDITY] Ok
+000843 * OK [MYRIGHTS "acdilrsw"] ACL
+000844 p001 OK [READ-WRITE] Ok
+000845 p002 OK "INBOX.a" created.
+000846 p003 OK COPY completed.
+000847 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000848 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000849 * 1 EXISTS
+000850 * 0 RECENT
+000851 * OK [UIDVALIDITY] Ok
+000852 * OK [MYRIGHTS "acdilrsw"] ACL
+000853 p004 OK [READ-WRITE] Ok
+000854 * FLAGS (-Label1 \Draft \Answered \Flagged \Deleted \Seen \Recent)
+000855 * OK [PERMANENTFLAGS (-Label1 \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000856 * 1 FETCH (FLAGS (\Deleted -Label1))
+000857 p005 OK STORE completed.
+000858 + OK
+000859 p006 OK APPEND Ok.
+000860 * 1 EXPUNGE
+000861 * FLAGS (-Label1 -Label2 \Draft \Answered \Flagged \Deleted \Seen \Recent)
+000862 * OK [PERMANENTFLAGS (-Label1 -Label2 \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000863 * 1 EXISTS
+000864 * 0 RECENT
+000865 * FLAGS (-Label2 \Draft \Answered \Flagged \Deleted \Seen \Recent)
+000866 * OK [PERMANENTFLAGS (-Label2 \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000867 p007 OK EXPUNGE completed
+000868 * 1 FETCH (FLAGS (-Label2))
+000869 p008 OK FETCH completed.
+000870 p009 OK COPY completed.
+000871 * 2 EXISTS
+000872 * 0 RECENT
+000873 p010 OK NOOP completed
+000874 * 1 FETCH (FLAGS (-Label2))
+000875 * 2 FETCH (FLAGS (-Label2))
+000876 P011 OK FETCH completed.
+000877 * 1 FETCH (FLAGS ())
+000878 P012 OK STORE completed.
+000879 * 1 FETCH (FLAGS (\Seen))
+000880 P013 OK STORE completed.
+000881 * 1 FETCH (FLAGS (\Seen))
+000882 * 2 FETCH (FLAGS (-Label2))
+000883 P013 OK FETCH completed.
+000884 * SEARCH 1
+000885 P014 OK SEARCH done.
+000886 * SEARCH 2
+000887 P015 OK SEARCH done.
+000888 * SEARCH 1
+000889 P016 OK SEARCH done.
+000890 * SEARCH
+000891 P017 OK SEARCH done.
+000892 P018 OK COPY completed.
+000893 P019 OK COPY completed.
+000894 * 6 EXISTS
+000895 * 0 RECENT
+000896 P020 OK NOOP completed
+000897 P021 OK COPY completed.
+000898 P022 OK COPY completed.
+000899 * 18 EXISTS
+000900 * 0 RECENT
+000901 P023 OK NOOP completed
+000902 * FLAGS (-Label1 -Label2 \Draft \Answered \Flagged \Deleted \Seen \Recent)
+000903 * OK [PERMANENTFLAGS (-Label1 -Label2 \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000904 * 4 FETCH (FLAGS (-Label1 -Label2))
+000905 P025 OK STORE completed.
+000906 * 1 FETCH (FLAGS (\Seen))
+000907 * 2 FETCH (FLAGS (-Label2))
+000908 * 3 FETCH (FLAGS (\Seen))
+000909 * 4 FETCH (FLAGS (-Label1 -Label2))
+000910 * 5 FETCH (FLAGS (\Seen))
+000911 * 6 FETCH (FLAGS (-Label2))
+000912 * 7 FETCH (FLAGS (\Seen))
+000913 * 8 FETCH (FLAGS (-Label2))
+000914 * 9 FETCH (FLAGS (\Seen))
+000915 * 10 FETCH (FLAGS (-Label2))
+000916 * 11 FETCH (FLAGS (\Seen))
+000917 * 12 FETCH (FLAGS (-Label2))
+000918 * 13 FETCH (FLAGS (\Seen))
+000919 * 14 FETCH (FLAGS (-Label2))
+000920 * 15 FETCH (FLAGS (\Seen))
+000921 * 16 FETCH (FLAGS (-Label2))
+000922 * 17 FETCH (FLAGS (\Seen))
+000923 * 18 FETCH (FLAGS (-Label2))
+000924 P024 OK FETCH completed.
+000925 * SEARCH 2 4 6 8 10 12 14 16 18
+000926 P025 OK SEARCH done.
+000927 * SEARCH 4
+000928 P026 OK SEARCH done.
+000929 * BYE Courier-IMAP server shutting down
+000930 pDONE OK LOGOUT completed
+000931 * PREAUTH Ready.
+000932 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+000933 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+000934 * 4 EXISTS
+000935 * 0 RECENT
+000936 * OK [UIDVALIDITY] Ok
+000937 * OK [MYRIGHTS "acdilrsw"] ACL
+000938 Q001 OK [READ-WRITE] Ok
+000939 * 1 FETCH (FLAGS (\Deleted))
+000940 * 2 FETCH (FLAGS ())
+000941 * 3 FETCH (FLAGS (\Deleted))
+000942 * 4 FETCH (FLAGS ())
+000943 Q002 OK FETCH completed.
+000944 * STATUS "INBOX.Trash" (MESSAGES 1)
+000945 Q003 OK STATUS Completed.
+000946 * 1 EXPUNGE
+000947 * 2 EXPUNGE
+000948 * 2 EXISTS
+000949 * 0 RECENT
+000950 Q004 OK EXPUNGE completed
+000951 * STATUS "INBOX.Trash" (MESSAGES 3)
+000952 Q005 OK STATUS Completed.
+000953 Q006 OK COPY completed.
+000954 * 1 FETCH (FLAGS (\Deleted))
+000955 * 2 FETCH (FLAGS (\Deleted))
+000956 Q007 OK STORE completed.
+000957 Q008 OK NOOP completed
+000958 * 1 EXPUNGE
+000959 * 1 EXPUNGE
+000960 * 0 EXISTS
+000961 * 0 RECENT
+000962 Q009 OK EXPUNGE completed
+000963 * STATUS "INBOX.Trash" (MESSAGES 4)
+000964 Q005 OK STATUS Completed.
+000965 * BYE Courier-IMAP server shutting down
+000966 qDONE OK LOGOUT completed
+000967 * PREAUTH Ready.
+000968 R001 OK "INBOX.a" created.
+000969 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")))("MYRIGHTS" "acdilrsw")(POSTADDRESS NIL))
+000969 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")))("MYRIGHTS" "acdilrsw")(POSTADDRESS NIL))
+000969 R002 OK LIST completed
+000970 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "aceilrswx")("administrators" "acdilrsw"))))
+000970 R003 OK ACL STORE completed.
+000971 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000971 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000971 R004 OK ACL STORE completed.
+000972 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser1" "alr"))))
+000972 R005 OK ACL STORE completed.
+000973 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser1" "alrw"))))
+000973 R006 OK ACL STORE completed.
+000974 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser1" "alr"))))
+000974 R007 OK ACL STORE completed.
+000975 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000975 R008 OK ACL STORE completed.
+000976 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser1" "alr"))))
+000976 R009 OK ACL STORE completed.
+000977 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000977 R010 OK ACL STORE completed.
+000978 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser1" "alr"))))
+000978 R011 OK ACL STORE completed.
+000979 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+000979 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+000979 R012 OK ACL SET completed.
+000980 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+000980 R013 OK ACL DELETE completed.
+000981 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000981 R014 OK ACL DELETE completed.
+000982 * RIGHTS-INFO "INBOX.a" "owner" al c e i p r s t w x
+000983 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000983 R015 OK ACL STORE completed.
+000984 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("-owner" "tw"))))
+000984 R016 OK ACL STORE completed.
+000985 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("-owner" "t"))))
+000985 R017 OK ACL STORE completed.
+000986 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000986 R018 OK ACL DELETE completed.
+000987 * RIGHTS-INFO "INBOX.a" "owner" al c e i p r s t w x
+000988 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+000988 R019 OK ACL STORE completed.
+000989 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "adlrsw")("administrators" "acdilrsw"))))
+000989 R020 OK ACL STORE completed.
+000990 R021 NO Access denied for CREATE on INBOX.a (ACL "c" required)
+000991 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+000991 R022 OK ACL STORE completed.
+000992 R023 OK "INBOX.a.b" created.
+000993 * LIST (\HasNoChildren) "." "INBOX.a.b" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+000993 R024 OK LIST completed
+000994 R025 OK "INBOX.c.d" created.
+000995 * LIST (\HasNoChildren) "." "INBOX.c.d" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+000995 * LIST (\Noselect \HasChildren) "." "INBOX.c" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+000995 R026 OK LIST completed
+000996 * LIST (\HasNoChildren) "." "INBOX.c.d" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr")("-owner" "x"))))
+000996 * LIST (\Noselect \HasChildren) "." "INBOX.c" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr")("-owner" "x"))))
+000996 R027 OK ACL STORE completed.
+000997 R028 NO Access denied for DELETE on INBOX.c.d (ACL "x" required)
+000998 * LIST (\HasNoChildren) "." "INBOX.c.d" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+000998 R029 OK ACL DELETE completed.
+000999 R030 OK Folder deleted.
+001000 R031 OK Folder directory delete punted.
+001001 * LIST (\HasChildren) "." "INBOX.a" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001001 * LIST (\HasNoChildren) "." "INBOX.a.b" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001001 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001001 R032 OK LIST completed
+001002 R033 OK Folder deleted.
+001003 * LIST (\HasNoChildren) "." "INBOX.a.b" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001003 * LIST (\Noselect \HasChildren) "." "INBOX.a" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001003 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001003 R034 OK LIST completed
+001004 R035 OK Folder renamed.
+001005 * LIST (\HasNoChildren) "." "INBOX.f.b" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001005 * LIST (\Noselect \HasChildren) "." "INBOX.f" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001005 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001005 R036 OK LIST completed
+001006 R037 OK "INBOX.f" created.
+001007 * LIST (\HasChildren) "." "INBOX.f" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001007 * LIST (\HasNoChildren) "." "INBOX.f.b" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw"))))
+001007 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001007 R038 OK LIST completed
+001008 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdlrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001008 R039 OK ACL STORE completed.
+001009 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001010 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001011 * 0 EXISTS
+001012 * 0 RECENT
+001013 * OK [UIDVALIDITY] Ok
+001014 * OK [MYRIGHTS "acdlrsw"] ACL
+001015 R040 OK [READ-WRITE] Ok
+001016 R041 NO Access denied for APPEND on INBOX (ACL "i" required)
+001017 * OK [MYRIGHTS "acdilrsw"] ACL
+001018 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001018 R042 OK ACL STORE completed.
+001019 + OK
+001020 R043 OK APPEND Ok.
+001021 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001022 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001023 * 1 EXISTS
+001024 * 0 RECENT
+001025 R043 OK NOOP completed
+001026 * 1 FETCH (FLAGS (\Answered \Seen \Deleted Foo))
+001027 R044 OK FETCH completed.
+001028 * OK [MYRIGHTS "aceilrsx"] ACL
+001029 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "aceilrsx")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001029 R045 OK ACL STORE completed.
+001030 + OK
+001031 R046 OK APPEND Ok.
+001032 * 2 EXISTS
+001033 * 0 RECENT
+001034 R047 OK NOOP completed
+001035 * 2 FETCH (FLAGS (\Seen))
+001036 R048 OK FETCH completed.
+001037 * OK [MYRIGHTS "acdilrsw"] ACL
+001038 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001038 R049 OK ACL STORE completed.
+001039 * OK [MYRIGHTS "acdilrw"] ACL
+001040 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001040 R050 OK ACL STORE completed.
+001041 + OK
+001042 R051 OK APPEND Ok.
+001043 * 3 EXISTS
+001044 * 0 RECENT
+001045 R052 OK NOOP completed
+001046 * 3 FETCH (FLAGS (\Answered \Deleted Foo))
+001047 R053 OK FETCH completed.
+001048 * 2 FETCH (FLAGS (\Seen \Deleted))
+001049 * 3 FETCH (FLAGS (\Answered \Deleted Foo))
+001050 R054 OK STORE completed.
+001051 * 1 FETCH (FLAGS (\Answered \Seen Foo))
+001052 R055 OK STORE completed.
+001053 * 2 EXPUNGE
+001054 * 2 EXPUNGE
+001055 * 1 EXISTS
+001056 * 0 RECENT
+001057 R056 OK EXPUNGE completed
+001058 * 1 FETCH (FLAGS (\Answered \Seen \Deleted Foo))
+001059 R057 OK STORE completed.
+001060 * OK [MYRIGHTS "acdilrsw"] ACL
+001061 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001061 R058 OK ACL STORE completed.
+001062 R059 OK "INBOX.x" created.
+001063 R060 OK COPY completed.
+001064 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001065 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001066 * 1 EXISTS
+001067 * 0 RECENT
+001068 * OK [UIDVALIDITY] Ok
+001069 * OK [MYRIGHTS "acdilrsw"] ACL
+001070 R061 OK [READ-WRITE] Ok
+001071 * 1 FETCH (FLAGS (\Answered \Seen \Deleted Foo))
+001072 R062 OK FETCH completed.
+001073 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001074 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001075 * 1 EXISTS
+001076 * 0 RECENT
+001077 * OK [UIDVALIDITY] Ok
+001078 * OK [MYRIGHTS "acdilrsw"] ACL
+001079 R063 OK [READ-WRITE] Ok
+001080 * LIST (\HasNoChildren) "." "INBOX.x" (("ACL" (("owner" "acdilrw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001080 R064 OK ACL STORE completed.
+001081 R065 OK COPY completed.
+001082 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001083 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001084 * 2 EXISTS
+001085 * 0 RECENT
+001086 * OK [UIDVALIDITY] Ok
+001087 * OK [MYRIGHTS "acdilrw"] ACL
+001088 R066 OK [READ-WRITE] Ok
+001089 * 2 FETCH (FLAGS (\Answered \Deleted Foo))
+001090 R067 OK FETCH completed.
+001091 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001092 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001093 * 1 EXISTS
+001094 * 0 RECENT
+001095 * OK [UIDVALIDITY] Ok
+001096 * OK [MYRIGHTS "acdilrsw"] ACL
+001097 R068 OK [READ-WRITE] Ok
+001098 * LIST (\HasNoChildren) "." "INBOX.x" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001098 R069 OK ACL STORE completed.
+001099 * LIST (\HasNoChildren) "." "INBOX.x" (("ACL" (("owner" "acdilrs")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001099 R070 OK ACL STORE completed.
+001100 R071 OK COPY completed.
+001101 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001102 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001103 * 3 EXISTS
+001104 * 0 RECENT
+001105 * OK [UIDVALIDITY] Ok
+001106 * OK [MYRIGHTS "acdilrs"] ACL
+001107 R072 OK [READ-WRITE] Ok
+001108 * 3 FETCH (FLAGS (\Seen \Deleted))
+001109 R073 OK FETCH completed.
+001110 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001111 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001112 * 1 EXISTS
+001113 * 0 RECENT
+001114 * OK [UIDVALIDITY] Ok
+001115 * OK [MYRIGHTS "acdilrsw"] ACL
+001116 R074 OK [READ-WRITE] Ok
+001117 * LIST (\HasNoChildren) "." "INBOX.x" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001117 R075 OK ACL STORE completed.
+001118 * LIST (\HasNoChildren) "." "INBOX.x" (("ACL" (("owner" "aceilrswx")("administrators" "acdilrsw")("user=systemuser2" "alr"))))
+001118 R076 OK ACL STORE completed.
+001119 R077 OK COPY completed.
+001120 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001121 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001122 * 4 EXISTS
+001123 * 0 RECENT
+001124 * OK [UIDVALIDITY] Ok
+001125 * OK [MYRIGHTS "aceilrswx"] ACL
+001126 R078 OK [READ-WRITE] Ok
+001127 * 4 FETCH (FLAGS (\Answered \Seen Foo))
+001128 R079 OK FETCH completed.
+001129 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001130 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001131 * 1 EXISTS
+001132 * 0 RECENT
+001133 * OK [UIDVALIDITY] Ok
+001134 * OK [MYRIGHTS "acdilrsw"] ACL
+001135 R080 OK [READ-WRITE] Ok
+001136 * BYE Courier-IMAP server shutting down
+001137 RDONE OK LOGOUT completed
+001138 * PREAUTH Ready.
+001139 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001140 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001141 * 1 EXISTS
+001142 * 1 RECENT
+001143 * OK [UIDVALIDITY] Ok
+001144 * OK [MYRIGHTS "acdilrsw"] ACL
+001145 S001 OK [READ-WRITE] Ok
+001146 S002 OK COPY completed.
+001147 * 2 EXISTS
+001148 * 1 RECENT
+001149 S003 OK NOOP completed
+001150 S004 OK COPY completed.
+001151 * 4 EXISTS
+001152 * 1 RECENT
+001153 S005 OK NOOP completed
+001154 * OK [MYRIGHTS "acilrstwx"] ACL
+001155 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acilrstwx")("administrators" "acdilrsw"))))
+001155 S006 OK ACL STORE completed.
+001156 * 1 FETCH (FLAGS (\Seen \Deleted \Recent))
+001157 S007 OK STORE completed.
+001158 S008 NO Access denied for EXPUNGE on current mailbox (ACL "e" required)
+001159 S009 OK mailbox closed.
+001160 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001161 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001162 * 4 EXISTS
+001163 * 0 RECENT
+001164 * OK [UIDVALIDITY] Ok
+001165 * OK [MYRIGHTS "acilrstwx"] ACL
+001166 S010 OK [READ-WRITE] Ok
+001167 * OK [MYRIGHTS "acdilrsw"] ACL
+001168 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+001168 S011 OK ACL STORE completed.
+001169 * 1 EXPUNGE
+001170 * 3 EXISTS
+001171 * 0 RECENT
+001172 S012 OK EXPUNGE completed
+001173 * 1 FETCH (FLAGS (\Seen \Deleted))
+001174 S013 OK STORE completed.
+001175 S014 OK mailbox closed.
+001176 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001177 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001178 * 2 EXISTS
+001179 * 0 RECENT
+001180 * OK [UIDVALIDITY] Ok
+001181 * OK [MYRIGHTS "acdilrsw"] ACL
+001182 S015 OK [READ-WRITE] Ok
+001183 S016 OK mailbox closed.
+001184 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilsw")("administrators" "acdilrsw"))))
+001184 S017 OK ACL STORE completed.
+001185 S018 NO Access denied for SELECT/EXAMINE on INBOX.a (ACL "r" required)
+001186 S019 NO Access denied for SELECT/EXAMINE on INBOX.a (ACL "r" required)
+001187 S020 NO Access denied for STATUS on INBOX.a (ACL "r" required)
+001188 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+001188 S021 OK ACL STORE completed.
+001189 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001190 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001191 * 2 EXISTS
+001192 * 0 RECENT
+001193 * OK [UIDVALIDITY] Ok
+001194 * OK [MYRIGHTS "acdilrsw"] ACL
+001195 S022 OK [READ-WRITE] Ok
+001196 + OK
+001197 S023 OK APPEND Ok.
+001198 * 3 EXISTS
+001199 * 0 RECENT
+001200 S024 OK NOOP completed
+001201 * 1 FETCH (FLAGS ())
+001202 * 2 FETCH (FLAGS ())
+001203 * 3 FETCH (FLAGS ())
+001204 S025 OK STORE completed.
+001205 * OK [MYRIGHTS "acdilrw"] ACL
+001206 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrw")("administrators" "acdilrsw"))))
+001206 S027 OK ACL STORE completed.
+001207 * 1 FETCH (BODY[] {83}
+001208 From: John <john@example.com>
+001209 To: john <john@example.com>
+001210 Subject: test
+001211
+001212 test
+001213 )
+001214 S028 OK FETCH completed.
+001215 * OK [MYRIGHTS "acdilrsw"] ACL
+001216 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+001216 S029 OK ACL STORE completed.
+001217 * 1 FETCH (BODY[] {83}
+001218 From: John <john@example.com>
+001219 To: john <john@example.com>
+001220 Subject: test
+001221
+001222 test
+001223 )
+001224 * 1 FETCH (FLAGS (\Seen))
+001225 S030 OK FETCH completed.
+001226 * OK [MYRIGHTS "acdilrw"] ACL
+001227 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrw")("administrators" "acdilrsw"))))
+001227 S031 OK ACL STORE completed.
+001228 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001229 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001230 * 1 FETCH (FLAGS (\Seen \Deleted Foo))
+001231 * 2 FETCH (FLAGS (\Deleted Foo))
+001232 S032 OK STORE completed.
+001233 * 1 FETCH (FLAGS (\Seen))
+001234 * 2 FETCH (FLAGS ())
+001235 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001236 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001237 S033 OK STORE completed.
+001238 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001239 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001240 * 2 FETCH (FLAGS (\Answered \Deleted Foo))
+001241 S034 OK STORE completed.
+001242 * 1 FETCH (FLAGS (\Seen))
+001243 * 2 FETCH (FLAGS (\Answered \Deleted Foo))
+001244 S035 OK FETCH completed.
+001245 * OK [MYRIGHTS "acdilrsw"] ACL
+001246 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+001246 S036 OK ACL STORE completed.
+001247 * OK [MYRIGHTS "acdilrs"] ACL
+001248 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrs")("administrators" "acdilrsw"))))
+001248 S037 OK ACL STORE completed.
+001249 * FLAGS (Bar Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001250 * OK [PERMANENTFLAGS (Bar Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001251 * 1 FETCH (FLAGS (\Seen \Deleted))
+001252 * 2 FETCH (FLAGS (\Answered \Seen \Deleted Foo))
+001253 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001254 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001255 S038 OK STORE completed.
+001256 * FLAGS (Bar Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001257 * OK [PERMANENTFLAGS (Bar Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001258 * 1 FETCH (FLAGS ())
+001259 * 2 FETCH (FLAGS (\Answered Foo))
+001260 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001261 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001262 S039 OK STORE completed.
+001263 * FLAGS (Bar Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001264 * OK [PERMANENTFLAGS (Bar Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001265 * 1 FETCH (FLAGS (\Seen \Deleted))
+001266 * 2 FETCH (FLAGS (\Answered \Seen \Deleted Foo))
+001267 * FLAGS (Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001268 * OK [PERMANENTFLAGS (Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001269 S040 OK STORE completed.
+001270 * OK [MYRIGHTS "acdilrsw"] ACL
+001271 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw"))))
+001271 S041 OK ACL STORE completed.
+001272 * 2 FETCH (FLAGS (\Answered \Seen Foo))
+001273 S042 OK STORE completed.
+001274 * OK [MYRIGHTS "aceilrswx"] ACL
+001275 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "aceilrswx")("administrators" "acdilrsw"))))
+001275 S043 OK ACL STORE completed.
+001276 * FLAGS (Bar Foo \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001277 * OK [PERMANENTFLAGS (Bar Foo \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001278 * 1 FETCH (FLAGS (\Seen \Deleted Bar))
+001279 * 2 FETCH (FLAGS (\Seen Bar))
+001280 * FLAGS (Bar \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001281 * OK [PERMANENTFLAGS (Bar \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001282 S044 OK STORE completed.
+001283 * 1 FETCH (FLAGS (\Deleted))
+001284 * 2 FETCH (FLAGS ())
+001285 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001286 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001287 S045 OK STORE completed.
+001288 * FLAGS (Bar \Draft \Answered \Flagged \Deleted \Seen \Recent)
+001289 * OK [PERMANENTFLAGS (Bar \* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001290 * 1 FETCH (FLAGS (\Seen \Deleted Bar))
+001291 * 2 FETCH (FLAGS (\Seen Bar))
+001292 S046 OK STORE completed.
+001293 S047 OK mailbox closed.
+001294 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=courierimaptestuser1" "alr"))))
+001294 S048 OK ACL STORE completed.
+001295 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=courierimaptestuser1" "alr")("user=courierimaptestuser2" "lr"))))
+001295 S049 OK ACL STORE completed.
+001296 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=courierimaptestuser1" "alr")("user=courierimaptestuser2" "lr"))))
+001296 S050 OK LIST completed
+001297 * BYE Courier-IMAP server shutting down
+001298 SDONE OK LOGOUT completed
+001299 * PREAUTH Ready.
+001300 * LIST (\Unmarked \HasNoChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l"))))
+001300 T001 OK ACL STORE completed.
+001301 T002 OK "INBOX.a" created.
+001302 * LIST (\HasNoChildren) "." "INBOX.a" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l")("anyone" "lr"))))
+001302 T003 OK ACL STORE completed.
+001303 + OK
+001304 T004 OK APPEND Ok.
+001305 * BYE Courier-IMAP server shutting down
+001306 T005 OK LOGOUT completed
+001307 * PREAUTH Ready.
+001308 * LIST (\Unmarked \HasNoChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "l"))))
+001308 T010 OK ACL STORE completed.
+001309 T011 OK "INBOX.a" created.
+001310 + OK
+001311 T012 OK APPEND Ok.
+001312 * BYE Courier-IMAP server shutting down
+001313 T013 OK LOGOUT completed
+001314 * PREAUTH Ready.
+001315 * LIST (\HasChildren) "." "#shared.a.user1"
+001315 * LIST (\HasChildren) "." "#shared.b.user2"
+001315 * LIST (\HasNoChildren) "." "#shared.a.user1.a"
+001315 * LIST (\HasNoChildren) "." "#shared.b.user2.a"
+001315 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001315 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001315 T020 OK LIST completed
+001316 * LIST (\Unmarked \HasNoChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "acdilrsw"))))
+001316 T021 OK ACL STORE completed.
+001317 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001317 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001317 T022 OK LIST completed
+001318 * BYE Courier-IMAP server shutting down
+001319 T023 OK LOGOUT completed
+001320 * PREAUTH Ready.
+001321 * LIST (\HasNoChildren) "." "#shared.a.user1.a"
+001321 * LIST (\HasNoChildren) "." "#shared.user0"
+001321 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001321 * LIST (\Noselect \HasChildren) "." "#shared.a.user1"
+001321 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001321 T030 OK LIST completed
+001322 * LIST (\HasNoChildren) "." "#shared.user0"
+001322 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001322 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001322 T031 OK LIST completed
+001323 * LIST (\Noselect \HasChildren) "." "#shared.a.user1"
+001323 T032 OK LIST completed
+001324 T033 OK LIST completed
+001325 T034 NO Access denied for SELECT/EXAMINE on #shared.a.user1 (ACL "r" required)
+001326 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001327 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001328 * 1 EXISTS
+001329 * 0 RECENT
+001330 * OK [UIDVALIDITY] Ok
+001331 * OK [MYRIGHTS "lr"] ACL
+001332 T035 OK [READ-ONLY] Ok
+001333 * 1 FETCH (ENVELOPE (NIL "user1" NIL NIL NIL NIL NIL NIL NIL NIL))
+001334 T036 OK FETCH completed.
+001335 T037 NO Access denied for STATUS on #shared.a.user1 (ACL "r" required)
+001336 T038 OK COPY completed.
+001337 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001338 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001339 * 1 EXISTS
+001340 * 0 RECENT
+001341 * OK [UIDVALIDITY] Ok
+001342 * OK [MYRIGHTS "acdilrsw"] ACL
+001343 T039 OK [READ-WRITE] Ok
+001344 T040 NO Access denied for CREATE on #shared.a.user1 (ACL "c" required)
+001345 T041 NO Access denied for CREATE on #shared.c (ACL "c" required)
+001346 T042 OK "#shared.user0.foo" created.
+001347 T043 OK Folder renamed.
+001348 * LIST (\HasChildren) "." "#shared.user0" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "acdilrsw"))))
+001348 * LIST (\HasNoChildren) "." "#shared.user0.bar" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "acdilrsw"))))
+001348 T044 OK LIST completed
+001349 * ACLFAILED "#shared.a.user1.a" NO Access denied for ACL STORE on #shared.a.user1.a (ACL "a" required)
+001350 T045 OK ACL STORE completed.
+001351 T046 OK "#shared.user0.foo" created.
+001352 * LIST (\HasNoChildren) "." "#shared.user0.foo" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "alr"))))
+001352 T047 OK ACL STORE completed.
+001353 * LIST (\HasNoChildren) "." "#shared.user0.bar" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "aceilrstw"))))
+001353 T048 OK ACL STORE completed.
+001354 T049 NO Access denied for RENAME on #shared.user0.bar (ACL "x" required)
+001355 * LIST (\HasNoChildren) "." "#shared.user0.bar" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "acdilrsw"))))
+001355 T050 OK ACL STORE completed.
+001356 T051 NO Access denied for RENAME on #shared.user0.foo (ACL "c" required)
+001357 * LIST (\HasNoChildren) "." "#shared.user0.foo" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("anyone" "aclr"))))
+001357 T052 OK ACL STORE completed.
+001358 T053 OK Folder renamed.
+001359 * LIST (\HasChildren) "." "#shared.user0"
+001359 * LIST (\HasChildren) "." "#shared.user0.foo"
+001359 * LIST (\HasNoChildren) "." "#shared.user0.foo.bar"
+001359 T054 OK LIST completed
+001360 * BYE Courier-IMAP server shutting down
+001361 TDONE OK LOGOUT completed
+001362 * PREAUTH Ready.
+001363 * LIST (\HasChildren) "." "#shared.b.us&AOk-r 3"
+001363 * LIST (\HasChildren) "." "#shared.b.use&-2"
+001363 * LIST (\HasChildren) "." "#shared.b.user 2"
+001363 * LIST (\HasNoChildren) "." "#shared.b.us&AOk-r 3.a"
+001363 * LIST (\HasNoChildren) "." "#shared.b.use&-2.a"
+001363 * LIST (\HasNoChildren) "." "#shared.b.user 2.a"
+001363 T055 OK LIST completed
+001364 * LIST (\HasChildren) "." "#shared.b.us&AOk-r 3"
+001364 * LIST (\HasChildren) "." "#shared.b.use&-2"
+001364 * LIST (\HasChildren) "." "#shared.b.user 2"
+001364 T056 OK LIST completed
+001365 * LIST (\HasNoChildren) "." "#shared.b.user 2.a"
+001365 T057 OK LIST completed
+001366 * LIST (\HasNoChildren) "." "#shared.b.use&-2.a"
+001366 T058 OK LIST completed
+001367 * BYE Courier-IMAP server shutting down
+001368 TDONE OK LOGOUT completed
+001369 * PREAUTH Ready.
+001370 * LIST (\HasChildren) "." "#shared.b.us&AOk-r\\;3"
+001370 * LIST (\HasChildren) "." "#shared.b.use&-2"
+001370 * LIST (\HasChildren) "." "#shared.b.user\\:2"
+001370 * LIST (\HasNoChildren) "." "#shared.b.us&AOk-r\\;3.a"
+001370 * LIST (\HasNoChildren) "." "#shared.b.use&-2.a"
+001370 * LIST (\HasNoChildren) "." "#shared.b.user\\:2.a"
+001370 T055 OK LIST completed
+001371 * LIST (\HasChildren) "." "#shared.b.us&AOk-r\\;3"
+001371 * LIST (\HasChildren) "." "#shared.b.use&-2"
+001371 * LIST (\HasChildren) "." "#shared.b.user\\:2"
+001371 T056 OK LIST completed
+001372 * LIST (\HasNoChildren) "." "#shared.b.user\\:2.a"
+001372 T057 OK LIST completed
+001373 * LIST (\HasNoChildren) "." "#shared.b.use&-2.a"
+001373 T058 OK LIST completed
+001374 T059 OK "INBOX.a.b" created.
+001375 * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent)
+001376 * OK [PERMANENTFLAGS (\* \Draft \Answered \Flagged \Deleted \Seen)] Limited
+001377 * 0 EXISTS
+001378 * 0 RECENT
+001379 * OK [UIDVALIDITY] Ok
+001380 * OK [MYRIGHTS "acdilrsw"] ACL
+001381 T060 OK [READ-WRITE] Ok
+001382 T061 NO Can't RENAME the currently-open folder
+001383 T062 OK mailbox closed.
+001384 T063 OK Folder renamed.
+001385 T064 NO INBOX rename not implemented.
+001386 T065 NO Cannot move a folder to a different account.
+001387 * LIST (\HasChildren) "." "#shared.b.us&AOk-r\\;3"
+001387 * LIST (\HasChildren) "." "#shared.b.use&-2"
+001387 * LIST (\HasChildren) "." "#shared.b.user\\:2"
+001387 * LIST (\HasChildren) "." "#shared.user0"
+001387 * LIST (\HasChildren) "." "#shared.user0.foo"
+001387 * LIST (\HasNoChildren) "." "#shared.b.us&AOk-r\\;3.a"
+001387 * LIST (\HasNoChildren) "." "#shared.b.use&-2.a"
+001387 * LIST (\HasNoChildren) "." "#shared.b.user\\:2.a"
+001387 * LIST (\HasNoChildren) "." "#shared.user0.foo.bar"
+001387 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001387 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001387 T066 OK LIST completed
+001388 * LIST (\HasChildren) "." "#shared.user0"
+001388 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001388 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001388 T067 OK LIST completed
+001389 * LIST (\HasChildren) "." "#shared.user0"
+001389 * LIST (\Noselect \HasChildren) "." "#shared.a"
+001389 * LIST (\Noselect \HasChildren) "." "#shared.b"
+001389 T068 OK LIST completed
+001390 * LIST (\HasChildren) "." "#shared.b.us&AOk-r\\;3"
+001390 * LIST (\HasChildren) "." "#shared.b.use&-2"
+001390 * LIST (\HasChildren) "." "#shared.b.user\\:2"
+001390 * LIST (\HasChildren) "." "#shared.user0.foo"
+001390 T069 OK LIST completed
+001391 * LIST (\HasNoChildren) "." "#shared.b.us&AOk-r\\;3.a"
+001391 * LIST (\HasNoChildren) "." "#shared.b.use&-2.a"
+001391 * LIST (\HasNoChildren) "." "#shared.b.user\\:2.a"
+001391 * LIST (\HasNoChildren) "." "#shared.user0.foo.bar"
+001391 T070 OK LIST completed
+001392 T071 OK LIST completed
+001393 * BYE Courier-IMAP server shutting down
+001394 TDONE OK LOGOUT completed
+001395 * PREAUTH Ready.
+001396 * LIST (\HasChildren) "." "#shared.usergroup1"
+001396 * LIST (\HasChildren) "." "#shared.usergroup1.foo"
+001396 * LIST (\HasNoChildren) "." "#shared.usergroup1.foo.bar"
+001396 T072 OK LIST completed
+001397 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l"))))
+001397 T073 OK LIST completed
+001398 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l")("user=fred" "lr"))))
+001398 T074 OK ACL STORE completed.
+001399 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l")("user=fred" "lr")("-user=john" "lr"))))
+001399 T075 OK ACL STORE completed.
+001400 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l")("user=fred" "lr")("-user=john" "lr")("-authuser" "lr"))))
+001400 T076 OK ACL STORE completed.
+001401 * ACL "INBOX" "owner" "acdilrsw" "administrators" "acdilrsw" "user0" "l" "fred" "lr" "-john" "lr" "-authuser" "lr"
+001402 T077 OK GETACL completed.
+001403 T078 OK ACLs updated.
+001404 T079 OK ACLs updated.
+001405 T080 OK ACLs updated.
+001406 * ACL "INBOX" "owner" "acdilrsw" "administrators" "acdilrsw" "user0" "l" "fred" "cr" "-john" "clr" "-authuser" "clr"
+001407 T081 OK GETACL completed.
+001408 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l")("user=fred" "cr")("-user=john" "clr")("-authuser" "clr"))))
+001408 T082 OK LIST completed
+001409 T083 OK ACLs updated.
+001410 T084 OK ACLs updated.
+001411 * ACL "INBOX" "owner" "acdilrsw" "administrators" "acdilrsw" "user0" "l" "-authuser" "clr"
+001412 T085 OK GETACL completed.
+001413 * LIST (\Unmarked \HasChildren) "." "INBOX" (("ACL" (("owner" "acdilrsw")("administrators" "acdilrsw")("user=user0" "l")("-authuser" "clr"))))
+001413 T086 OK LIST completed
+001414 * BYE Courier-IMAP server shutting down
+001415 TDONE OK LOGOUT completed
diff --git a/imap/testsuitefix.pl.in b/imap/testsuitefix.pl.in new file mode 100644 index 0000000..ee22d8c --- /dev/null +++ b/imap/testsuitefix.pl.in @@ -0,0 +1,21 @@ +#! @PERL@ +# +# Copyright 2000-2001 Double Precision, Inc. See COPYING for +# distribution information. +# +# Ok, the output of LIST is given in filesystem order, so fix that by +# prefixing a line number count, which doesn't incremenet for a LIST, +# and have the output of this script sorted. +# + +$n=0; + +while (<>) +{ + s/\[COPYUID.*\] //; + s/\[APPENDUID.*\] //; + s/^\* ADD \"UID=.*/* ADD UID/; + s/^\* COPY \d+ \"NEWUID=.*/* COPY NEWUID/; + printf("%06d %s", $n, $_); + ++$n unless $_ =~ /^\* (LIST|LSUB)/; +} diff --git a/imap/thread.c b/imap/thread.c new file mode 100644 index 0000000..0670f22 --- /dev/null +++ b/imap/thread.c @@ -0,0 +1,500 @@ +/* +** Copyright 2000-2009 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +#include "config.h" +#include "imapd.h" +#include "thread.h" +#include "searchinfo.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "imapwrite.h" +#include "imaptoken.h" +#include "imapscanclient.h" +#include "rfc822/rfc822.h" +#include "rfc822/rfc2047.h" +#include "rfc822/imaprefs.h" +#include "unicode/unicode.h" + +static void thread_os_callback(struct searchinfo *, struct searchinfo *, int, + unsigned long, void *); +static void thread_ref_callback(struct searchinfo *, struct searchinfo *, int, + unsigned long, void *); + +extern struct imapscaninfo current_maildir_info; + + +struct os_threadinfo { + struct os_threadinfo *next; + char *subj; + time_t sent_date; + unsigned long n; + } ; + +struct os_threadinfo_list { + struct os_threadinfo_list *next; + size_t thread_start; +} ; + +struct os_struct { + struct os_threadinfo *list; + unsigned nmsgs; + struct os_threadinfo **msgs; + } ; + +static void os_init(struct os_struct *os) +{ + memset(os, 0, sizeof(*os)); +} + +static void os_add(struct os_struct *os, unsigned long n, const char *s, + time_t sent_date) +{ +struct os_threadinfo *osi=(struct os_threadinfo *) + malloc(sizeof(struct os_threadinfo)); + + if (!osi) write_error_exit(0); + osi->subj=strdup(s); + /* This decodes the MIME encoding */ + if (!osi->subj) write_error_exit(0); + osi->sent_date=sent_date; + osi->n=n; + osi->next=os->list; + os->list=osi; + ++os->nmsgs; +} + +static void os_free(struct os_struct *os) +{ +struct os_threadinfo *p; + + while ((p=os->list) != 0) + { + os->list=p->next; + free(p->subj); + free(p); + } + if (os->msgs) free(os->msgs); +} + +static int cmpsubjs(const void *a, const void *b) +{ + const struct os_threadinfo *ap=*(const struct os_threadinfo **)a; + const struct os_threadinfo *bp=*(const struct os_threadinfo **)b; + int rc=strcmp( ap->subj, bp->subj); + + if (rc) return (rc); + + return (ap->sent_date < bp->sent_date ? -1: + ap->sent_date > bp->sent_date ? 1:0); +} + +/* Print the meat of the THREAD ORDEREDSUBJECT response */ + +static void printos(struct os_threadinfo **array, size_t cnt) +{ + size_t i; + struct os_threadinfo_list *thread_list=NULL, *threadptr, **tptr; + + /* + ** thread_list - indexes to start of each thread, sort indexes by + ** sent_date + */ + + for (i=0; i<cnt; i++) + { + /* Find start of next thread */ + + if (i > 0 && strcmp(array[i-1]->subj, array[i]->subj) == 0) + continue; + + threadptr=malloc(sizeof(struct os_threadinfo_list)); + if (!threadptr) + write_error_exit(0); + threadptr->thread_start=i; + + /* Insert into the list, sorted by sent date */ + + for (tptr= &thread_list; *tptr; tptr=&(*tptr)->next) + if ( array[(*tptr)->thread_start]->sent_date + > array[i]->sent_date) + break; + + threadptr->next= *tptr; + *tptr=threadptr; + } + + while ( (threadptr=thread_list) != NULL) + { + size_t i, j; + const char *p; + + thread_list=threadptr->next; + + i=threadptr->thread_start; + free(threadptr); + + for (j=i+1; j<cnt; j++) + { + if (strcmp(array[i]->subj, array[j]->subj)) + break; + } + + p="("; + while (i < j) + { + writes(p); + p=" "; + writen(array[i]->n); + ++i; + } + writes(")"); + } +} + +void dothreadorderedsubj(struct searchinfo *si, struct searchinfo *sihead, + const char *charset, int isuid) +{ +struct os_struct os; + + os_init(&os); + search_internal(si, sihead, charset, isuid, thread_os_callback, &os); + + if (os.nmsgs > 0) /* Found some messages */ + { + size_t i; + struct os_threadinfo *o; + + /* Convert it to an array */ + + os.msgs= (struct os_threadinfo **) + malloc(os.nmsgs * sizeof(struct os_threadinfo *)); + if (!os.msgs) write_error_exit(0); + for (o=os.list, i=0; o; o=o->next, i++) + os.msgs[i]=o; + + /* Sort the array */ + + qsort(os.msgs, os.nmsgs, sizeof(*os.msgs), cmpsubjs); + + /* Print the array */ + + printos(os.msgs, os.nmsgs); + } + os_free(&os); +} + +/* +This callback function is called once search finds a qualifying message. +We save its message number and subject in a link list. +*/ + +static void thread_os_callback(struct searchinfo *si, + struct searchinfo *sihead, + int isuid, unsigned long i, + void *voidarg) +{ + if (sihead->type == search_orderedsubj) + /* SHOULD BE ALWAYS TRUE */ + os_add( (struct os_struct *)voidarg, + isuid ? current_maildir_info.msgs[i].uid:i+1, + sihead->as ? sihead->as:"", + sihead->bs ? rfc822_parsedt(sihead->bs):0); +} + +static void printthread(struct imap_refmsg *, int); + +void dothreadreferences(struct searchinfo *si, struct searchinfo *sihead, + const char *charset, + int isuid) +{ + struct imap_refmsgtable *reftable; + struct imap_refmsg *root; + + if (!(reftable=rfc822_threadalloc())) + { + write_error_exit(0); + return; + } + + search_internal(si, sihead, charset, 0, + thread_ref_callback, reftable); + + root=rfc822_thread(reftable); + printthread(root, isuid); + rfc822_threadfree(reftable); +} + +static void thread_ref_callback(struct searchinfo *si, + struct searchinfo *sihead, + int isuid, unsigned long i, + void *voidarg) +{ + if (sihead->type == search_references1 && sihead->a && + sihead->a->type == search_references2 && sihead->a->a && + sihead->a->a->type == search_references3 && sihead->a->a->a && + sihead->a->a->a->type == search_references4) + { + const char *ref, *inreplyto, *subject, *date, *msgid; + + ref=sihead->as; + inreplyto=sihead->bs; + date=sihead->a->as; + subject=sihead->a->a->as; + msgid=sihead->a->a->a->as; + +#if 0 + fprintf(stderr, "REFERENCES: ref=%s, inreplyto=%s, subject=%s, date=%s, msgid=%s\n", + ref ? ref:"", + inreplyto ? inreplyto:"", + subject ? subject:"", + date ? date:"", + msgid ? msgid:""); +#endif + + if (!rfc822_threadmsg( (struct imap_refmsgtable *)voidarg, + msgid, ref && *ref ? ref:inreplyto, + subject, date, 0, i)) + write_error_exit(0); + } +} + +static void printthread(struct imap_refmsg *msg, int isuid) +{ + const char *pfix=""; + + while (msg) + { + if (!msg->isdummy) + { + writes(pfix); + writen(isuid ? + current_maildir_info.msgs[msg->seqnum].uid: + msg->seqnum+1); + pfix=" "; + } + + if (msg->firstchild && (msg->firstchild->nextsib + || msg->firstchild->isdummy + || msg->parent == NULL)) + { + writes(pfix); + for (msg=msg->firstchild; msg; msg=msg->nextsib) + { + struct imap_refmsg *msg2; + + msg2=msg; + + if (msg2->isdummy) + msg2=msg2->firstchild; + + for (; msg2; msg2=msg2->firstchild) + { + if (!msg2->isdummy || + msg2->nextsib) + break; + } + + if (msg2) + { + writes("("); + printthread(msg, isuid); + writes(")"); + } + } + break; + } + msg=msg->firstchild; + } +} + +void free_temp_sort_stack(struct temp_sort_stack *t) +{ + while (t) + { + struct temp_sort_stack *u=t->next; + + free(t); + t=u; + } +} + +/* ---------------------------------- SORT ---------------------------- */ + +/* sortmsginfo holds the sorting information for a message. */ + +struct sortmsginfo { + struct sortmsginfo *next; /* next msg */ + unsigned long n; /* msg number/uid */ + char **sortfields; /* array of sorting fields */ + char *sortorder; /* [x]=0 - normal, [x]=1 - reversed */ + size_t nfields; + } ; + +struct sortmsgs { + struct sortmsginfo *list; /* The actual list */ + struct sortmsginfo **array; /* In array form */ + size_t nmsgs; /* Array size/count of msgs */ + size_t nfields; /* From the SORT() arg */ + } ; + +static void free_sortmsgs(struct sortmsgs *p) +{ +struct sortmsginfo *q; + + if (p->array) free(p->array); + while ((q=p->list) != 0) + { + size_t i; + + p->list=q->next; + for (i=0; i<p->nfields; i++) + if (q->sortfields[i]) + free(q->sortfields[i]); + if (q->sortfields) free(q->sortfields); + if (q->sortorder) free(q->sortorder); + free(q); + } +} + +static void sort_callback(struct searchinfo *, struct searchinfo *, int, + unsigned long, void *); + +static int cmpsort(const void *a, const void *b) +{ +const struct sortmsginfo *ap=*(const struct sortmsginfo **)a; +const struct sortmsginfo *bp=*(const struct sortmsginfo **)b; +size_t i; + + for (i=0; i<ap->nfields; i++) + { + int n=strcmp(ap->sortfields[i], bp->sortfields[i]); + + if (n < 0) + return (ap->sortorder[i] ? 1:-1); + if (n > 0) + return (ap->sortorder[i] ? -1:1); + } + return (0); +} + +void dosortmsgs(struct searchinfo *si, struct searchinfo *sihead, + const char *charset, int isuid) +{ +struct sortmsgs sm; +struct searchinfo *p; + + memset(&sm, 0, sizeof(sm)); + for (p=sihead; p; p=p->a) + switch (p->type) { + case search_orderedsubj: + case search_arrival: + case search_cc: + case search_date: + case search_from: + case search_size: + case search_to: + ++sm.nfields; + break; + default: + break; + } + search_internal(si, sihead, charset, isuid, sort_callback, &sm); + if (sm.nmsgs > 0) + { + size_t i; + struct sortmsginfo *o; + + /* Convert it to an array */ + + sm.array= (struct sortmsginfo **) + malloc(sm.nmsgs * sizeof(struct sortmsginfo *)); + if (!sm.array) write_error_exit(0); + for (o=sm.list, i=0; o; o=o->next, i++) + sm.array[i]=o; + + /* Sort the array */ + + qsort(sm.array, sm.nmsgs, sizeof(*sm.array), cmpsort); + + /* Print the array */ + + for (i=0; i<sm.nmsgs; i++) + { + writes(" "); + writen(sm.array[i]->n); + } + } + free_sortmsgs(&sm); +} + +static void sort_callback(struct searchinfo *si, struct searchinfo *sihead, + int isuid, unsigned long n, void *voidarg) +{ +struct sortmsgs *sm=(struct sortmsgs *)voidarg; +struct sortmsginfo *msg=(struct sortmsginfo *) + malloc(sizeof(struct sortmsginfo)); +struct searchinfo *ss; +int rev; +size_t i; + + if (msg) memset(msg, 0, sizeof(*msg)); + if (!msg || (sm->nfields && ((msg->sortfields=(char **) + malloc(sizeof(char *)*sm->nfields)) == 0 || + (msg->sortorder=(char *) + malloc(sm->nfields)) == 0))) + write_error_exit(0); + + if (sm->nfields) + { + memset(msg->sortfields, 0, sizeof(char *)*sm->nfields); + memset(msg->sortorder, 0, sm->nfields); + } + + rev=0; + i=0; + +/* fprintf(stderr, "--\n"); */ + + for (ss=sihead; ss; ss=ss->a) + { + char *p; + + if (i >= sm->nfields) + break; /* Something's fucked up, better handle it + ** gracefully, instead of dumping core. + */ + switch (ss->type) { + case search_reverse: + rev=1-rev; + continue; + case search_orderedsubj: + case search_arrival: + case search_cc: + case search_date: + case search_from: + case search_size: + case search_to: + p=ss->as; + if (!p) p=""; + msg->sortfields[i]=my_strdup(p); + msg->sortorder[i]=rev; + /* fprintf(stderr, "%d %s\n", msg->sortorder[i], msg->sortfields[i]); */ + ++i; + rev=0; + continue; + default: + break; + } + break; + } + + msg->nfields=sm->nfields; + msg->n=isuid ? current_maildir_info.msgs[n].uid:n+1; + msg->next=sm->list; + sm->list=msg; + ++sm->nmsgs; +} diff --git a/imap/thread.h b/imap/thread.h new file mode 100644 index 0000000..5f53389 --- /dev/null +++ b/imap/thread.h @@ -0,0 +1,33 @@ +#ifndef thread_h +#define thread_h + +#include "searchinfo.h" +/* +** Copyright 2000 Double Precision, Inc. +** See COPYING for distribution information. +*/ + + +struct threadinfo; + +extern int thread_orderedsubj(struct threadinfo *, struct threadinfo *); + +struct unicode_info; + +void dothreadorderedsubj(struct searchinfo *, struct searchinfo *, + const char *, int); +void dothreadreferences(struct searchinfo *, struct searchinfo *, + const char *, int); + +/* While we're at it, some support for SORT */ + +struct temp_sort_stack { /* Temporary stack list of SORT criteria */ + struct temp_sort_stack *next; + search_type type; + } ; + +void free_temp_sort_stack(struct temp_sort_stack *); +void dosortmsgs(struct searchinfo *, struct searchinfo *, + const char *, int); + +#endif |
