Back to index

citadel  8.12
serv_imap.c
Go to the documentation of this file.
00001 /*
00002  * IMAP server for the Citadel system
00003  *
00004  * Copyright (C) 2000-2011 by Art Cancro and others.
00005  * This code is released under the terms of the GNU General Public License.
00006  *
00007  * WARNING: the IMAP protocol is badly designed.  No implementation of it
00008  * is perfect.  Indeed, with so much gratuitous complexity, *all* IMAP
00009  * implementations have bugs.
00010  *
00011  * This program is open source software; you can redistribute it and/or modify
00012  * it under the terms of the GNU General Public License as published by
00013  * the Free Software Foundation; either version 3 of the License, or
00014  * (at your option) any later version.
00015  *
00016  * This program is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  * GNU General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU General Public License
00022  * along with this program; if not, write to the Free Software
00023  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00024  */
00025 
00026 #include "sysdep.h"
00027 #include <stdlib.h>
00028 #include <unistd.h>
00029 #include <stdio.h>
00030 #include <fcntl.h>
00031 #include <signal.h>
00032 #include <pwd.h>
00033 #include <errno.h>
00034 #include <sys/types.h>
00035 
00036 #if TIME_WITH_SYS_TIME
00037 # include <sys/time.h>
00038 # include <time.h>
00039 #else
00040 # if HAVE_SYS_TIME_H
00041 #  include <sys/time.h>
00042 # else
00043 #  include <time.h>
00044 # endif
00045 #endif
00046 
00047 #include <sys/wait.h>
00048 #include <ctype.h>
00049 #include <string.h>
00050 #include <limits.h>
00051 #include <libcitadel.h>
00052 #include "citadel.h"
00053 #include "server.h"
00054 #include "citserver.h"
00055 #include "support.h"
00056 #include "config.h"
00057 #include "user_ops.h"
00058 #include "database.h"
00059 #include "msgbase.h"
00060 #include "internet_addressing.h"
00061 #include "serv_imap.h"
00062 #include "imap_tools.h"
00063 #include "imap_list.h"
00064 #include "imap_fetch.h"
00065 #include "imap_search.h"
00066 #include "imap_store.h"
00067 #include "imap_acl.h"
00068 #include "imap_metadata.h"
00069 #include "imap_misc.h"
00070 
00071 #include "ctdl_module.h"
00072 int IMAPDebugEnabled = 0;
00073 HashList *ImapCmds = NULL;
00074 void registerImapCMD(const char *First, long FLen, 
00075                    const char *Second, long SLen,
00076                    imap_handler H,
00077                    int Flags)
00078 {
00079        imap_handler_hook *h;
00080 
00081        h = (imap_handler_hook*) malloc(sizeof(imap_handler_hook));
00082        memset(h, 0, sizeof(imap_handler_hook));
00083 
00084        h->Flags = Flags;
00085        h->h = H;
00086        if (SLen == 0) {
00087               Put(ImapCmds, First, FLen, h, NULL);
00088        }
00089        else {
00090               char CMD[SIZ];
00091               memcpy(CMD, First, FLen);
00092               memcpy(CMD+FLen, Second, SLen);
00093               CMD[FLen+SLen] = '\0';
00094               Put(ImapCmds, CMD, FLen + SLen, h, NULL);
00095        }
00096 }
00097 
00098 void imap_cleanup(void)
00099 {
00100        DeleteHash(&ImapCmds);
00101 }
00102 
00103 const imap_handler_hook *imap_lookup(int num_parms, ConstStr *Params)
00104 {
00105        struct CitContext *CCC = CC;
00106        void *v;
00107        citimap *Imap = CCCIMAP;
00108 
00109        if (num_parms < 1)
00110               return NULL;
00111 
00112        /* we abuse the Reply-buffer for uppercasing... */
00113        StrBufPlain(Imap->Reply, CKEY(Params[1]));
00114        StrBufUpCase(Imap->Reply);
00115 
00116        IMAP_syslog(LOG_DEBUG, "---- Looking up [%s] -----", 
00117                     ChrPtr(Imap->Reply));
00118        if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
00119        {
00120               IMAPM_syslog(LOG_DEBUG, "Found."); 
00121               FlushStrBuf(Imap->Reply);
00122               return (imap_handler_hook *) v;
00123        }
00124 
00125        if (num_parms == 1)
00126        {
00127               IMAPM_syslog(LOG_DEBUG, "NOT Found."); 
00128               FlushStrBuf(Imap->Reply);
00129               return NULL;
00130        }
00131        
00132        IMAP_syslog(LOG_DEBUG, "---- Looking up [%s] -----", 
00133                     ChrPtr(Imap->Reply));
00134        StrBufAppendBufPlain(Imap->Reply, CKEY(Params[2]), 0);
00135        StrBufUpCase(Imap->Reply);
00136        if (GetHash(ImapCmds, SKEY(Imap->Reply), &v))
00137        {
00138               IMAPM_syslog(LOG_DEBUG, "Found."); 
00139               FlushStrBuf(Imap->Reply);
00140               return (imap_handler_hook *) v;
00141        }
00142        IMAPM_syslog(LOG_DEBUG, "NOT Found."); 
00143        FlushStrBuf(Imap->Reply);
00144               return NULL;
00145 }
00146 
00147 /* imap_rename() uses this struct containing list of rooms to rename */
00148 struct irl {
00149        struct irl *next;
00150        char irl_oldroom[ROOMNAMELEN];
00151        char irl_newroom[ROOMNAMELEN];
00152        int irl_newfloor;
00153 };
00154 
00155 /* Data which is passed between imap_rename() and imap_rename_backend() */
00156 typedef struct __irlparms {
00157        const char *oldname;
00158        long oldnamelen;
00159        const char *newname;
00160        long newnamelen;
00161        struct irl **irl;
00162 }irlparms;
00163 
00164 
00165 /*
00166  * If there is a message ID map in memory, free it
00167  */
00168 void imap_free_msgids(void)
00169 {
00170        citimap *Imap = IMAP;
00171        if (Imap->msgids != NULL) {
00172               free(Imap->msgids);
00173               Imap->msgids = NULL;
00174               Imap->num_msgs = 0;
00175               Imap->num_alloc = 0;
00176        }
00177        if (Imap->flags != NULL) {
00178               free(Imap->flags);
00179               Imap->flags = NULL;
00180        }
00181        Imap->last_mtime = (-1);
00182 }
00183 
00184 
00185 /*
00186  * If there is a transmitted message in memory, free it
00187  */
00188 void imap_free_transmitted_message(void)
00189 {
00190        FreeStrBuf(&IMAP->TransmittedMessage);
00191 }
00192 
00193 
00194 /*
00195  * Set the \Seen, \Recent. and \Answered flags, based on the sequence
00196  * sets stored in the visit record for this user/room.  Note that we have
00197  * to parse each sequence set manually here, because calling the utility
00198  * function is_msg_in_sequence_set() over and over again is too expensive.
00199  *
00200  * first_msg should be set to 0 to rescan the flags for every message in the
00201  * room, or some other value if we're only interested in an incremental
00202  * update.
00203  */
00204 void imap_set_seen_flags(int first_msg)
00205 {
00206        citimap *Imap = IMAP;
00207        visit vbuf;
00208        int i;
00209        int num_sets;
00210        int s;
00211        char setstr[64], lostr[64], histr[64];
00212        long lo, hi;
00213 
00214        if (Imap->num_msgs < 1) return;
00215        CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
00216 
00217        for (i = first_msg; i < Imap->num_msgs; ++i) {
00218               Imap->flags[i] = Imap->flags[i] & ~IMAP_SEEN;
00219               Imap->flags[i] |= IMAP_RECENT;
00220               Imap->flags[i] = Imap->flags[i] & ~IMAP_ANSWERED;
00221        }
00222 
00223        /*
00224         * Do the "\Seen" flag.
00225         * (Any message not "\Seen" is considered "\Recent".)
00226         */
00227        num_sets = num_tokens(vbuf.v_seen, ',');
00228        for (s=0; s<num_sets; ++s) {
00229               extract_token(setstr, vbuf.v_seen, s, ',', sizeof setstr);
00230 
00231               extract_token(lostr, setstr, 0, ':', sizeof lostr);
00232               if (num_tokens(setstr, ':') >= 2) {
00233                      extract_token(histr, setstr, 1, ':', sizeof histr);
00234                      if (!strcmp(histr, "*")) {
00235                             snprintf(histr, sizeof histr, "%ld", LONG_MAX);
00236                      }
00237               } 
00238               else {
00239                      strcpy(histr, lostr);
00240               }
00241               lo = atol(lostr);
00242               hi = atol(histr);
00243 
00244               for (i = first_msg; i < Imap->num_msgs; ++i) {
00245                      if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
00246                             Imap->flags[i] |= IMAP_SEEN;
00247                             Imap->flags[i] = Imap->flags[i] & ~IMAP_RECENT;
00248                      }
00249               }
00250        }
00251 
00252        /* Do the ANSWERED flag */
00253        num_sets = num_tokens(vbuf.v_answered, ',');
00254        for (s=0; s<num_sets; ++s) {
00255               extract_token(setstr, vbuf.v_answered, s, ',', sizeof setstr);
00256 
00257               extract_token(lostr, setstr, 0, ':', sizeof lostr);
00258               if (num_tokens(setstr, ':') >= 2) {
00259                      extract_token(histr, setstr, 1, ':', sizeof histr);
00260                      if (!strcmp(histr, "*")) {
00261                             snprintf(histr, sizeof histr, "%ld", LONG_MAX);
00262                      }
00263               } 
00264               else {
00265                      strcpy(histr, lostr);
00266               }
00267               lo = atol(lostr);
00268               hi = atol(histr);
00269 
00270               for (i = first_msg; i < Imap->num_msgs; ++i) {
00271                      if ((Imap->msgids[i] >= lo) && (Imap->msgids[i] <= hi)){
00272                             Imap->flags[i] |= IMAP_ANSWERED;
00273                      }
00274               }
00275        }
00276 
00277 }
00278 
00279 
00280 
00281 /*
00282  * Back end for imap_load_msgids()
00283  *
00284  * Optimization: instead of calling realloc() to add each message, we
00285  * allocate space in the list for REALLOC_INCREMENT messages at a time.  This
00286  * allows the mapping to proceed much faster.
00287  */
00288 void imap_add_single_msgid(long msgnum, void *userdata)
00289 {
00290        citimap *Imap = IMAP;
00291 
00292        ++Imap->num_msgs;
00293        if (Imap->num_msgs > Imap->num_alloc) {
00294               Imap->num_alloc += REALLOC_INCREMENT;
00295               Imap->msgids = realloc(Imap->msgids, (Imap->num_alloc * sizeof(long)) );
00296               Imap->flags = realloc(Imap->flags, (Imap->num_alloc * sizeof(long)) );
00297        }
00298        Imap->msgids[Imap->num_msgs - 1] = msgnum;
00299        Imap->flags[Imap->num_msgs - 1] = 0;
00300 }
00301 
00302 
00303 
00304 /*
00305  * Set up a message ID map for the current room (folder)
00306  */
00307 void imap_load_msgids(void)
00308 {
00309        struct CitContext *CCC = CC;
00310        struct cdbdata *cdbfr;
00311        citimap *Imap = CCCIMAP;
00312 
00313        if (Imap->selected == 0) {
00314               IMAPM_syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
00315               return;
00316        }
00317 
00318        imap_free_msgids();  /* If there was already a map, free it */
00319 
00320        /* Load the message list */
00321        cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
00322        if (cdbfr != NULL) {
00323               Imap->msgids = malloc(cdbfr->len);
00324               memcpy(Imap->msgids, cdbfr->ptr, cdbfr->len);
00325               Imap->num_msgs = cdbfr->len / sizeof(long);
00326               Imap->num_alloc = cdbfr->len / sizeof(long);
00327               cdb_free(cdbfr);
00328        }
00329 
00330        if (Imap->num_msgs) {
00331               Imap->flags = malloc(Imap->num_alloc * sizeof(long));
00332               memset(Imap->flags, 0, (Imap->num_alloc * sizeof(long)) );
00333        }
00334 
00335        imap_set_seen_flags(0);
00336 }
00337 
00338 
00339 /*
00340  * Re-scan the selected room (folder) and see if it's been changed at all
00341  */
00342 void imap_rescan_msgids(void)
00343 {
00344        struct CitContext *CCC = CC;
00345        citimap *Imap = CCCIMAP;
00346        int original_num_msgs = 0;
00347        long original_highest = 0L;
00348        int i, j, jstart;
00349        int message_still_exists;
00350        struct cdbdata *cdbfr;
00351        long *msglist = NULL;
00352        int num_msgs = 0;
00353        int num_recent = 0;
00354 
00355        if (Imap->selected == 0) {
00356               IMAPM_syslog(LOG_ERR, "imap_load_msgids() can't run; no room selected");
00357               return;
00358        }
00359 
00360        /*
00361         * Check to see if the room's contents have changed.
00362         * If not, we can avoid this rescan.
00363         */
00364        CtdlGetRoom(&CC->room, CC->room.QRname);
00365        if (Imap->last_mtime == CC->room.QRmtime) {      /* No changes! */
00366               return;
00367        }
00368 
00369        /* Load the *current* message list from disk, so we can compare it
00370         * to what we have in memory.
00371         */
00372        cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
00373        if (cdbfr != NULL) {
00374               msglist = malloc(cdbfr->len + 1);
00375               if (msglist == NULL) {
00376                      IMAPM_syslog(LOG_CRIT, "malloc() failed");
00377                      CC->kill_me = KILLME_MALLOC_FAILED;
00378                      return;
00379               }
00380               memcpy(msglist, cdbfr->ptr, (size_t)cdbfr->len);
00381               num_msgs = cdbfr->len / sizeof(long);
00382               cdb_free(cdbfr);
00383        } else {
00384               num_msgs = 0;
00385        }
00386 
00387        /*
00388         * Check to see if any of the messages we know about have been expunged
00389         */
00390        if (Imap->num_msgs > 0) {
00391               jstart = 0;
00392               for (i = 0; i < Imap->num_msgs; ++i) {
00393 
00394                      message_still_exists = 0;
00395                      if (num_msgs > 0) {
00396                             for (j = jstart; j < num_msgs; ++j) {
00397                                    if (msglist[j] == Imap->msgids[i]) {
00398                                           message_still_exists = 1;
00399                                           jstart = j;
00400                                           break;
00401                                    }
00402                             }
00403                      }
00404 
00405                      if (message_still_exists == 0) {
00406                             IAPrintf("* %d EXPUNGE\r\n", i + 1);
00407 
00408                             /* Here's some nice stupid nonsense.  When a
00409                              * message is expunged, we have to slide all
00410                              * the existing messages up in the message
00411                              * array.
00412                              */
00413                             --Imap->num_msgs;
00414                             memmove(&Imap->msgids[i],
00415                                    &Imap->msgids[i + 1],
00416                                    (sizeof(long) *
00417                                     (Imap->num_msgs - i)));
00418                             memmove(&Imap->flags[i],
00419                                    &Imap->flags[i + 1],
00420                                    (sizeof(long) *
00421                                     (Imap->num_msgs - i)));
00422 
00423                             --i;
00424                      }
00425 
00426               }
00427        }
00428 
00429        /*
00430         * Remember how many messages were here before we re-scanned.
00431         */
00432        original_num_msgs = Imap->num_msgs;
00433        if (Imap->num_msgs > 0) {
00434               original_highest = Imap->msgids[Imap->num_msgs - 1];
00435        } else {
00436               original_highest = 0L;
00437        }
00438 
00439        /*
00440         * Now peruse the room for *new* messages only.
00441         * This logic is probably the cause of Bug # 368
00442         * [ http://bugzilla.citadel.org/show_bug.cgi?id=368 ]
00443         */
00444        if (num_msgs > 0) {
00445               for (j = 0; j < num_msgs; ++j) {
00446                      if (msglist[j] > original_highest) {
00447                             imap_add_single_msgid(msglist[j], NULL);
00448                      }
00449               }
00450        }
00451        imap_set_seen_flags(original_num_msgs);
00452 
00453        /*
00454         * If new messages have arrived, tell the client about them.
00455         */
00456        if (Imap->num_msgs > original_num_msgs) {
00457 
00458               for (j = 0; j < num_msgs; ++j) {
00459                      if (Imap->flags[j] & IMAP_RECENT) {
00460                             ++num_recent;
00461                      }
00462               }
00463 
00464               IAPrintf("* %d EXISTS\r\n", Imap->num_msgs);
00465               IAPrintf("* %d RECENT\r\n", num_recent);
00466        }
00467 
00468        if (msglist != NULL) {
00469               free(msglist);
00470        }
00471        Imap->last_mtime = CC->room.QRmtime;
00472 }
00473 
00474 
00475 /*
00476  * This cleanup function blows away the temporary memory and files used by
00477  * the IMAP server.
00478  */
00479 void imap_cleanup_function(void)
00480 {
00481        struct CitContext *CCC = CC;
00482        citimap *Imap = CCCIMAP;
00483 
00484        /* Don't do this stuff if this is not a Imap session! */
00485        if (CC->h_command_function != imap_command_loop)
00486               return;
00487 
00488        /* If there is a mailbox selected, auto-expunge it. */
00489        if (Imap->selected) {
00490               imap_do_expunge();
00491        }
00492 
00493        IMAPM_syslog(LOG_DEBUG, "Performing IMAP cleanup hook");
00494        imap_free_msgids();
00495        imap_free_transmitted_message();
00496 
00497        if (Imap->cached_rfc822 != NULL) {
00498               FreeStrBuf(&Imap->cached_rfc822);
00499               Imap->cached_rfc822_msgnum = (-1);
00500               Imap->cached_rfc822_withbody = 0;
00501        }
00502 
00503        if (Imap->cached_body != NULL) {
00504               free(Imap->cached_body);
00505               Imap->cached_body = NULL;
00506               Imap->cached_body_len = 0;
00507               Imap->cached_bodymsgnum = (-1);
00508        }
00509        FreeStrBuf(&Imap->Cmd.CmdBuf);
00510        FreeStrBuf(&Imap->Reply);
00511        if (Imap->Cmd.Params != NULL) free(Imap->Cmd.Params);
00512        free(Imap);
00513        IMAPM_syslog(LOG_DEBUG, "Finished IMAP cleanup hook");
00514 }
00515 
00516 
00517 /*
00518  * Does the actual work of the CAPABILITY command (because we need to
00519  * output this stuff in other places as well)
00520  */
00521 void imap_output_capability_string(void) {
00522        IAPuts("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
00523 
00524 #ifdef HAVE_OPENSSL
00525        if (!CC->redirect_ssl) IAPuts(" STARTTLS");
00526 #endif
00527 
00528 #ifndef DISABLE_IMAP_ACL
00529        IAPuts(" ACL");
00530 #endif
00531 
00532        /* We are building a partial implementation of METADATA for the sole purpose
00533         * of interoperating with the ical/vcard version of the Bynari Insight Connector.
00534         * It is not a full RFC5464 implementation, but it should refuse non-Bynari
00535         * metadata in a compatible and graceful way.
00536         */
00537        IAPuts(" METADATA");
00538 
00539        /*
00540         * LIST-EXTENDED was originally going to be required by the METADATA extension.
00541         * It was mercifully removed prior to the finalization of RFC5464.  We started
00542         * implementing this but stopped when we learned that it would not be needed.
00543         * If you uncomment this declaration you are responsible for writing a lot of new
00544         * code.
00545         *
00546         * IAPuts(" LIST-EXTENDED")
00547         */
00548 }
00549 
00550 
00551 /*
00552  * implements the CAPABILITY command
00553  */
00554 void imap_capability(int num_parms, ConstStr *Params)
00555 {
00556        IAPuts("* ");
00557        imap_output_capability_string();
00558        IAPuts("\r\n");
00559        IReply("OK CAPABILITY completed");
00560 }
00561 
00562 
00563 /*
00564  * Implements the ID command (specified by RFC2971)
00565  *
00566  * We ignore the client-supplied information, and output a NIL response.
00567  * Although this is technically a valid implementation of the extension, it
00568  * is quite useless.  It exists only so that we may see which clients are
00569  * making use of this extension.
00570  * 
00571  */
00572 void imap_id(int num_parms, ConstStr *Params)
00573 {
00574        IAPuts("* ID NIL\r\n");
00575        IReply("OK ID completed");
00576 }
00577 
00578 
00579 /*
00580  * Here's where our IMAP session begins its happy day.
00581  */
00582 void imap_greeting(void)
00583 {
00584        citimap *Imap;
00585        CitContext *CCC = CC;
00586 
00587        strcpy(CCC->cs_clientname, "IMAP session");
00588        CCC->session_specific_data = malloc(sizeof(citimap));
00589        Imap = (citimap *)CCC->session_specific_data;
00590        memset(Imap, 0, sizeof(citimap));
00591        Imap->authstate = imap_as_normal;
00592        Imap->cached_rfc822_msgnum = (-1);
00593        Imap->cached_rfc822_withbody = 0;
00594        Imap->Reply = NewStrBufPlain(NULL, SIZ * 10); /* 40k */
00595 
00596        if (CCC->nologin)
00597        {
00598               IAPuts("* BYE; Server busy, try later\r\n");
00599               CCC->kill_me = KILLME_NOLOGIN;
00600               IUnbuffer();
00601               return;
00602        }
00603 
00604        IAPuts("* OK [");
00605        imap_output_capability_string();
00606        IAPrintf("] %s IMAP4rev1 %s ready\r\n", config.c_fqdn, CITADEL);
00607        IUnbuffer();
00608 }
00609 
00610 
00611 /*
00612  * IMAPS is just like IMAP, except it goes crypto right away.
00613  */
00614 void imaps_greeting(void) {
00615        CtdlModuleStartCryptoMsgs(NULL, NULL, NULL);
00616 #ifdef HAVE_OPENSSL
00617        if (!CC->redirect_ssl) CC->kill_me = KILLME_NO_CRYPTO;         /* kill session if no crypto */
00618 #endif
00619        imap_greeting();
00620 }
00621 
00622 
00623 /*
00624  * implements the LOGIN command (ordinary username/password login)
00625  */
00626 void imap_login(int num_parms, ConstStr *Params)
00627 {
00628 
00629        switch (num_parms) {
00630        case 3:
00631               if (Params[2].Key[0] == '{') {
00632                      IAPuts("+ go ahead\r\n");
00633                      IMAP->authstate = imap_as_expecting_multilineusername;
00634                      strcpy(IMAP->authseq, Params[0].Key);
00635                      return;
00636               }
00637               else {
00638                      IReply("BAD incorrect number of parameters");
00639                      return;
00640               }
00641        case 4:
00642               if (CtdlLoginExistingUser(NULL, Params[2].Key) == login_ok) {
00643                      if (CtdlTryPassword(Params[3].Key, Params[3].len) == pass_ok) {
00644                             /* hm, thats not doable by IReply :-( */
00645                             IAPrintf("%s OK [", Params[0].Key);
00646                             imap_output_capability_string();
00647                             IAPrintf("] Hello, %s\r\n", CC->user.fullname);
00648                             return;
00649                      }
00650                      else
00651                      {
00652                             IReplyPrintf("NO AUTHENTICATE %s failed",
00653                                         Params[3].Key);
00654                      }
00655               }
00656 
00657               IReply("BAD Login incorrect");
00658        default:
00659               IReply("BAD incorrect number of parameters");
00660               return;
00661        }
00662 
00663 }
00664 
00665 
00666 /*
00667  * Implements the AUTHENTICATE command
00668  */
00669 void imap_authenticate(int num_parms, ConstStr *Params)
00670 {
00671        char UsrBuf[SIZ];
00672 
00673        if (num_parms != 3) {
00674               IReply("BAD incorrect number of parameters");
00675               return;
00676        }
00677 
00678        if (CC->logged_in) {
00679               IReply("BAD Already logged in.");
00680               return;
00681        }
00682 
00683        if (!strcasecmp(Params[2].Key, "LOGIN")) {
00684               CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
00685               IAPrintf("+ %s\r\n", UsrBuf);
00686               IMAP->authstate = imap_as_expecting_username;
00687               strcpy(IMAP->authseq, Params[0].Key);
00688               return;
00689        }
00690 
00691        if (!strcasecmp(Params[2].Key, "PLAIN")) {
00692               // CtdlEncodeBase64(UsrBuf, "Username:", 9, 0);
00693               // IAPuts("+ %s\r\n", UsrBuf);
00694               IAPuts("+ \r\n");
00695               IMAP->authstate = imap_as_expecting_plainauth;
00696               strcpy(IMAP->authseq, Params[0].Key);
00697               return;
00698        }
00699 
00700        else {
00701               IReplyPrintf("NO AUTHENTICATE %s failed",
00702                           Params[1].Key);
00703        }
00704 }
00705 
00706 
00707 void imap_auth_plain(void)
00708 {
00709        citimap *Imap = IMAP;
00710        const char *decoded_authstring;
00711        char ident[256];
00712        char user[256];
00713        char pass[256];
00714        int result;
00715        long len;
00716 
00717        memset(pass, 0, sizeof(pass));
00718        StrBufDecodeBase64(Imap->Cmd.CmdBuf);
00719 
00720        decoded_authstring = ChrPtr(Imap->Cmd.CmdBuf);
00721        safestrncpy(ident, decoded_authstring, sizeof ident);
00722        safestrncpy(user, &decoded_authstring[strlen(ident) + 1], sizeof user);
00723        len = safestrncpy(pass, &decoded_authstring[strlen(ident) + strlen(user) + 2], sizeof pass);
00724        if (len < 0)
00725               len = sizeof(pass) - 1;
00726 
00727        Imap->authstate = imap_as_normal;
00728 
00729        if (!IsEmptyStr(ident)) {
00730               result = CtdlLoginExistingUser(user, ident);
00731        }
00732        else {
00733               result = CtdlLoginExistingUser(NULL, user);
00734        }
00735 
00736        if (result == login_ok) {
00737               if (CtdlTryPassword(pass, len) == pass_ok) {
00738                      IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
00739                      return;
00740               }
00741        }
00742        IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
00743 }
00744 
00745 
00746 void imap_auth_login_user(long state)
00747 {
00748        char PWBuf[SIZ];
00749        citimap *Imap = IMAP;
00750 
00751        switch (state){
00752        case imap_as_expecting_username:
00753               StrBufDecodeBase64(Imap->Cmd.CmdBuf);
00754               CtdlLoginExistingUser(NULL, ChrPtr(Imap->Cmd.CmdBuf));
00755               CtdlEncodeBase64(PWBuf, "Password:", 9, 0);
00756               IAPrintf("+ %s\r\n", PWBuf);
00757               
00758               Imap->authstate = imap_as_expecting_password;
00759               return;
00760        case imap_as_expecting_multilineusername:
00761               extract_token(PWBuf, ChrPtr(Imap->Cmd.CmdBuf), 1, ' ', sizeof(PWBuf));
00762               CtdlLoginExistingUser(NULL, ChrPtr(Imap->Cmd.CmdBuf));
00763               IAPuts("+ go ahead\r\n");
00764               Imap->authstate = imap_as_expecting_multilinepassword;
00765               return;
00766        }
00767 }
00768 
00769 
00770 void imap_auth_login_pass(long state)
00771 {
00772        citimap *Imap = IMAP;
00773        const char *pass = NULL;
00774        long len = 0;
00775 
00776        switch (state) {
00777        default:
00778        case imap_as_expecting_password:
00779               StrBufDecodeBase64(Imap->Cmd.CmdBuf);
00780               pass = ChrPtr(Imap->Cmd.CmdBuf);
00781               len = StrLength(Imap->Cmd.CmdBuf);
00782               break;
00783        case imap_as_expecting_multilinepassword:
00784               pass = ChrPtr(Imap->Cmd.CmdBuf);
00785               len = StrLength(Imap->Cmd.CmdBuf);
00786               break;
00787        }
00788        if (len > USERNAME_SIZE)
00789               StrBufCutAt(Imap->Cmd.CmdBuf, USERNAME_SIZE, NULL);
00790 
00791        if (CtdlTryPassword(pass, len) == pass_ok) {
00792               IAPrintf("%s OK authentication succeeded\r\n", Imap->authseq);
00793        } else {
00794               IAPrintf("%s NO authentication failed\r\n", Imap->authseq);
00795        }
00796        Imap->authstate = imap_as_normal;
00797        return;
00798 }
00799 
00800 
00801 /*
00802  * implements the STARTTLS command (Citadel API version)
00803  */
00804 void imap_starttls(int num_parms, ConstStr *Params)
00805 {
00806        char ok_response[SIZ];
00807        char nosup_response[SIZ];
00808        char error_response[SIZ];
00809 
00810        snprintf(ok_response, SIZ,  "%s OK begin TLS negotiation now\r\n",    Params[0].Key);
00811        snprintf(nosup_response, SIZ,      "%s NO TLS not supported here\r\n",       Params[0].Key);
00812        snprintf(error_response, SIZ,      "%s BAD Internal error\r\n",              Params[0].Key);
00813        CtdlModuleStartCryptoMsgs(ok_response, nosup_response, error_response);
00814 }
00815 
00816 
00817 /*
00818  * implements the SELECT command
00819  */
00820 void imap_select(int num_parms, ConstStr *Params)
00821 {
00822        citimap *Imap = IMAP;
00823        char towhere[ROOMNAMELEN];
00824        char augmented_roomname[ROOMNAMELEN];
00825        int c = 0;
00826        int ok = 0;
00827        int ra = 0;
00828        struct ctdlroom QRscratch;
00829        int msgs, new;
00830        int i;
00831 
00832        /* Convert the supplied folder name to a roomname */
00833        i = imap_roomname(towhere, sizeof towhere, Params[2].Key);
00834        if (i < 0) {
00835               IReply("NO Invalid mailbox name.");
00836               Imap->selected = 0;
00837               return;
00838        }
00839 
00840        /* First try a regular match */
00841        c = CtdlGetRoom(&QRscratch, towhere);
00842 
00843        /* Then try a mailbox name match */
00844        if (c != 0) {
00845               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname, &CC->user, towhere);
00846               c = CtdlGetRoom(&QRscratch, augmented_roomname);
00847               if (c == 0) {
00848                      safestrncpy(towhere, augmented_roomname, sizeof(towhere));
00849               }
00850        }
00851 
00852        /* If the room exists, check security/access */
00853        if (c == 0) {
00854               /* See if there is an existing user/room relationship */
00855               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
00856 
00857               /* normal clients have to pass through security */
00858               if (ra & UA_KNOWN) {
00859                      ok = 1;
00860               }
00861        }
00862 
00863        /* Fail here if no such room */
00864        if (!ok) {
00865               IReply("NO ... no such room, or access denied");
00866               return;
00867        }
00868 
00869        /* If we already had some other folder selected, auto-expunge it */
00870        imap_do_expunge();
00871 
00872        /*
00873         * CtdlUserGoto() formally takes us to the desired room, happily returning
00874         * the number of messages and number of new messages.
00875         */
00876        memcpy(&CC->room, &QRscratch, sizeof(struct ctdlroom));
00877        CtdlUserGoto(NULL, 0, 0, &msgs, &new);
00878        Imap->selected = 1;
00879 
00880        if (!strcasecmp(Params[1].Key, "EXAMINE")) {
00881               Imap->readonly = 1;
00882        } else {
00883               Imap->readonly = 0;
00884        }
00885 
00886        imap_load_msgids();
00887        Imap->last_mtime = CC->room.QRmtime;
00888 
00889        IAPrintf("* %d EXISTS\r\n", msgs);
00890        IAPrintf("* %d RECENT\r\n", new);
00891 
00892        IAPrintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE);
00893        IAPrintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CitControl.MMhighest + 1);
00894 
00895        /* Technically, \Deleted is a valid flag, but not a permanent flag,
00896         * because we don't maintain its state across sessions.  Citadel
00897         * automatically expunges mailboxes when they are de-selected.
00898         * 
00899         * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
00900         * some clients (particularly Thunderbird) to misbehave -- they simply
00901         * elect not to transmit the flag at all.  So we have to advertise
00902         * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
00903         */
00904        IAPuts("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
00905        IAPuts("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
00906 
00907        IReplyPrintf("OK [%s] %s completed",
00908               (Imap->readonly ? "READ-ONLY" : "READ-WRITE"), Params[1].Key
00909        );
00910 }
00911 
00912 
00913 /*
00914  * Does the real work for expunge.
00915  */
00916 int imap_do_expunge(void)
00917 {
00918        struct CitContext *CCC = CC;
00919        citimap *Imap = CCCIMAP;
00920        int i;
00921        int num_expunged = 0;
00922        long *delmsgs = NULL;
00923        int num_delmsgs = 0;
00924 
00925        IMAPM_syslog(LOG_DEBUG, "imap_do_expunge() called");
00926        if (Imap->selected == 0) {
00927               return (0);
00928        }
00929 
00930        if (Imap->num_msgs > 0) {
00931               delmsgs = malloc(Imap->num_msgs * sizeof(long));
00932               for (i = 0; i < Imap->num_msgs; ++i) {
00933                      if (Imap->flags[i] & IMAP_DELETED) {
00934                             delmsgs[num_delmsgs++] = Imap->msgids[i];
00935                      }
00936               }
00937               if (num_delmsgs > 0) {
00938                      CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, "");
00939               }
00940               num_expunged += num_delmsgs;
00941               free(delmsgs);
00942        }
00943 
00944        if (num_expunged > 0) {
00945               imap_rescan_msgids();
00946        }
00947 
00948        IMAP_syslog(LOG_DEBUG, "Expunged %d messages from <%s>", num_expunged, CC->room.QRname);
00949        return (num_expunged);
00950 }
00951 
00952 
00953 /*
00954  * implements the EXPUNGE command syntax
00955  */
00956 void imap_expunge(int num_parms, ConstStr *Params)
00957 {
00958        int num_expunged = 0;
00959 
00960        num_expunged = imap_do_expunge();
00961        IReplyPrintf("OK expunged %d messages.", num_expunged);
00962 }
00963 
00964 
00965 /*
00966  * implements the CLOSE command
00967  */
00968 void imap_close(int num_parms, ConstStr *Params)
00969 {
00970 
00971        /* Yes, we always expunge on close. */
00972        if (IMAP->selected) {
00973               imap_do_expunge();
00974        }
00975 
00976        IMAP->selected = 0;
00977        IMAP->readonly = 0;
00978        imap_free_msgids();
00979        IReply("OK CLOSE completed");
00980 }
00981 
00982 
00983 /*
00984  * Implements the NAMESPACE command.
00985  */
00986 void imap_namespace(int num_parms, ConstStr *Params)
00987 {
00988        int i;
00989        struct floor *fl;
00990        int floors = 0;
00991        char Namespace[SIZ];
00992 
00993        IAPuts("* NAMESPACE ");
00994 
00995        /* All personal folders are subordinate to INBOX. */
00996        IAPuts("((\"INBOX/\" \"/\")) ");
00997 
00998        /* Other users' folders ... coming soon! FIXME */
00999        IAPuts("NIL ");
01000 
01001        /* Show all floors as shared namespaces.  Neato! */
01002        IAPuts("(");
01003        for (i = 0; i < MAXFLOORS; ++i) {
01004               fl = CtdlGetCachedFloor(i);
01005               if (fl->f_flags & F_INUSE) {
01006                      /* if (floors > 0) IAPuts(" "); samjam says this confuses javamail */
01007                      IAPuts("(");
01008                      snprintf(Namespace, sizeof(Namespace), "%s/", fl->f_name);
01009                      plain_imap_strout(Namespace);
01010                      IAPuts(" \"/\")");
01011                      ++floors;
01012               }
01013        }
01014        IAPuts(")");
01015 
01016        /* Wind it up with a newline and a completion message. */
01017        IAPuts("\r\n");
01018        IReply("OK NAMESPACE completed");
01019 }
01020 
01021 
01022 /*
01023  * Implements the CREATE command
01024  *
01025  */
01026 void imap_create(int num_parms, ConstStr *Params)
01027 {
01028        struct CitContext *CCC = CC;
01029        int ret;
01030        char roomname[ROOMNAMELEN];
01031        int floornum;
01032        int flags;
01033        int newroomtype = 0;
01034        int newroomview = 0;
01035        char *notification_message = NULL;
01036 
01037        if (num_parms < 3) {
01038               IReply("NO A foder name must be specified");
01039               return;
01040        }
01041 
01042        if (strchr(Params[2].Key, '\\') != NULL) {
01043               IReply("NO Invalid character in folder name");
01044               IMAPM_syslog(LOG_ERR, "invalid character in folder name");
01045               return;
01046        }
01047 
01048        ret = imap_roomname(roomname, sizeof roomname, Params[2].Key);
01049        if (ret < 0) {
01050               IReply("NO Invalid mailbox name or location");
01051               IMAPM_syslog(LOG_ERR, "invalid mailbox name or location");
01052               return;
01053        }
01054        floornum = (ret & 0x00ff);  /* lower 8 bits = floor number */
01055        flags = (ret & 0xff00);     /* upper 8 bits = flags        */
01056 
01057        if (flags & IR_MAILBOX) {
01058               if (strncasecmp(Params[2].Key, "INBOX/", 6)) {
01059                      IReply("NO Personal folders must be created under INBOX");
01060                      IMAPM_syslog(LOG_ERR, "not subordinate to inbox");
01061                      return;
01062               }
01063        }
01064 
01065        if (flags & IR_MAILBOX) {
01066               newroomtype = 4;            /* private mailbox */
01067               newroomview = VIEW_MAILBOX;
01068        } else {
01069               newroomtype = 0;            /* public folder */
01070               newroomview = VIEW_BBS;
01071        }
01072 
01073        IMAP_syslog(LOG_INFO, "Create new room <%s> on floor <%d> with type <%d>",
01074                   roomname, floornum, newroomtype);
01075 
01076        ret = CtdlCreateRoom(roomname, newroomtype, "", floornum, 1, 0, newroomview);
01077        if (ret == 0) {
01078               /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY!  BYNARI CONNECTOR DEPENDS ON IT! ***/
01079               IReply("NO Mailbox already exists, or create failed");
01080        } else {
01081               IReply("OK CREATE completed");
01082               /* post a message in Aide> describing the new room */
01083               notification_message = malloc(1024);
01084               snprintf(notification_message, 1024,
01085                      "A new room called \"%s\" has been created by %s%s%s%s\n",
01086                      roomname,
01087                      CC->user.fullname,
01088                      ((ret & QR_MAILBOX) ? " [personal]" : ""),
01089                      ((ret & QR_PRIVATE) ? " [private]" : ""),
01090                      ((ret & QR_GUESSNAME) ? " [hidden]" : "")
01091               );
01092               CtdlAideMessage(notification_message, "Room Creation Message");
01093               free(notification_message);
01094        }
01095        IMAPM_syslog(LOG_DEBUG, "imap_create() completed");
01096 }
01097 
01098 
01099 /*
01100  * Locate a room by its IMAP folder name, and check access to it.
01101  * If zapped_ok is nonzero, we can also look for the room in the zapped list.
01102  */
01103 int imap_grabroom(char *returned_roomname, const char *foldername, int zapped_ok)
01104 {
01105        int ret;
01106        char augmented_roomname[ROOMNAMELEN];
01107        char roomname[ROOMNAMELEN];
01108        int c;
01109        struct ctdlroom QRscratch;
01110        int ra;
01111        int ok = 0;
01112 
01113        ret = imap_roomname(roomname, sizeof roomname, foldername);
01114        if (ret < 0) {
01115               return (1);
01116        }
01117 
01118        /* First try a regular match */
01119        c = CtdlGetRoom(&QRscratch, roomname);
01120 
01121        /* Then try a mailbox name match */
01122        if (c != 0) {
01123               CtdlMailboxName(augmented_roomname, sizeof augmented_roomname,
01124                          &CC->user, roomname);
01125               c = CtdlGetRoom(&QRscratch, augmented_roomname);
01126               if (c == 0)
01127                      safestrncpy(roomname, augmented_roomname, sizeof(roomname));
01128        }
01129 
01130        /* If the room exists, check security/access */
01131        if (c == 0) {
01132               /* See if there is an existing user/room relationship */
01133               CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL);
01134 
01135               /* normal clients have to pass through security */
01136               if (ra & UA_KNOWN) {
01137                      ok = 1;
01138               }
01139               if ((zapped_ok) && (ra & UA_ZAPPED)) {
01140                      ok = 1;
01141               }
01142        }
01143 
01144        /* Fail here if no such room */
01145        if (!ok) {
01146               strcpy(returned_roomname, "");
01147               return (2);
01148        } else {
01149               safestrncpy(returned_roomname, QRscratch.QRname, ROOMNAMELEN);
01150               return (0);
01151        }
01152 }
01153 
01154 
01155 /*
01156  * Implements the STATUS command (sort of)
01157  *
01158  */
01159 void imap_status(int num_parms, ConstStr *Params)
01160 {
01161        int ret;
01162        char roomname[ROOMNAMELEN];
01163        char imaproomname[SIZ];
01164        char savedroom[ROOMNAMELEN];
01165        int msgs, new;
01166 
01167        ret = imap_grabroom(roomname, Params[2].Key, 1);
01168        if (ret != 0) {
01169               IReply("NO Invalid mailbox name or location, or access denied");
01170               return;
01171        }
01172 
01173        /*
01174         * CtdlUserGoto() formally takes us to the desired room, happily returning
01175         * the number of messages and number of new messages.  (If another
01176         * folder is selected, save its name so we can return there!!!!!)
01177         */
01178        if (IMAP->selected) {
01179               strcpy(savedroom, CC->room.QRname);
01180        }
01181        CtdlUserGoto(roomname, 0, 0, &msgs, &new);
01182 
01183        /*
01184         * Tell the client what it wants to know.  In fact, tell it *more* than
01185         * it wants to know.  We happily IGnore the supplied status data item
01186         * names and simply spew all possible data items.  It's far easier to
01187         * code and probably saves us some processing time too.
01188         */
01189        imap_mailboxname(imaproomname, sizeof imaproomname, &CC->room);
01190        IAPuts("* STATUS ");
01191               plain_imap_strout(imaproomname);
01192        IAPrintf(" (MESSAGES %d ", msgs);
01193        IAPrintf("RECENT %d ", new);       /* Initially, new==recent */
01194        IAPrintf("UIDNEXT %ld ", CitControl.MMhighest + 1);
01195        IAPrintf("UNSEEN %d)\r\n", new);
01196        
01197        /*
01198         * If another folder is selected, go back to that room so we can resume
01199         * our happy day without violent explosions.
01200         */
01201        if (IMAP->selected) {
01202               CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
01203        }
01204 
01205        /*
01206         * Oooh, look, we're done!
01207         */
01208        IReply("OK STATUS completed");
01209 }
01210 
01211 
01212 /*
01213  * Implements the SUBSCRIBE command
01214  *
01215  */
01216 void imap_subscribe(int num_parms, ConstStr *Params)
01217 {
01218        int ret;
01219        char roomname[ROOMNAMELEN];
01220        char savedroom[ROOMNAMELEN];
01221        int msgs, new;
01222 
01223        ret = imap_grabroom(roomname, Params[2].Key, 1);
01224        if (ret != 0) {
01225               IReplyPrintf(
01226                      "NO Error %d: invalid mailbox name or location, or access denied",
01227                      ret
01228               );
01229               return;
01230        }
01231 
01232        /*
01233         * CtdlUserGoto() formally takes us to the desired room, which has the side
01234         * effect of marking the room as not-zapped ... exactly the effect
01235         * we're looking for.
01236         */
01237        if (IMAP->selected) {
01238               strcpy(savedroom, CC->room.QRname);
01239        }
01240        CtdlUserGoto(roomname, 0, 0, &msgs, &new);
01241 
01242        /*
01243         * If another folder is selected, go back to that room so we can resume
01244         * our happy day without violent explosions.
01245         */
01246        if (IMAP->selected) {
01247               CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
01248        }
01249 
01250        IReply("OK SUBSCRIBE completed");
01251 }
01252 
01253 
01254 /*
01255  * Implements the UNSUBSCRIBE command
01256  *
01257  */
01258 void imap_unsubscribe(int num_parms, ConstStr *Params)
01259 {
01260        int ret;
01261        char roomname[ROOMNAMELEN];
01262        char savedroom[ROOMNAMELEN];
01263        int msgs, new;
01264 
01265        ret = imap_grabroom(roomname, Params[2].Key, 1);
01266        if (ret != 0) {
01267               IReply("NO Invalid mailbox name or location, or access denied");
01268               return;
01269        }
01270 
01271        /*
01272         * CtdlUserGoto() formally takes us to the desired room.
01273         */
01274        if (IMAP->selected) {
01275               strcpy(savedroom, CC->room.QRname);
01276        }
01277        CtdlUserGoto(roomname, 0, 0, &msgs, &new);
01278 
01279        /* 
01280         * Now make the API call to zap the room
01281         */
01282        if (CtdlForgetThisRoom() == 0) {
01283               IReply("OK UNSUBSCRIBE completed");
01284        } else {
01285               IReply("NO You may not unsubscribe from this folder.");
01286        }
01287 
01288        /*
01289         * If another folder is selected, go back to that room so we can resume
01290         * our happy day without violent explosions.
01291         */
01292        if (IMAP->selected) {
01293               CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
01294        }
01295 }
01296 
01297 
01298 /*
01299  * Implements the DELETE command
01300  *
01301  */
01302 void imap_delete(int num_parms, ConstStr *Params)
01303 {
01304        int ret;
01305        char roomname[ROOMNAMELEN];
01306        char savedroom[ROOMNAMELEN];
01307        int msgs, new;
01308 
01309        ret = imap_grabroom(roomname, Params[2].Key, 1);
01310        if (ret != 0) {
01311               IReply("NO Invalid mailbox name, or access denied");
01312               return;
01313        }
01314 
01315        /*
01316         * CtdlUserGoto() formally takes us to the desired room, happily returning
01317         * the number of messages and number of new messages.  (If another
01318         * folder is selected, save its name so we can return there!!!!!)
01319         */
01320        if (IMAP->selected) {
01321               strcpy(savedroom, CC->room.QRname);
01322        }
01323        CtdlUserGoto(roomname, 0, 0, &msgs, &new);
01324 
01325        /*
01326         * Now delete the room.
01327         */
01328        if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) {
01329               CtdlScheduleRoomForDeletion(&CC->room);
01330               IReply("OK DELETE completed");
01331        } else {
01332               IReply("NO Can't delete this folder.");
01333        }
01334 
01335        /*
01336         * If another folder is selected, go back to that room so we can resume
01337         * our happy day without violent explosions.
01338         */
01339        if (IMAP->selected) {
01340               CtdlUserGoto(savedroom, 0, 0, &msgs, &new);
01341        }
01342 }
01343 
01344 
01345 /*
01346  * Back end function for imap_rename()
01347  */
01348 void imap_rename_backend(struct ctdlroom *qrbuf, void *data)
01349 {
01350        char foldername[SIZ];
01351        char newfoldername[SIZ];
01352        char newroomname[ROOMNAMELEN];
01353        int newfloor = 0;
01354        struct irl *irlp = NULL;    /* scratch pointer */
01355        irlparms *myirlparms;
01356 
01357        myirlparms = (irlparms *) data;
01358        imap_mailboxname(foldername, sizeof foldername, qrbuf);
01359 
01360        /* Rename subfolders */
01361        if ((!strncasecmp(foldername, myirlparms->oldname,
01362                        myirlparms->oldnamelen)
01363            && (foldername[myirlparms->oldnamelen] == '/'))) {
01364 
01365               sprintf(newfoldername, "%s/%s",
01366                      myirlparms->newname,
01367                      &foldername[myirlparms->oldnamelen + 1]
01368                   );
01369 
01370               newfloor = imap_roomname(newroomname,
01371                                     sizeof newroomname,
01372                                     newfoldername) & 0xFF;
01373 
01374               irlp = (struct irl *) malloc(sizeof(struct irl));
01375               strcpy(irlp->irl_newroom, newroomname);
01376               strcpy(irlp->irl_oldroom, qrbuf->QRname);
01377               irlp->irl_newfloor = newfloor;
01378               irlp->next = *(myirlparms->irl);
01379               *(myirlparms->irl) = irlp;
01380        }
01381 }
01382 
01383 
01384 /*
01385  * Implements the RENAME command
01386  *
01387  */
01388 void imap_rename(int num_parms, ConstStr *Params)
01389 {
01390        char old_room[ROOMNAMELEN];
01391        char new_room[ROOMNAMELEN];
01392        int newr;
01393        int new_floor;
01394        int r;
01395        struct irl *irl = NULL;     /* the list */
01396        struct irl *irlp = NULL;    /* scratch pointer */
01397        irlparms irlparms;
01398        char aidemsg[1024];
01399 
01400        if (strchr(Params[3].Key, '\\') != NULL) {
01401               IReply("NO Invalid character in folder name");
01402               return;
01403        }
01404 
01405        imap_roomname(old_room, sizeof old_room, Params[2].Key);
01406        newr = imap_roomname(new_room, sizeof new_room, Params[3].Key);
01407        new_floor = (newr & 0xFF);
01408 
01409        r = CtdlRenameRoom(old_room, new_room, new_floor);
01410 
01411        if (r == crr_room_not_found) {
01412               IReply("NO Could not locate this folder");
01413               return;
01414        }
01415        if (r == crr_already_exists) {
01416               IReplyPrintf("NO '%s' already exists.");
01417               return;
01418        }
01419        if (r == crr_noneditable) {
01420               IReply("NO This folder is not editable.");
01421               return;
01422        }
01423        if (r == crr_invalid_floor) {
01424               IReply("NO Folder root does not exist.");
01425               return;
01426        }
01427        if (r == crr_access_denied) {
01428               IReply("NO You do not have permission to edit this folder.");
01429               return;
01430        }
01431        if (r != crr_ok) {
01432               IReplyPrintf("NO Rename failed - undefined error %d", r);
01433               return;
01434        }
01435 
01436        /* If this is the INBOX, then RFC2060 says we have to just move the
01437         * contents.  In a Citadel environment it's easier to rename the room
01438         * (already did that) and create a new inbox.
01439         */
01440        if (!strcasecmp(Params[2].Key, "INBOX")) {
01441               CtdlCreateRoom(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX);
01442        }
01443 
01444        /* Otherwise, do the subfolders.  Build a list of rooms to rename... */
01445        else {
01446               irlparms.oldname = Params[2].Key;
01447               irlparms.oldnamelen = Params[2].len;
01448               irlparms.newname = Params[3].Key;
01449               irlparms.newnamelen = Params[3].len;
01450               irlparms.irl = &irl;
01451               CtdlForEachRoom(imap_rename_backend, (void *) &irlparms);
01452 
01453               /* ... and now rename them. */
01454               while (irl != NULL) {
01455                      r = CtdlRenameRoom(irl->irl_oldroom,
01456                                       irl->irl_newroom,
01457                                       irl->irl_newfloor);
01458                      if (r != crr_ok) {
01459                             struct CitContext *CCC = CC;
01460                             /* FIXME handle error returns better */
01461                             IMAP_syslog(LOG_ERR, "CtdlRenameRoom() error %d", r);
01462                      }
01463                      irlp = irl;
01464                      irl = irl->next;
01465                      free(irlp);
01466               }
01467        }
01468 
01469        snprintf(aidemsg, sizeof aidemsg, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
01470               Params[2].Key,
01471               Params[3].Key,
01472               CC->curr_user
01473        );
01474        CtdlAideMessage(aidemsg, "IMAP folder rename");
01475 
01476        IReply("OK RENAME completed");
01477 }
01478 
01479 
01480 /* 
01481  * Main command loop for IMAP sessions.
01482  */
01483 void imap_command_loop(void)
01484 {
01485        struct CitContext *CCC = CC;
01486        struct timeval tv1, tv2;
01487        suseconds_t total_time = 0;
01488        citimap *Imap;
01489        const char *pchs, *pche;
01490        const imap_handler_hook *h;
01491 
01492        gettimeofday(&tv1, NULL);
01493        CCC->lastcmd = time(NULL);
01494        Imap = CCCIMAP;
01495 
01496        flush_output();
01497        if (Imap->Cmd.CmdBuf == NULL)
01498               Imap->Cmd.CmdBuf = NewStrBufPlain(NULL, SIZ);
01499        else
01500               FlushStrBuf(Imap->Cmd.CmdBuf);
01501 
01502        if (CtdlClientGetLine(Imap->Cmd.CmdBuf) < 1) {
01503               IMAPM_syslog(LOG_ERR, "client disconnected: ending session.");
01504               CC->kill_me = KILLME_CLIENT_DISCONNECTED;
01505               return;
01506        }
01507 
01508        if (Imap->authstate == imap_as_expecting_password) {
01509               IMAPM_syslog(LOG_INFO, "<password>");
01510        }
01511        else if (Imap->authstate == imap_as_expecting_plainauth) {
01512               IMAPM_syslog(LOG_INFO, "<plain_auth>");
01513        }
01514        else if ((Imap->authstate == imap_as_expecting_multilineusername) || 
01515                cbmstrcasestr(ChrPtr(Imap->Cmd.CmdBuf), " LOGIN ")) {
01516               IMAPM_syslog(LOG_INFO, "LOGIN...");
01517        }
01518        else {
01519               IMAP_syslog(LOG_DEBUG, "%s", ChrPtr(Imap->Cmd.CmdBuf));
01520        }
01521 
01522        pchs = ChrPtr(Imap->Cmd.CmdBuf);
01523        pche = pchs + StrLength(Imap->Cmd.CmdBuf);
01524 
01525        while ((pche > pchs) &&
01526               ((*pche == '\n') ||
01527               (*pche == '\r')))
01528        {
01529               pche --;
01530               StrBufCutRight(Imap->Cmd.CmdBuf, 1);
01531        }
01532        StrBufTrim(Imap->Cmd.CmdBuf);
01533 
01534        /* If we're in the middle of a multi-line command, handle that */
01535        switch (Imap->authstate){
01536        case imap_as_expecting_username:
01537               imap_auth_login_user(imap_as_expecting_username);
01538               IUnbuffer();
01539               return;
01540        case imap_as_expecting_multilineusername:
01541               imap_auth_login_user(imap_as_expecting_multilineusername);
01542               IUnbuffer();
01543               return;
01544        case imap_as_expecting_plainauth:
01545               imap_auth_plain();
01546               IUnbuffer();
01547               return;
01548        case imap_as_expecting_password:
01549               imap_auth_login_pass(imap_as_expecting_password);
01550               IUnbuffer();
01551               return;
01552        case imap_as_expecting_multilinepassword:
01553               imap_auth_login_pass(imap_as_expecting_multilinepassword);
01554               IUnbuffer();
01555               return;
01556        default:
01557               break;
01558        }
01559 
01560        /* Ok, at this point we're in normal command mode.
01561         * If the command just submitted does not contain a literal, we
01562         * might think about delivering some untagged stuff...
01563         */
01564 
01565        /* Grab the tag, command, and parameters. */
01566        imap_parameterize(&Imap->Cmd);
01567 #if 0 
01568 /* debug output the parsed vector */
01569        {
01570               int i;
01571               IMAP_syslog(LOG_DEBUG, "----- %ld params", Imap->Cmd.num_parms);
01572 
01573        for (i=0; i < Imap->Cmd.num_parms; i++) {
01574               if (Imap->Cmd.Params[i].len != strlen(Imap->Cmd.Params[i].Key))
01575                      IMAP_syslog(LOG_DEBUG, "*********** %ld != %ld : %s",
01576                                 Imap->Cmd.Params[i].len, 
01577                                 strlen(Imap->Cmd.Params[i].Key),
01578                                   Imap->Cmd.Params[i].Key);
01579               else
01580                      IMAP_syslog(LOG_DEBUG, "%ld : %s",
01581                                 Imap->Cmd.Params[i].len, 
01582                                 Imap->Cmd.Params[i].Key);
01583        }}
01584 #endif
01585 
01586        /* Now for the command set. */
01587        h = imap_lookup(Imap->Cmd.num_parms, Imap->Cmd.Params);
01588 
01589        if (h == NULL)
01590        {
01591               IReply("BAD command unrecognized");
01592               goto BAIL;
01593        }
01594 
01595        /* RFC3501 says that we cannot output untagged data during these commands */
01596        if ((h->Flags & I_FLAG_UNTAGGED) == 0) {
01597 
01598               /* we can put any additional untagged stuff right here in the future */
01599 
01600               /*
01601                * Before processing the command that was just entered... if we happen
01602                * to have a folder selected, we'd like to rescan that folder for new
01603                * messages, and for deletions/changes of existing messages.  This
01604                * could probably be optimized better with some deep thought...
01605                */
01606               if (Imap->selected) {
01607                      imap_rescan_msgids();
01608               }
01609        }
01610 
01611        /* does our command require a logged-in state */
01612        if ((!CC->logged_in) && ((h->Flags & I_FLAG_LOGGED_IN) != 0)) {
01613               IReply("BAD Not logged in.");
01614               goto BAIL;
01615        }
01616 
01617        /* does our command require the SELECT state on a mailbox */
01618        if ((Imap->selected == 0) && ((h->Flags & I_FLAG_SELECT) != 0)){
01619               IReply("BAD no folder selected");
01620               goto BAIL;
01621        }
01622        h->h(Imap->Cmd.num_parms, Imap->Cmd.Params);
01623 
01624        /* If the client transmitted a message we can free it now */
01625 
01626 BAIL:
01627        IUnbuffer();
01628 
01629        imap_free_transmitted_message();
01630 
01631        gettimeofday(&tv2, NULL);
01632        total_time = (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000));
01633        IMAP_syslog(LOG_DEBUG, "IMAP command completed in %ld.%ld seconds",
01634                   (total_time / 1000000),
01635                   (total_time % 1000000)
01636               );
01637 }
01638 
01639 void imap_noop (int num_parms, ConstStr *Params)
01640 {
01641        IReply("OK No operation");
01642 }
01643 
01644 void imap_logout(int num_parms, ConstStr *Params)
01645 {
01646        if (IMAP->selected) {
01647               imap_do_expunge();   /* yes, we auto-expunge at logout */
01648        }
01649        IAPrintf("* BYE %s logging out\r\n", config.c_fqdn);
01650        IReply("OK Citadel IMAP session ended.");
01651        CC->kill_me = KILLME_CLIENT_LOGGED_OUT;
01652        return;
01653 }
01654 
01655 const char *CitadelServiceIMAP="IMAP";
01656 const char *CitadelServiceIMAPS="IMAPS";
01657 
01658 void SetIMAPDebugEnabled(const int n)
01659 {
01660        IMAPDebugEnabled = n;
01661 }
01662 /*
01663  * This function is called to register the IMAP extension with Citadel.
01664  */
01665 CTDL_MODULE_INIT(imap)
01666 {
01667        if (ImapCmds == NULL)
01668               ImapCmds = NewHash(1, NULL);
01669 
01670        RegisterImapCMD("NOOP", "", imap_noop, I_FLAG_NONE);
01671        RegisterImapCMD("CHECK", "", imap_noop, I_FLAG_NONE);
01672        RegisterImapCMD("ID", "", imap_id, I_FLAG_NONE);
01673        RegisterImapCMD("LOGOUT", "", imap_logout, I_FLAG_NONE);
01674        RegisterImapCMD("LOGIN", "", imap_login, I_FLAG_NONE);
01675        RegisterImapCMD("AUTHENTICATE", "", imap_authenticate, I_FLAG_NONE);
01676        RegisterImapCMD("CAPABILITY", "", imap_capability, I_FLAG_NONE);
01677 #ifdef HAVE_OPENSSL
01678        RegisterImapCMD("STARTTLS", "", imap_starttls, I_FLAG_NONE);
01679 #endif
01680 
01681        /* The commans below require a logged-in state */
01682        RegisterImapCMD("SELECT", "", imap_select, I_FLAG_LOGGED_IN);
01683        RegisterImapCMD("EXAMINE", "", imap_select, I_FLAG_LOGGED_IN);
01684        RegisterImapCMD("LSUB", "", imap_list, I_FLAG_LOGGED_IN);
01685        RegisterImapCMD("LIST", "", imap_list, I_FLAG_LOGGED_IN);
01686        RegisterImapCMD("CREATE", "", imap_create, I_FLAG_LOGGED_IN);
01687        RegisterImapCMD("DELETE", "", imap_delete, I_FLAG_LOGGED_IN);
01688        RegisterImapCMD("RENAME", "", imap_rename, I_FLAG_LOGGED_IN);
01689        RegisterImapCMD("STATUS", "", imap_status, I_FLAG_LOGGED_IN);
01690        RegisterImapCMD("SUBSCRIBE", "", imap_subscribe, I_FLAG_LOGGED_IN);
01691        RegisterImapCMD("UNSUBSCRIBE", "", imap_unsubscribe, I_FLAG_LOGGED_IN);
01692        RegisterImapCMD("APPEND", "", imap_append, I_FLAG_LOGGED_IN);
01693        RegisterImapCMD("NAMESPACE", "", imap_namespace, I_FLAG_LOGGED_IN);
01694        RegisterImapCMD("SETACL", "", imap_setacl, I_FLAG_LOGGED_IN);
01695        RegisterImapCMD("DELETEACL", "", imap_deleteacl, I_FLAG_LOGGED_IN);
01696        RegisterImapCMD("GETACL", "", imap_getacl, I_FLAG_LOGGED_IN);
01697        RegisterImapCMD("LISTRIGHTS", "", imap_listrights, I_FLAG_LOGGED_IN);
01698        RegisterImapCMD("MYRIGHTS", "", imap_myrights, I_FLAG_LOGGED_IN);
01699        RegisterImapCMD("GETMETADATA", "", imap_getmetadata, I_FLAG_LOGGED_IN);
01700        RegisterImapCMD("SETMETADATA", "", imap_setmetadata, I_FLAG_LOGGED_IN);
01701 
01702        /* The commands below require the SELECT state on a mailbox */
01703        RegisterImapCMD("FETCH", "", imap_fetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
01704        RegisterImapCMD("UID", "FETCH", imap_uidfetch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01705        RegisterImapCMD("SEARCH", "", imap_search, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
01706        RegisterImapCMD("UID", "SEARCH", imap_uidsearch, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01707        RegisterImapCMD("STORE", "", imap_store, I_FLAG_LOGGED_IN | I_FLAG_SELECT | I_FLAG_UNTAGGED);
01708        RegisterImapCMD("UID", "STORE", imap_uidstore, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01709        RegisterImapCMD("COPY", "", imap_copy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01710        RegisterImapCMD("UID", "COPY", imap_uidcopy, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01711        RegisterImapCMD("EXPUNGE", "", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01712        RegisterImapCMD("UID", "EXPUNGE", imap_expunge, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01713        RegisterImapCMD("CLOSE", "", imap_close, I_FLAG_LOGGED_IN | I_FLAG_SELECT);
01714 
01715        if (!threading)
01716        {
01717               CtdlRegisterDebugFlagHook(HKEY("imapsrv"), SetIMAPDebugEnabled, &IMAPDebugEnabled);
01718               CtdlRegisterServiceHook(config.c_imap_port,
01719                                    NULL, imap_greeting, imap_command_loop, NULL, CitadelServiceIMAP);
01720 #ifdef HAVE_OPENSSL
01721               CtdlRegisterServiceHook(config.c_imaps_port,
01722                                    NULL, imaps_greeting, imap_command_loop, NULL, CitadelServiceIMAPS);
01723 #endif
01724               CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP, PRIO_STOP + 30);
01725               CtdlRegisterCleanupHook(imap_cleanup);
01726        }
01727        
01728        /* return our module name for the log */
01729        return "imap";
01730 }