X-Git-Url: https://code.citadel.org/?a=blobdiff_plain;f=citadel%2Fserv_imap.c;h=9c17ccae7aac8e3d98c231a46fe120d4e6454de0;hb=02d9dbca862ffa00b2041287596119a54867762c;hp=1646b1fa634deacc628a58a0202941b8398c42bd;hpb=6d06bc14dacf74d97a8c710eaed338511087154b;p=citadel.git diff --git a/citadel/serv_imap.c b/citadel/serv_imap.c index 1646b1fa6..9c17ccae7 100644 --- a/citadel/serv_imap.c +++ b/citadel/serv_imap.c @@ -1,12 +1,13 @@ /* * $Id$ * - * IMAP server for the Citadel/UX system - * Copyright (C) 2000-2002 by Art Cancro and others. + * IMAP server for the Citadel system + * Copyright (C) 2000-2006 by Art Cancro and others. * This code is released under the terms of the GNU General Public License. * - * WARNING: Mark Crispin is an idiot. IMAP is the most brain-damaged protocol - * you will ever have the profound lack of pleasure to encounter. + * WARNING: the IMAP protocol is badly designed. No implementation of it + * is perfect. Indeed, with so much gratuitous complexity, *all* IMAP + * implementations have bugs. */ #include "sysdep.h" @@ -81,14 +82,16 @@ struct irlparms { void imap_free_msgids(void) { if (IMAP->msgids != NULL) { - phree(IMAP->msgids); + free(IMAP->msgids); IMAP->msgids = NULL; IMAP->num_msgs = 0; + IMAP->num_alloc = 0; } if (IMAP->flags != NULL) { - phree(IMAP->flags); + free(IMAP->flags); IMAP->flags = NULL; } + IMAP->last_mtime = (-1); } @@ -98,7 +101,7 @@ void imap_free_msgids(void) void imap_free_transmitted_message(void) { if (IMAP->transmitted_message != NULL) { - phree(IMAP->transmitted_message); + free(IMAP->transmitted_message); IMAP->transmitted_message = NULL; IMAP->transmitted_length = 0; } @@ -106,27 +109,88 @@ void imap_free_transmitted_message(void) /* - * Set the \\Seen flag for messages which aren't new + * Set the \Seen, \Recent. and \Answered flags, based on the sequence + * sets stored in the visit record for this user/room. Note that we have + * to parse each sequence set manually here, because calling the utility + * function is_msg_in_sequence_set() over and over again is too expensive. + * + * first_msg should be set to 0 to rescan the flags for every message in the + * room, or some other value if we're only interested in an incremental + * update. */ -void imap_set_seen_flags(void) +void imap_set_seen_flags(int first_msg) { struct visit vbuf; int i; + int num_sets; + int s; + char setstr[64], lostr[64], histr[64]; + long lo, hi; + if (IMAP->num_msgs < 1) return; CtdlGetRelationship(&vbuf, &CC->user, &CC->room); - if (IMAP->num_msgs > 0) { - for (i = 0; i < IMAP->num_msgs; ++i) { - if (is_msg_in_mset(vbuf.v_seen, IMAP->msgids[i])) { + + for (i = first_msg; i < IMAP->num_msgs; ++i) { + IMAP->flags[i] = IMAP->flags[i] & ~IMAP_SEEN; + IMAP->flags[i] |= IMAP_RECENT; + IMAP->flags[i] = IMAP->flags[i] & ~IMAP_ANSWERED; + } + + /* + * Do the "\Seen" flag. + * (Any message not "\Seen" is considered "\Recent".) + */ + num_sets = num_tokens(vbuf.v_seen, ','); + for (s=0; s= 2) { + extract_token(histr, setstr, 1, ':', sizeof histr); + if (!strcmp(histr, "*")) { + snprintf(histr, sizeof histr, "%ld", LONG_MAX); + } + } + else { + strcpy(histr, lostr); + } + lo = atol(lostr); + hi = atol(histr); + + for (i = first_msg; i < IMAP->num_msgs; ++i) { + if ((IMAP->msgids[i] >= lo) && (IMAP->msgids[i] <= hi)){ IMAP->flags[i] |= IMAP_SEEN; + IMAP->flags[i] = IMAP->flags[i] & ~IMAP_RECENT; + } + } + } + + /* Do the ANSWERED flag */ + num_sets = num_tokens(vbuf.v_answered, ','); + for (s=0; s= 2) { + extract_token(histr, setstr, 1, ':', sizeof histr); + if (!strcmp(histr, "*")) { + snprintf(histr, sizeof histr, "%ld", LONG_MAX); } - if (is_msg_in_mset - (vbuf.v_answered, IMAP->msgids[i])) { + } + else { + strcpy(histr, lostr); + } + lo = atol(lostr); + hi = atol(histr); + + for (i = first_msg; i < IMAP->num_msgs; ++i) { + if ((IMAP->msgids[i] >= lo) && (IMAP->msgids[i] <= hi)){ IMAP->flags[i] |= IMAP_ANSWERED; } } } -} +} @@ -140,22 +204,13 @@ void imap_set_seen_flags(void) void imap_add_single_msgid(long msgnum, void *userdata) { - IMAP->num_msgs = IMAP->num_msgs + 1; - if (IMAP->msgids == NULL) { - IMAP->msgids = mallok(IMAP->num_msgs * sizeof(long) - * REALLOC_INCREMENT); - } else if (IMAP->num_msgs % REALLOC_INCREMENT == 0) { - IMAP->msgids = reallok(IMAP->msgids, - (IMAP->num_msgs + - REALLOC_INCREMENT) * sizeof(long)); - } - if (IMAP->flags == NULL) { - IMAP->flags = mallok(IMAP->num_msgs * sizeof(long) - * REALLOC_INCREMENT); - } else if (IMAP->num_msgs % REALLOC_INCREMENT == 0) { - IMAP->flags = reallok(IMAP->flags, - (IMAP->num_msgs + - REALLOC_INCREMENT) * sizeof(long)); + ++IMAP->num_msgs; + if (IMAP->num_msgs > IMAP->num_alloc) { + IMAP->num_alloc += REALLOC_INCREMENT; + IMAP->msgids = realloc(IMAP->msgids, + (IMAP->num_alloc * sizeof(long)) ); + IMAP->flags = realloc(IMAP->flags, + (IMAP->num_alloc * sizeof(long)) ); } IMAP->msgids[IMAP->num_msgs - 1] = msgnum; IMAP->flags[IMAP->num_msgs - 1] = 0; @@ -168,6 +223,7 @@ void imap_add_single_msgid(long msgnum, void *userdata) */ void imap_load_msgids(void) { + struct cdbdata *cdbfr; if (IMAP->selected == 0) { lprintf(CTDL_ERR, @@ -177,12 +233,22 @@ void imap_load_msgids(void) imap_free_msgids(); /* If there was already a map, free it */ - CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, - imap_add_single_msgid, NULL); + /* Load the message list */ + cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); + if (cdbfr != NULL) { + IMAP->msgids = malloc(cdbfr->len); + memcpy(IMAP->msgids, cdbfr->ptr, cdbfr->len); + IMAP->num_msgs = cdbfr->len / sizeof(long); + IMAP->num_alloc = cdbfr->len / sizeof(long); + cdb_free(cdbfr); + } + + if (IMAP->num_msgs) { + IMAP->flags = malloc(IMAP->num_alloc * sizeof(long)); + memset(IMAP->flags, 0, (IMAP->num_alloc * sizeof(long)) ); + } - imap_set_seen_flags(); - lprintf(CTDL_DEBUG, "imap_load_msgids() mapped %d messages\n", - IMAP->num_msgs); + imap_set_seen_flags(0); } @@ -194,12 +260,12 @@ void imap_rescan_msgids(void) int original_num_msgs = 0; long original_highest = 0L; - int i, j; + int i, j, jstart; int message_still_exists; struct cdbdata *cdbfr; long *msglist = NULL; int num_msgs = 0; - + int num_recent = 0; if (IMAP->selected == 0) { lprintf(CTDL_ERR, @@ -207,13 +273,26 @@ void imap_rescan_msgids(void) return; } + /* + * Check to see if the room's contents have changed. + * If not, we can avoid this rescan. + */ + getroom(&CC->room, CC->room.QRname); + if (IMAP->last_mtime == CC->room.QRmtime) { /* No changes! */ + return; + } + /* Load the *current* message list from disk, so we can compare it * to what we have in memory. */ cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long)); if (cdbfr != NULL) { - msglist = mallok(cdbfr->len); - memcpy(msglist, cdbfr->ptr, cdbfr->len); + msglist = malloc(cdbfr->len); + if (msglist == NULL) { + lprintf(CTDL_CRIT, "malloc() failed\n"); + abort(); + } + memcpy(msglist, cdbfr->ptr, (size_t)cdbfr->len); num_msgs = cdbfr->len / sizeof(long); cdb_free(cdbfr); } else { @@ -223,23 +302,28 @@ void imap_rescan_msgids(void) /* * Check to see if any of the messages we know about have been expunged */ - if (IMAP->num_msgs > 0) + if (IMAP->num_msgs > 0) { + jstart = 0; for (i = 0; i < IMAP->num_msgs; ++i) { message_still_exists = 0; - if (num_msgs > 0) - for (j = 0; j < num_msgs; ++j) { + if (num_msgs > 0) { + for (j = jstart; j < num_msgs; ++j) { if (msglist[j] == IMAP->msgids[i]) { message_still_exists = 1; + jstart = j; + break; } } + } if (message_still_exists == 0) { cprintf("* %d EXPUNGE\r\n", i + 1); - /* Here's some nice stupid nonsense. When a message - * is expunged, we have to slide all the existing - * messages up in the message array. + /* Here's some nice stupid nonsense. When a + * message is expunged, we have to slide all + * the existing messages up in the message + * array. */ --IMAP->num_msgs; memcpy(&IMAP->msgids[i], @@ -255,6 +339,7 @@ void imap_rescan_msgids(void) } } + } /* * Remember how many messages were here before we re-scanned. @@ -269,23 +354,34 @@ void imap_rescan_msgids(void) /* * Now peruse the room for *new* messages only. */ - if (num_msgs > 0) + if (num_msgs > 0) { for (j = 0; j < num_msgs; ++j) { if (msglist[j] > original_highest) { imap_add_single_msgid(msglist[j], NULL); } } - imap_set_seen_flags(); + } + imap_set_seen_flags(original_num_msgs); /* * If new messages have arrived, tell the client about them. */ if (IMAP->num_msgs > original_num_msgs) { + + for (j = 0; j < num_msgs; ++j) { + if (IMAP->flags[j] & IMAP_RECENT) { + ++num_recent; + } + } + cprintf("* %d EXISTS\r\n", IMAP->num_msgs); + cprintf("* %d RECENT\r\n", num_recent); } - if (num_msgs != 0) - phree(msglist); + if (num_msgs != 0) { + free(msglist); + } + IMAP->last_mtime = CC->room.QRmtime; } @@ -305,26 +401,74 @@ void imap_cleanup_function(void) if (CC->h_command_function != imap_command_loop) return; + /* If there is a mailbox selected, auto-expunge it. */ + if (IMAP->selected) { + imap_do_expunge(); + } + lprintf(CTDL_DEBUG, "Performing IMAP cleanup hook\n"); imap_free_msgids(); imap_free_transmitted_message(); - if (IMAP->cached_fetch != NULL) { - fclose(IMAP->cached_fetch); - IMAP->cached_fetch = NULL; - IMAP->cached_msgnum = (-1); + if (IMAP->cached_rfc822_data != NULL) { + free(IMAP->cached_rfc822_data); + IMAP->cached_rfc822_data = NULL; + IMAP->cached_rfc822_msgnum = (-1); + IMAP->cached_rfc822_withbody = 0; } if (IMAP->cached_body != NULL) { - fclose(IMAP->cached_body); + free(IMAP->cached_body); IMAP->cached_body = NULL; + IMAP->cached_body_len = 0; IMAP->cached_bodymsgnum = (-1); } + free(IMAP); lprintf(CTDL_DEBUG, "Finished IMAP cleanup hook\n"); } +/* + * Does the actual work of the CAPABILITY command (because we need to + * output this stuff in other places as well) + */ +void imap_output_capability_string(void) { + cprintf("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=LOGIN"); +#ifdef HAVE_OPENSSL + if (!CC->redirect_ssl) cprintf(" STARTTLS"); +#endif +} + +/* + * implements the CAPABILITY command + */ +void imap_capability(int num_parms, char *parms[]) +{ + cprintf("* "); + imap_output_capability_string(); + cprintf("\r\n"); + cprintf("%s OK CAPABILITY completed\r\n", parms[0]); +} + + + +/* + * Implements the ID command (specified by RFC2971) + * + * We ignore the client-supplied information, and output a NIL response. + * Although this is technically a valid implementation of the extension, it + * is quite useless. It exists only so that we may see which clients are + * making use of this extension. + * + */ +void imap_id(int num_parms, char *parms[]) +{ + cprintf("* ID NIL\r\n"); + cprintf("%s OK ID completed\r\n", parms[0]); +} + + /* * Here's where our IMAP session begins its happy day. @@ -333,14 +477,27 @@ void imap_greeting(void) { strcpy(CC->cs_clientname, "IMAP session"); - CtdlAllocUserData(SYM_IMAP, sizeof(struct citimap)); + IMAP = malloc(sizeof (struct citimap)); + memset(IMAP, 0, sizeof(struct citimap)); IMAP->authstate = imap_as_normal; - IMAP->cached_fetch = NULL; - IMAP->cached_msgnum = (-1); + IMAP->cached_rfc822_data = NULL; + IMAP->cached_rfc822_msgnum = (-1); + IMAP->cached_rfc822_withbody = 0; + + cprintf("* OK ["); + imap_output_capability_string(); + cprintf("] %s IMAP4rev1 %s ready\r\n", config.c_fqdn, CITADEL); +} - cprintf("* OK %s Citadel/UX IMAP4rev1 server ready\r\n", - config.c_fqdn); +/* + * IMAPS is just like IMAP, except it goes crypto right away. + */ +#ifdef HAVE_OPENSSL +void imaps_greeting(void) { + CtdlStartTLS(NULL, NULL, NULL); + imap_greeting(); } +#endif /* @@ -348,9 +505,16 @@ void imap_greeting(void) */ void imap_login(int num_parms, char *parms[]) { + if (num_parms != 4) { + cprintf("%s BAD incorrect number of parameters\r\n", parms[0]); + return; + } + if (CtdlLoginExistingUser(parms[2]) == login_ok) { if (CtdlTryPassword(parms[3]) == pass_ok) { - cprintf("%s OK login successful\r\n", parms[0]); + cprintf("%s OK [", parms[0]); + imap_output_capability_string(); + cprintf("] Hello, %s\r\n", CC->user.fullname); return; } } @@ -419,23 +583,6 @@ void imap_auth_login_pass(char *cmd) } -/* - * implements the CAPABILITY command - */ -void imap_capability(int num_parms, char *parms[]) -{ - cprintf("* CAPABILITY IMAP4 IMAP4REV1 NAMESPACE AUTH=LOGIN"); - -#ifdef HAVE_OPENSSL - cprintf(" STARTTLS"); -#endif - - cprintf("\r\n"); - cprintf("%s OK CAPABILITY completed\r\n", parms[0]); -} - - - /* * implements the STARTTLS command (Citadel API version) */ @@ -501,7 +648,7 @@ void imap_select(int num_parms, char *parms[]) /* If the room exists, check security/access */ if (c == 0) { /* See if there is an existing user/room relationship */ - ra = CtdlRoomAccess(&QRscratch, &CC->user); + CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); /* normal clients have to pass through security */ if (ra & UA_KNOWN) { @@ -513,7 +660,6 @@ void imap_select(int num_parms, char *parms[]) if (!ok) { cprintf("%s NO ... no such room, or access denied\r\n", parms[0]); - /* IMAP->selected = 0; */ return; } @@ -535,19 +681,22 @@ void imap_select(int num_parms, char *parms[]) } imap_load_msgids(); + IMAP->last_mtime = CC->room.QRmtime; cprintf("* %d EXISTS\r\n", msgs); cprintf("* %d RECENT\r\n", new); + cprintf("* OK [UIDVALIDITY 1] UID validity status\r\n"); + cprintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CitControl.MMhighest + 1); + /* Note that \Deleted is a valid flag, but not a permanent flag, * because we don't maintain its state across sessions. Citadel * automatically expunges mailboxes when they are de-selected. */ cprintf("* FLAGS (\\Deleted \\Seen \\Answered)\r\n"); - cprintf("* OK [PERMANENTFLAGS (\\Seen \\Answered)] " + cprintf("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] " "permanent flags\r\n"); - cprintf("* OK [UIDVALIDITY 0] UIDs valid\r\n"); cprintf("%s OK [%s] %s completed\r\n", parms[0], (IMAP->readonly ? "READ-ONLY" : "READ-WRITE"), parms[1]); @@ -556,35 +705,40 @@ void imap_select(int num_parms, char *parms[]) /* - * does the real work for expunge + * Does the real work for expunge. */ int imap_do_expunge(void) { int i; int num_expunged = 0; + long *delmsgs = NULL; + int num_delmsgs = 0; lprintf(CTDL_DEBUG, "imap_do_expunge() called\n"); - if (IMAP->selected == 0) + if (IMAP->selected == 0) { return (0); + } - if (IMAP->num_msgs > 0) + if (IMAP->num_msgs > 0) { + delmsgs = malloc(IMAP->num_msgs * sizeof(long)); for (i = 0; i < IMAP->num_msgs; ++i) { if (IMAP->flags[i] & IMAP_DELETED) { - CtdlDeleteMessages(CC->room.QRname, - IMAP->msgids[i], ""); - ++num_expunged; - lprintf(CTDL_DEBUG, "%ld ... deleted\n", - IMAP->msgids[i]); - } else { - lprintf(CTDL_DEBUG, "%ld ... not deleted\n", - IMAP->msgids[i]); + delmsgs[num_delmsgs++] = IMAP->msgids[i]; } } + if (num_delmsgs > 0) { + CtdlDeleteMessages(CC->room.QRname, delmsgs, num_delmsgs, ""); + } + num_expunged += num_delmsgs; + free(delmsgs); + } if (num_expunged > 0) { imap_rescan_msgids(); } + lprintf(CTDL_DEBUG, "Expunged %d messages from <%s>\n", + num_expunged, CC->room.QRname); return (num_expunged); } @@ -608,7 +762,9 @@ void imap_close(int num_parms, char *parms[]) { /* Yes, we always expunge on close. */ - imap_do_expunge(); + if (IMAP->selected) { + imap_do_expunge(); + } IMAP->selected = 0; IMAP->readonly = 0; @@ -630,7 +786,7 @@ void imap_namespace(int num_parms, char *parms[]) cprintf("* NAMESPACE "); /* All personal folders are subordinate to INBOX. */ - cprintf("((\"INBOX|\" \"|\")) "); + cprintf("((\"INBOX/\" \"/\")) "); /* Other users' folders ... coming soon! FIXME */ cprintf("NIL "); @@ -642,9 +798,9 @@ void imap_namespace(int num_parms, char *parms[]) if (fl->f_flags & F_INUSE) { if (floors > 0) cprintf(" "); cprintf("("); - sprintf(buf, "%s|", fl->f_name); + sprintf(buf, "%s/", fl->f_name); imap_strout(buf); - cprintf(" \"|\")"); + cprintf(" \"/\")"); ++floors; } } @@ -670,7 +826,7 @@ void imap_list_floors(char *cmd, char *pattern) if (fl->f_flags & F_INUSE) { if (imap_mailbox_matches_pattern (pattern, fl->f_name)) { - cprintf("* %s (\\NoSelect) \"|\" ", cmd); + cprintf("* %s (\\NoSelect) \"/\" ", cmd); imap_strout(fl->f_name); cprintf("\r\n"); } @@ -695,11 +851,11 @@ void imap_lsub_listroom(struct ctdlroom *qrbuf, void *data) pattern = (char *) data; /* Only list rooms to which the user has access!! */ - ra = CtdlRoomAccess(qrbuf, &CC->user); + CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL); if (ra & UA_KNOWN) { imap_mailboxname(buf, sizeof buf, qrbuf); if (imap_mailbox_matches_pattern(pattern, buf)) { - cprintf("* LSUB () \"|\" "); + cprintf("* LSUB () \"/\" "); imap_strout(buf); cprintf("\r\n"); } @@ -720,7 +876,7 @@ void imap_lsub(int num_parms, char *parms[]) snprintf(pattern, sizeof pattern, "%s%s", parms[2], parms[3]); if (strlen(parms[3]) == 0) { - cprintf("* LIST (\\Noselect) \"|\" \"\"\r\n"); + cprintf("* LIST (\\Noselect) \"/\" \"\"\r\n"); } else { @@ -745,12 +901,12 @@ void imap_list_listroom(struct ctdlroom *qrbuf, void *data) pattern = (char *) data; /* Only list rooms to which the user has access!! */ - ra = CtdlRoomAccess(qrbuf, &CC->user); + CtdlRoomAccess(qrbuf, &CC->user, &ra, NULL); if ((ra & UA_KNOWN) || ((ra & UA_GOTOALLOWED) && (ra & UA_ZAPPED))) { imap_mailboxname(buf, sizeof buf, qrbuf); if (imap_mailbox_matches_pattern(pattern, buf)) { - cprintf("* LIST () \"|\" "); + cprintf("* LIST () \"/\" "); imap_strout(buf); cprintf("\r\n"); } @@ -771,7 +927,7 @@ void imap_list(int num_parms, char *parms[]) snprintf(pattern, sizeof pattern, "%s%s", parms[2], parms[3]); if (strlen(parms[3]) == 0) { - cprintf("* LIST (\\Noselect) \"|\" \"\"\r\n"); + cprintf("* LIST (\\Noselect) \"/\" \"\"\r\n"); } else { @@ -794,12 +950,13 @@ void imap_create(int num_parms, char *parms[]) char roomname[ROOMNAMELEN]; int floornum; int flags; - int newroomtype; + int newroomtype = 0; + int newroomview = 0; if (strchr(parms[2], '\\') != NULL) { cprintf("%s NO Invalid character in folder name\r\n", parms[0]); - lprintf(9, "invalid character in folder name\n"); + lprintf(CTDL_DEBUG, "invalid character in folder name\n"); return; } @@ -807,30 +964,32 @@ void imap_create(int num_parms, char *parms[]) if (ret < 0) { cprintf("%s NO Invalid mailbox name or location\r\n", parms[0]); - lprintf(9, "invalid mailbox name or location\n"); + lprintf(CTDL_DEBUG, "invalid mailbox name or location\n"); return; } floornum = (ret & 0x00ff); /* lower 8 bits = floor number */ flags = (ret & 0xff00); /* upper 8 bits = flags */ if (flags & IR_MAILBOX) { - if (strncasecmp(parms[2], "INBOX|", 6)) { + if (strncasecmp(parms[2], "INBOX/", 6)) { cprintf("%s NO Personal folders must be created under INBOX\r\n", parms[0]); - lprintf(9, "not subordinate to inbox\n"); + lprintf(CTDL_DEBUG, "not subordinate to inbox\n"); return; } } if (flags & IR_MAILBOX) { - newroomtype = 4; /* private mailbox */ + newroomtype = 4; /* private mailbox */ + newroomview = VIEW_MAILBOX; } else { - newroomtype = 0; /* public folder */ + newroomtype = 0; /* public folder */ + newroomview = VIEW_BBS; } lprintf(CTDL_INFO, "Create new room <%s> on floor <%d> with type <%d>\n", roomname, floornum, newroomtype); - ret = create_room(roomname, newroomtype, "", floornum, 1, 0); + ret = create_room(roomname, newroomtype, "", floornum, 1, 0, newroomview); if (ret == 0) { cprintf ("%s NO Mailbox already exists, or create failed\r\n", @@ -838,14 +997,15 @@ void imap_create(int num_parms, char *parms[]) } else { cprintf("%s OK CREATE completed\r\n", parms[0]); } - lprintf(9, "imap_create() completed\n"); + lprintf(CTDL_DEBUG, "imap_create() completed\n"); } /* - * Locate a room by its IMAP folder name, and check access to it + * Locate a room by its IMAP folder name, and check access to it. + * If zapped_ok is nonzero, we can also look for the room in the zapped list. */ -int imap_grabroom(char *returned_roomname, char *foldername) +int imap_grabroom(char *returned_roomname, char *foldername, int zapped_ok) { int ret; char augmented_roomname[ROOMNAMELEN]; @@ -875,12 +1035,15 @@ int imap_grabroom(char *returned_roomname, char *foldername) /* If the room exists, check security/access */ if (c == 0) { /* See if there is an existing user/room relationship */ - ra = CtdlRoomAccess(&QRscratch, &CC->user); + CtdlRoomAccess(&QRscratch, &CC->user, &ra, NULL); /* normal clients have to pass through security */ if (ra & UA_KNOWN) { ok = 1; } + if ((zapped_ok) && (ra & UA_ZAPPED)) { + ok = 1; + } } /* Fail here if no such room */ @@ -906,7 +1069,7 @@ void imap_status(int num_parms, char *parms[]) char savedroom[ROOMNAMELEN]; int msgs, new; - ret = imap_grabroom(roomname, parms[2]); + ret = imap_grabroom(roomname, parms[2], 0); if (ret != 0) { cprintf ("%s NO Invalid mailbox name or location, or access denied\r\n", @@ -934,7 +1097,7 @@ void imap_status(int num_parms, char *parms[]) cprintf("* STATUS "); imap_strout(buf); cprintf(" (MESSAGES %d ", msgs); - cprintf("RECENT 0 "); /* FIXME we need to implement this */ + cprintf("RECENT %d ", new); /* Initially, new==recent */ cprintf("UIDNEXT %ld ", CitControl.MMhighest + 1); cprintf("UNSEEN %d)\r\n", new); @@ -965,11 +1128,13 @@ void imap_subscribe(int num_parms, char *parms[]) char savedroom[ROOMNAMELEN]; int msgs, new; - ret = imap_grabroom(roomname, parms[2]); + ret = imap_grabroom(roomname, parms[2], 1); if (ret != 0) { - cprintf - ("%s NO Invalid mailbox name or location, or access denied\r\n", - parms[0]); + cprintf( + "%s NO Error %d: invalid mailbox name or location, or access denied\r\n", + parms[0], + ret + ); return; } @@ -1006,7 +1171,7 @@ void imap_unsubscribe(int num_parms, char *parms[]) char savedroom[ROOMNAMELEN]; int msgs, new; - ret = imap_grabroom(roomname, parms[2]); + ret = imap_grabroom(roomname, parms[2], 0); if (ret != 0) { cprintf ("%s NO Invalid mailbox name or location, or access denied\r\n", @@ -1055,7 +1220,7 @@ void imap_delete(int num_parms, char *parms[]) char savedroom[ROOMNAMELEN]; int msgs, new; - ret = imap_grabroom(roomname, parms[2]); + ret = imap_grabroom(roomname, parms[2], 1); if (ret != 0) { cprintf("%s NO Invalid mailbox name, or access denied\r\n", parms[0]); @@ -1076,8 +1241,8 @@ void imap_delete(int num_parms, char *parms[]) * Now delete the room. */ if (CtdlDoIHavePermissionToDeleteThisRoom(&CC->room)) { + schedule_room_for_deletion(&CC->room); cprintf("%s OK DELETE completed\r\n", parms[0]); - delete_room(&CC->room); } else { cprintf("%s NO Can't delete this folder.\r\n", parms[0]); } @@ -1110,9 +1275,9 @@ void imap_rename_backend(struct ctdlroom *qrbuf, void *data) /* Rename subfolders */ if ((!strncasecmp(foldername, irlparms->oldname, strlen(irlparms->oldname)) - && (foldername[strlen(irlparms->oldname)] == '|'))) { + && (foldername[strlen(irlparms->oldname)] == '/'))) { - sprintf(newfoldername, "%s|%s", + sprintf(newfoldername, "%s/%s", irlparms->newname, &foldername[strlen(irlparms->oldname) + 1] ); @@ -1121,7 +1286,7 @@ void imap_rename_backend(struct ctdlroom *qrbuf, void *data) sizeof newroomname, newfoldername) & 0xFF; - irlp = (struct irl *) mallok(sizeof(struct irl)); + irlp = (struct irl *) malloc(sizeof(struct irl)); strcpy(irlp->irl_newroom, newroomname); strcpy(irlp->irl_oldroom, qrbuf->QRname); irlp->irl_newfloor = newfloor; @@ -1192,7 +1357,7 @@ void imap_rename(int num_parms, char *parms[]) * (already did that) and create a new inbox. */ if (!strcasecmp(parms[2], "INBOX")) { - create_room(MAILROOM, 4, "", 0, 1, 0); + create_room(MAILROOM, 4, "", 0, 1, 0, VIEW_MAILBOX); } /* Otherwise, do the subfolders. Build a list of rooms to rename... */ @@ -1209,12 +1374,11 @@ void imap_rename(int num_parms, char *parms[]) irl->irl_newfloor); if (r != crr_ok) { /* FIXME handle error returns better */ - lprintf(CTDL_ERR, "CtdlRenameRoom() error %d\n", - r); + lprintf(CTDL_ERR, "CtdlRenameRoom() error %d\n", r); } irlp = irl; irl = irl->next; - phree(irlp); + free(irlp); } } @@ -1232,20 +1396,31 @@ void imap_command_loop(void) char cmdbuf[SIZ]; char *parms[SIZ]; int num_parms; + struct timeval tv1, tv2; - time(&CC->lastcmd); + gettimeofday(&tv1, NULL); + CC->lastcmd = time(NULL); memset(cmdbuf, 0, sizeof cmdbuf); /* Clear it, just in case */ - if (client_gets(cmdbuf) < 1) { - lprintf(CTDL_ERR, "IMAP socket is broken. Ending session.\r\n"); + flush_output(); + if (client_getln(cmdbuf, sizeof cmdbuf) < 1) { + lprintf(CTDL_ERR, "Client disconnected: ending session.\r\n"); CC->kill_me = 1; return; } - lprintf(CTDL_INFO, "IMAP: %s\r\n", cmdbuf); + if (IMAP->authstate == imap_as_expecting_password) { + lprintf(CTDL_INFO, "IMAP: \n"); + } + else if (bmstrcasestr(cmdbuf, " LOGIN ")) { + lprintf(CTDL_INFO, "IMAP: LOGIN...\n"); + } + else { + lprintf(CTDL_INFO, "IMAP: %s\n", cmdbuf); + } + while (strlen(cmdbuf) < 5) strcat(cmdbuf, " "); - /* strip off l/t whitespace and CRLF */ if (cmdbuf[strlen(cmdbuf) - 1] == '\n') cmdbuf[strlen(cmdbuf) - 1] = 0; @@ -1263,11 +1438,10 @@ void imap_command_loop(void) return; } - /* Ok, at this point we're in normal command mode. The first thing * we do is print any incoming pages (yeah! we really do!) */ - imap_print_express_messages(); + imap_print_instant_messages(); /* * Before processing the command that was just entered... if we happen @@ -1295,8 +1469,15 @@ void imap_command_loop(void) parms[0]); } + else if (!strcasecmp(parms[1], "ID")) { + imap_id(num_parms, parms); + } + + else if (!strcasecmp(parms[1], "LOGOUT")) { - imap_do_expunge(); /* yes, we auto-expunge */ + if (IMAP->selected) { + imap_do_expunge(); /* yes, we auto-expunge */ + } cprintf("* BYE %s logging out\r\n", config.c_fqdn); cprintf("%s OK thank you for using Citadel IMAP\r\n", parms[0]); @@ -1434,6 +1615,12 @@ void imap_command_loop(void) /* If the client transmitted a message we can free it now */ imap_free_transmitted_message(); + + gettimeofday(&tv2, NULL); + lprintf(CTDL_DEBUG, "IMAP %s took %ld microseconds\n", + parms[1], + (tv2.tv_usec + (tv2.tv_sec * 1000000)) - (tv1.tv_usec + (tv1.tv_sec * 1000000)) + ); } @@ -1444,7 +1631,11 @@ void imap_command_loop(void) char *serv_imap_init(void) { CtdlRegisterServiceHook(config.c_imap_port, - NULL, imap_greeting, imap_command_loop); + NULL, imap_greeting, imap_command_loop, NULL); +#ifdef HAVE_OPENSSL + CtdlRegisterServiceHook(config.c_imaps_port, + NULL, imaps_greeting, imap_command_loop, NULL); +#endif CtdlRegisterSessionHook(imap_cleanup_function, EVT_STOP); return "$Id$"; }