4 * IMAP server for the Citadel system
5 * Copyright (C) 2000-2007 by Art Cancro and others.
6 * This code is released under the terms of the GNU General Public License.
8 * WARNING: the IMAP protocol is badly designed. No implementation of it
9 * is perfect. Indeed, with so much gratuitous complexity, *all* IMAP
10 * implementations have bugs.
21 #include <sys/types.h>
23 #if TIME_WITH_SYS_TIME
24 # include <sys/time.h>
28 # include <sys/time.h>
38 #include <libcitadel.h>
41 #include "citserver.h"
49 #include "internet_addressing.h"
50 #include "serv_imap.h"
51 #include "imap_tools.h"
52 #include "imap_list.h"
53 #include "imap_fetch.h"
54 #include "imap_search.h"
55 #include "imap_store.h"
57 #include "imap_metadata.h"
58 #include "imap_misc.h"
61 #include "ctdl_module.h"
64 /* imap_rename() uses this struct containing list of rooms to rename */
67 char irl_oldroom
[ROOMNAMELEN
];
68 char irl_newroom
[ROOMNAMELEN
];
72 /* Data which is passed between imap_rename() and imap_rename_backend() */
81 * If there is a message ID map in memory, free it
83 void imap_free_msgids(void)
85 if (IMAP
->msgids
!= NULL
) {
91 if (IMAP
->flags
!= NULL
) {
95 IMAP
->last_mtime
= (-1);
100 * If there is a transmitted message in memory, free it
102 void imap_free_transmitted_message(void)
104 if (IMAP
->transmitted_message
!= NULL
) {
105 free(IMAP
->transmitted_message
);
106 IMAP
->transmitted_message
= NULL
;
107 IMAP
->transmitted_length
= 0;
113 * Set the \Seen, \Recent. and \Answered flags, based on the sequence
114 * sets stored in the visit record for this user/room. Note that we have
115 * to parse each sequence set manually here, because calling the utility
116 * function is_msg_in_sequence_set() over and over again is too expensive.
118 * first_msg should be set to 0 to rescan the flags for every message in the
119 * room, or some other value if we're only interested in an incremental
122 void imap_set_seen_flags(int first_msg
)
128 char setstr
[64], lostr
[64], histr
[64];
131 if (IMAP
->num_msgs
< 1) return;
132 CtdlGetRelationship(&vbuf
, &CC
->user
, &CC
->room
);
134 for (i
= first_msg
; i
< IMAP
->num_msgs
; ++i
) {
135 IMAP
->flags
[i
] = IMAP
->flags
[i
] & ~IMAP_SEEN
;
136 IMAP
->flags
[i
] |= IMAP_RECENT
;
137 IMAP
->flags
[i
] = IMAP
->flags
[i
] & ~IMAP_ANSWERED
;
141 * Do the "\Seen" flag.
142 * (Any message not "\Seen" is considered "\Recent".)
144 num_sets
= num_tokens(vbuf
.v_seen
, ',');
145 for (s
=0; s
<num_sets
; ++s
) {
146 extract_token(setstr
, vbuf
.v_seen
, s
, ',', sizeof setstr
);
148 extract_token(lostr
, setstr
, 0, ':', sizeof lostr
);
149 if (num_tokens(setstr
, ':') >= 2) {
150 extract_token(histr
, setstr
, 1, ':', sizeof histr
);
151 if (!strcmp(histr
, "*")) {
152 snprintf(histr
, sizeof histr
, "%ld", LONG_MAX
);
156 strcpy(histr
, lostr
);
161 for (i
= first_msg
; i
< IMAP
->num_msgs
; ++i
) {
162 if ((IMAP
->msgids
[i
] >= lo
) && (IMAP
->msgids
[i
] <= hi
)){
163 IMAP
->flags
[i
] |= IMAP_SEEN
;
164 IMAP
->flags
[i
] = IMAP
->flags
[i
] & ~IMAP_RECENT
;
169 /* Do the ANSWERED flag */
170 num_sets
= num_tokens(vbuf
.v_answered
, ',');
171 for (s
=0; s
<num_sets
; ++s
) {
172 extract_token(setstr
, vbuf
.v_answered
, s
, ',', sizeof setstr
);
174 extract_token(lostr
, setstr
, 0, ':', sizeof lostr
);
175 if (num_tokens(setstr
, ':') >= 2) {
176 extract_token(histr
, setstr
, 1, ':', sizeof histr
);
177 if (!strcmp(histr
, "*")) {
178 snprintf(histr
, sizeof histr
, "%ld", LONG_MAX
);
182 strcpy(histr
, lostr
);
187 for (i
= first_msg
; i
< IMAP
->num_msgs
; ++i
) {
188 if ((IMAP
->msgids
[i
] >= lo
) && (IMAP
->msgids
[i
] <= hi
)){
189 IMAP
->flags
[i
] |= IMAP_ANSWERED
;
199 * Back end for imap_load_msgids()
201 * Optimization: instead of calling realloc() to add each message, we
202 * allocate space in the list for REALLOC_INCREMENT messages at a time. This
203 * allows the mapping to proceed much faster.
205 void imap_add_single_msgid(long msgnum
, void *userdata
)
209 if (IMAP
->num_msgs
> IMAP
->num_alloc
) {
210 IMAP
->num_alloc
+= REALLOC_INCREMENT
;
211 IMAP
->msgids
= realloc(IMAP
->msgids
,
212 (IMAP
->num_alloc
* sizeof(long)) );
213 IMAP
->flags
= realloc(IMAP
->flags
,
214 (IMAP
->num_alloc
* sizeof(long)) );
216 IMAP
->msgids
[IMAP
->num_msgs
- 1] = msgnum
;
217 IMAP
->flags
[IMAP
->num_msgs
- 1] = 0;
223 * Set up a message ID map for the current room (folder)
225 void imap_load_msgids(void)
227 struct cdbdata
*cdbfr
;
229 if (IMAP
->selected
== 0) {
230 CtdlLogPrintf(CTDL_ERR
,
231 "imap_load_msgids() can't run; no room selected\n");
235 imap_free_msgids(); /* If there was already a map, free it */
237 /* Load the message list */
238 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &CC
->room
.QRnumber
, sizeof(long));
240 IMAP
->msgids
= malloc(cdbfr
->len
);
241 memcpy(IMAP
->msgids
, cdbfr
->ptr
, cdbfr
->len
);
242 IMAP
->num_msgs
= cdbfr
->len
/ sizeof(long);
243 IMAP
->num_alloc
= cdbfr
->len
/ sizeof(long);
247 if (IMAP
->num_msgs
) {
248 IMAP
->flags
= malloc(IMAP
->num_alloc
* sizeof(long));
249 memset(IMAP
->flags
, 0, (IMAP
->num_alloc
* sizeof(long)) );
252 imap_set_seen_flags(0);
257 * Re-scan the selected room (folder) and see if it's been changed at all
259 void imap_rescan_msgids(void)
262 int original_num_msgs
= 0;
263 long original_highest
= 0L;
265 int message_still_exists
;
266 struct cdbdata
*cdbfr
;
267 long *msglist
= NULL
;
271 if (IMAP
->selected
== 0) {
272 CtdlLogPrintf(CTDL_ERR
,
273 "imap_load_msgids() can't run; no room selected\n");
278 * Check to see if the room's contents have changed.
279 * If not, we can avoid this rescan.
281 getroom(&CC
->room
, CC
->room
.QRname
);
282 if (IMAP
->last_mtime
== CC
->room
.QRmtime
) { /* No changes! */
286 /* Load the *current* message list from disk, so we can compare it
287 * to what we have in memory.
289 cdbfr
= cdb_fetch(CDB_MSGLISTS
, &CC
->room
.QRnumber
, sizeof(long));
291 msglist
= malloc(cdbfr
->len
);
292 if (msglist
== NULL
) {
293 CtdlLogPrintf(CTDL_CRIT
, "malloc() failed\n");
296 memcpy(msglist
, cdbfr
->ptr
, (size_t)cdbfr
->len
);
297 num_msgs
= cdbfr
->len
/ sizeof(long);
304 * Check to see if any of the messages we know about have been expunged
306 if (IMAP
->num_msgs
> 0) {
308 for (i
= 0; i
< IMAP
->num_msgs
; ++i
) {
310 message_still_exists
= 0;
312 for (j
= jstart
; j
< num_msgs
; ++j
) {
313 if (msglist
[j
] == IMAP
->msgids
[i
]) {
314 message_still_exists
= 1;
321 if (message_still_exists
== 0) {
322 cprintf("* %d EXPUNGE\r\n", i
+ 1);
324 /* Here's some nice stupid nonsense. When a
325 * message is expunged, we have to slide all
326 * the existing messages up in the message
330 memcpy(&IMAP
->msgids
[i
],
331 &IMAP
->msgids
[i
+ 1],
333 (IMAP
->num_msgs
- i
)));
334 memcpy(&IMAP
->flags
[i
],
337 (IMAP
->num_msgs
- i
)));
346 * Remember how many messages were here before we re-scanned.
348 original_num_msgs
= IMAP
->num_msgs
;
349 if (IMAP
->num_msgs
> 0) {
350 original_highest
= IMAP
->msgids
[IMAP
->num_msgs
- 1];
352 original_highest
= 0L;
356 * Now peruse the room for *new* messages only.
359 for (j
= 0; j
< num_msgs
; ++j
) {
360 if (msglist
[j
] > original_highest
) {
361 imap_add_single_msgid(msglist
[j
], NULL
);
365 imap_set_seen_flags(original_num_msgs
);
368 * If new messages have arrived, tell the client about them.
370 if (IMAP
->num_msgs
> original_num_msgs
) {
372 for (j
= 0; j
< num_msgs
; ++j
) {
373 if (IMAP
->flags
[j
] & IMAP_RECENT
) {
378 cprintf("* %d EXISTS\r\n", IMAP
->num_msgs
);
379 cprintf("* %d RECENT\r\n", num_recent
);
385 IMAP
->last_mtime
= CC
->room
.QRmtime
;
395 * This cleanup function blows away the temporary memory and files used by
398 void imap_cleanup_function(void)
401 /* Don't do this stuff if this is not a IMAP session! */
402 if (CC
->h_command_function
!= imap_command_loop
)
405 /* If there is a mailbox selected, auto-expunge it. */
406 if (IMAP
->selected
) {
410 CtdlLogPrintf(CTDL_DEBUG
, "Performing IMAP cleanup hook\n");
412 imap_free_transmitted_message();
414 if (IMAP
->cached_rfc822_data
!= NULL
) {
415 free(IMAP
->cached_rfc822_data
);
416 IMAP
->cached_rfc822_data
= NULL
;
417 IMAP
->cached_rfc822_msgnum
= (-1);
418 IMAP
->cached_rfc822_withbody
= 0;
421 if (IMAP
->cached_body
!= NULL
) {
422 free(IMAP
->cached_body
);
423 IMAP
->cached_body
= NULL
;
424 IMAP
->cached_body_len
= 0;
425 IMAP
->cached_bodymsgnum
= (-1);
429 CtdlLogPrintf(CTDL_DEBUG
, "Finished IMAP cleanup hook\n");
434 * Does the actual work of the CAPABILITY command (because we need to
435 * output this stuff in other places as well)
437 void imap_output_capability_string(void) {
438 cprintf("CAPABILITY IMAP4REV1 NAMESPACE ID AUTH=PLAIN AUTH=LOGIN UIDPLUS");
441 if (!CC
->redirect_ssl
) cprintf(" STARTTLS");
444 #ifndef DISABLE_IMAP_ACL
448 /* We are building a partial implementation of METADATA for the sole purpose
449 * of interoperating with the ical/vcard version of the Bynari Insight Connector.
450 * It is not a full RFC5464 implementation, but it should refuse non-Bynari
451 * metadata in a compatible and graceful way.
453 cprintf(" METADATA");
456 * LIST-EXTENDED was originally going to be required by the METADATA extension.
457 * It was mercifully removed prior to the finalization of RFC5464. We started
458 * implementing this but stopped when we learned that it would not be needed.
459 * If you uncomment this declaration you are responsible for writing a lot of new
462 * cprintf(" LIST-EXTENDED")
467 * implements the CAPABILITY command
469 void imap_capability(int num_parms
, char *parms
[])
472 imap_output_capability_string();
474 cprintf("%s OK CAPABILITY completed\r\n", parms
[0]);
480 * Implements the ID command (specified by RFC2971)
482 * We ignore the client-supplied information, and output a NIL response.
483 * Although this is technically a valid implementation of the extension, it
484 * is quite useless. It exists only so that we may see which clients are
485 * making use of this extension.
488 void imap_id(int num_parms
, char *parms
[])
490 cprintf("* ID NIL\r\n");
491 cprintf("%s OK ID completed\r\n", parms
[0]);
497 * Here's where our IMAP session begins its happy day.
499 void imap_greeting(void)
502 strcpy(CC
->cs_clientname
, "IMAP session");
503 CC
->session_specific_data
= malloc(sizeof(struct citimap
));
504 memset(IMAP
, 0, sizeof(struct citimap
));
505 IMAP
->authstate
= imap_as_normal
;
506 IMAP
->cached_rfc822_data
= NULL
;
507 IMAP
->cached_rfc822_msgnum
= (-1);
508 IMAP
->cached_rfc822_withbody
= 0;
512 cprintf("* BYE; Server busy, try later\r\n");
517 imap_output_capability_string();
518 cprintf("] %s IMAP4rev1 %s ready\r\n", config
.c_fqdn
, CITADEL
);
522 * IMAPS is just like IMAP, except it goes crypto right away.
524 void imaps_greeting(void) {
525 CtdlModuleStartCryptoMsgs(NULL
, NULL
, NULL
);
527 if (!CC
->redirect_ssl
) CC
->kill_me
= 1; /* kill session if no crypto */
534 * implements the LOGIN command (ordinary username/password login)
536 void imap_login(int num_parms
, char *parms
[])
538 if (num_parms
!= 4) {
539 cprintf("%s BAD incorrect number of parameters\r\n", parms
[0]);
543 if (CtdlLoginExistingUser(NULL
, parms
[2]) == login_ok
) {
544 if (CtdlTryPassword(parms
[3]) == pass_ok
) {
545 cprintf("%s OK [", parms
[0]);
546 imap_output_capability_string();
547 cprintf("] Hello, %s\r\n", CC
->user
.fullname
);
552 cprintf("%s BAD Login incorrect\r\n", parms
[0]);
557 * Implements the AUTHENTICATE command
559 void imap_authenticate(int num_parms
, char *parms
[])
563 if (num_parms
!= 3) {
564 cprintf("%s BAD incorrect number of parameters\r\n",
570 cprintf("%s BAD Already logged in.\r\n", parms
[0]);
574 if (!strcasecmp(parms
[2], "LOGIN")) {
575 CtdlEncodeBase64(buf
, "Username:", 9, 0);
576 cprintf("+ %s\r\n", buf
);
577 IMAP
->authstate
= imap_as_expecting_username
;
578 strcpy(IMAP
->authseq
, parms
[0]);
582 if (!strcasecmp(parms
[2], "PLAIN")) {
583 // CtdlEncodeBase64(buf, "Username:", 9, 0);
584 // cprintf("+ %s\r\n", buf);
586 IMAP
->authstate
= imap_as_expecting_plainauth
;
587 strcpy(IMAP
->authseq
, parms
[0]);
592 cprintf("%s NO AUTHENTICATE %s failed\r\n",
597 void imap_auth_plain(char *cmd
)
599 char decoded_authstring
[1024];
605 CtdlDecodeBase64(decoded_authstring
, cmd
, strlen(cmd
));
606 safestrncpy(ident
, decoded_authstring
, sizeof ident
);
607 safestrncpy(user
, &decoded_authstring
[strlen(ident
) + 1], sizeof user
);
608 safestrncpy(pass
, &decoded_authstring
[strlen(ident
) + strlen(user
) + 2], sizeof pass
);
610 IMAP
->authstate
= imap_as_normal
;
612 if (!IsEmptyStr(ident
)) {
613 result
= CtdlLoginExistingUser(user
, ident
);
616 result
= CtdlLoginExistingUser(NULL
, user
);
619 if (result
== login_ok
) {
620 if (CtdlTryPassword(pass
) == pass_ok
) {
621 cprintf("%s OK authentication succeeded\r\n", IMAP
->authseq
);
625 cprintf("%s NO authentication failed\r\n", IMAP
->authseq
);
628 void imap_auth_login_user(char *cmd
)
632 CtdlDecodeBase64(buf
, cmd
, SIZ
);
633 CtdlLoginExistingUser(NULL
, buf
);
634 CtdlEncodeBase64(buf
, "Password:", 9, 0);
635 cprintf("+ %s\r\n", buf
);
636 IMAP
->authstate
= imap_as_expecting_password
;
640 void imap_auth_login_pass(char *cmd
)
644 CtdlDecodeBase64(buf
, cmd
, SIZ
);
645 if (CtdlTryPassword(buf
) == pass_ok
) {
646 cprintf("%s OK authentication succeeded\r\n", IMAP
->authseq
);
648 cprintf("%s NO authentication failed\r\n", IMAP
->authseq
);
650 IMAP
->authstate
= imap_as_normal
;
656 * implements the STARTTLS command (Citadel API version)
658 void imap_starttls(int num_parms
, char *parms
[])
660 char ok_response
[SIZ
];
661 char nosup_response
[SIZ
];
662 char error_response
[SIZ
];
665 "%s OK begin TLS negotiation now\r\n",
667 sprintf(nosup_response
,
668 "%s NO TLS not supported here\r\n",
670 sprintf(error_response
,
671 "%s BAD Internal error\r\n",
673 CtdlModuleStartCryptoMsgs(ok_response
, nosup_response
, error_response
);
678 * implements the SELECT command
680 void imap_select(int num_parms
, char *parms
[])
683 char augmented_roomname
[ROOMNAMELEN
];
687 struct ctdlroom QRscratch
;
693 /* Convert the supplied folder name to a roomname */
694 i
= imap_roomname(towhere
, sizeof towhere
, parms
[2]);
696 cprintf("%s NO Invalid mailbox name.\r\n", parms
[0]);
700 floornum
= (i
& 0x00ff);
701 roomflags
= (i
& 0xff00);
703 /* First try a regular match */
704 c
= getroom(&QRscratch
, towhere
);
706 /* Then try a mailbox name match */
708 MailboxName(augmented_roomname
, sizeof augmented_roomname
,
710 c
= getroom(&QRscratch
, augmented_roomname
);
712 strcpy(towhere
, augmented_roomname
);
715 /* If the room exists, check security/access */
717 /* See if there is an existing user/room relationship */
718 CtdlRoomAccess(&QRscratch
, &CC
->user
, &ra
, NULL
);
720 /* normal clients have to pass through security */
726 /* Fail here if no such room */
728 cprintf("%s NO ... no such room, or access denied\r\n",
733 /* If we already had some other folder selected, auto-expunge it */
737 * usergoto() formally takes us to the desired room, happily returning
738 * the number of messages and number of new messages.
740 memcpy(&CC
->room
, &QRscratch
, sizeof(struct ctdlroom
));
741 usergoto(NULL
, 0, 0, &msgs
, &new);
744 if (!strcasecmp(parms
[1], "EXAMINE")) {
751 IMAP
->last_mtime
= CC
->room
.QRmtime
;
753 cprintf("* %d EXISTS\r\n", msgs
);
754 cprintf("* %d RECENT\r\n", new);
756 cprintf("* OK [UIDVALIDITY %ld] UID validity status\r\n", GLOBAL_UIDVALIDITY_VALUE
);
757 cprintf("* OK [UIDNEXT %ld] Predicted next UID\r\n", CitControl
.MMhighest
+ 1);
759 /* Technically, \Deleted is a valid flag, but not a permanent flag,
760 * because we don't maintain its state across sessions. Citadel
761 * automatically expunges mailboxes when they are de-selected.
763 * Unfortunately, omitting \Deleted as a PERMANENTFLAGS flag causes
764 * some clients (particularly Thunderbird) to misbehave -- they simply
765 * electing not to transmit the flag at all. So we have to advertise
766 * \Deleted as a PERMANENTFLAGS flag, even though it technically isn't.
768 cprintf("* FLAGS (\\Deleted \\Seen \\Answered)\r\n");
769 cprintf("* OK [PERMANENTFLAGS (\\Deleted \\Seen \\Answered)] permanent flags\r\n");
771 cprintf("%s OK [%s] %s completed\r\n",
773 (IMAP
->readonly
? "READ-ONLY" : "READ-WRITE"), parms
[1]);
779 * Does the real work for expunge.
781 int imap_do_expunge(void)
784 int num_expunged
= 0;
785 long *delmsgs
= NULL
;
788 CtdlLogPrintf(CTDL_DEBUG
, "imap_do_expunge() called\n");
789 if (IMAP
->selected
== 0) {
793 if (IMAP
->num_msgs
> 0) {
794 delmsgs
= malloc(IMAP
->num_msgs
* sizeof(long));
795 for (i
= 0; i
< IMAP
->num_msgs
; ++i
) {
796 if (IMAP
->flags
[i
] & IMAP_DELETED
) {
797 delmsgs
[num_delmsgs
++] = IMAP
->msgids
[i
];
800 if (num_delmsgs
> 0) {
801 CtdlDeleteMessages(CC
->room
.QRname
, delmsgs
, num_delmsgs
, "");
803 num_expunged
+= num_delmsgs
;
807 if (num_expunged
> 0) {
808 imap_rescan_msgids();
811 CtdlLogPrintf(CTDL_DEBUG
, "Expunged %d messages from <%s>\n",
812 num_expunged
, CC
->room
.QRname
);
813 return (num_expunged
);
818 * implements the EXPUNGE command syntax
820 void imap_expunge(int num_parms
, char *parms
[])
822 int num_expunged
= 0;
824 num_expunged
= imap_do_expunge();
825 cprintf("%s OK expunged %d messages.\r\n", parms
[0], num_expunged
);
830 * implements the CLOSE command
832 void imap_close(int num_parms
, char *parms
[])
835 /* Yes, we always expunge on close. */
836 if (IMAP
->selected
) {
843 cprintf("%s OK CLOSE completed\r\n", parms
[0]);
848 * Implements the NAMESPACE command.
850 void imap_namespace(int num_parms
, char *parms
[])
857 cprintf("* NAMESPACE ");
859 /* All personal folders are subordinate to INBOX. */
860 cprintf("((\"INBOX/\" \"/\")) ");
862 /* Other users' folders ... coming soon! FIXME */
865 /* Show all floors as shared namespaces. Neato! */
867 for (i
= 0; i
< MAXFLOORS
; ++i
) {
869 if (fl
->f_flags
& F_INUSE
) {
870 if (floors
> 0) cprintf(" ");
872 sprintf(buf
, "%s/", fl
->f_name
);
880 /* Wind it up with a newline and a completion message. */
882 cprintf("%s OK NAMESPACE completed\r\n", parms
[0]);
888 * Implements the CREATE command
891 void imap_create(int num_parms
, char *parms
[])
894 char roomname
[ROOMNAMELEN
];
899 char *notification_message
= NULL
;
901 if (strchr(parms
[2], '\\') != NULL
) {
902 cprintf("%s NO Invalid character in folder name\r\n",
904 CtdlLogPrintf(CTDL_DEBUG
, "invalid character in folder name\n");
908 ret
= imap_roomname(roomname
, sizeof roomname
, parms
[2]);
910 cprintf("%s NO Invalid mailbox name or location\r\n",
912 CtdlLogPrintf(CTDL_DEBUG
, "invalid mailbox name or location\n");
915 floornum
= (ret
& 0x00ff); /* lower 8 bits = floor number */
916 flags
= (ret
& 0xff00); /* upper 8 bits = flags */
918 if (flags
& IR_MAILBOX
) {
919 if (strncasecmp(parms
[2], "INBOX/", 6)) {
920 cprintf("%s NO Personal folders must be created under INBOX\r\n", parms
[0]);
921 CtdlLogPrintf(CTDL_DEBUG
, "not subordinate to inbox\n");
926 if (flags
& IR_MAILBOX
) {
927 newroomtype
= 4; /* private mailbox */
928 newroomview
= VIEW_MAILBOX
;
930 newroomtype
= 0; /* public folder */
931 newroomview
= VIEW_BBS
;
934 CtdlLogPrintf(CTDL_INFO
, "Create new room <%s> on floor <%d> with type <%d>\n",
935 roomname
, floornum
, newroomtype
);
937 ret
= create_room(roomname
, newroomtype
, "", floornum
, 1, 0, newroomview
);
939 /*** DO NOT CHANGE THIS ERROR MESSAGE IN ANY WAY! BYNARI CONNECTOR DEPENDS ON IT! ***/
940 cprintf("%s NO Mailbox already exists, or create failed\r\n", parms
[0]);
942 cprintf("%s OK CREATE completed\r\n", parms
[0]);
943 /* post a message in Aide> describing the new room */
944 notification_message
= malloc(1024);
945 snprintf(notification_message
, 1024,
946 "A new room called \"%s\" has been created by %s%s%s%s\n",
949 ((ret
& QR_MAILBOX
) ? " [personal]" : ""),
950 ((ret
& QR_PRIVATE
) ? " [private]" : ""),
951 ((ret
& QR_GUESSNAME
) ? " [hidden]" : "")
953 aide_message(notification_message
, "Room Creation Message");
954 free(notification_message
);
956 CtdlLogPrintf(CTDL_DEBUG
, "imap_create() completed\n");
961 * Locate a room by its IMAP folder name, and check access to it.
962 * If zapped_ok is nonzero, we can also look for the room in the zapped list.
964 int imap_grabroom(char *returned_roomname
, char *foldername
, int zapped_ok
)
967 char augmented_roomname
[ROOMNAMELEN
];
968 char roomname
[ROOMNAMELEN
];
970 struct ctdlroom QRscratch
;
974 ret
= imap_roomname(roomname
, sizeof roomname
, foldername
);
979 /* First try a regular match */
980 c
= getroom(&QRscratch
, roomname
);
982 /* Then try a mailbox name match */
984 MailboxName(augmented_roomname
, sizeof augmented_roomname
,
985 &CC
->user
, roomname
);
986 c
= getroom(&QRscratch
, augmented_roomname
);
988 strcpy(roomname
, augmented_roomname
);
991 /* If the room exists, check security/access */
993 /* See if there is an existing user/room relationship */
994 CtdlRoomAccess(&QRscratch
, &CC
->user
, &ra
, NULL
);
996 /* normal clients have to pass through security */
1000 if ((zapped_ok
) && (ra
& UA_ZAPPED
)) {
1005 /* Fail here if no such room */
1007 strcpy(returned_roomname
, "");
1010 strcpy(returned_roomname
, QRscratch
.QRname
);
1017 * Implements the STATUS command (sort of)
1020 void imap_status(int num_parms
, char *parms
[])
1023 char roomname
[ROOMNAMELEN
];
1025 char savedroom
[ROOMNAMELEN
];
1028 ret
= imap_grabroom(roomname
, parms
[2], 0);
1031 ("%s NO Invalid mailbox name or location, or access denied\r\n",
1037 * usergoto() formally takes us to the desired room, happily returning
1038 * the number of messages and number of new messages. (If another
1039 * folder is selected, save its name so we can return there!!!!!)
1041 if (IMAP
->selected
) {
1042 strcpy(savedroom
, CC
->room
.QRname
);
1044 usergoto(roomname
, 0, 0, &msgs
, &new);
1047 * Tell the client what it wants to know. In fact, tell it *more* than
1048 * it wants to know. We happily IGnore the supplied status data item
1049 * names and simply spew all possible data items. It's far easier to
1050 * code and probably saves us some processing time too.
1052 imap_mailboxname(buf
, sizeof buf
, &CC
->room
);
1053 cprintf("* STATUS ");
1055 cprintf(" (MESSAGES %d ", msgs
);
1056 cprintf("RECENT %d ", new); /* Initially, new==recent */
1057 cprintf("UIDNEXT %ld ", CitControl
.MMhighest
+ 1);
1058 cprintf("UNSEEN %d)\r\n", new);
1061 * If another folder is selected, go back to that room so we can resume
1062 * our happy day without violent explosions.
1064 if (IMAP
->selected
) {
1065 usergoto(savedroom
, 0, 0, &msgs
, &new);
1069 * Oooh, look, we're done!
1071 cprintf("%s OK STATUS completed\r\n", parms
[0]);
1077 * Implements the SUBSCRIBE command
1080 void imap_subscribe(int num_parms
, char *parms
[])
1083 char roomname
[ROOMNAMELEN
];
1084 char savedroom
[ROOMNAMELEN
];
1087 ret
= imap_grabroom(roomname
, parms
[2], 1);
1090 "%s NO Error %d: invalid mailbox name or location, or access denied\r\n",
1098 * usergoto() formally takes us to the desired room, which has the side
1099 * effect of marking the room as not-zapped ... exactly the effect
1100 * we're looking for.
1102 if (IMAP
->selected
) {
1103 strcpy(savedroom
, CC
->room
.QRname
);
1105 usergoto(roomname
, 0, 0, &msgs
, &new);
1108 * If another folder is selected, go back to that room so we can resume
1109 * our happy day without violent explosions.
1111 if (IMAP
->selected
) {
1112 usergoto(savedroom
, 0, 0, &msgs
, &new);
1115 cprintf("%s OK SUBSCRIBE completed\r\n", parms
[0]);
1120 * Implements the UNSUBSCRIBE command
1123 void imap_unsubscribe(int num_parms
, char *parms
[])
1126 char roomname
[ROOMNAMELEN
];
1127 char savedroom
[ROOMNAMELEN
];
1130 ret
= imap_grabroom(roomname
, parms
[2], 0);
1133 ("%s NO Invalid mailbox name or location, or access denied\r\n",
1139 * usergoto() formally takes us to the desired room.
1141 if (IMAP
->selected
) {
1142 strcpy(savedroom
, CC
->room
.QRname
);
1144 usergoto(roomname
, 0, 0, &msgs
, &new);
1147 * Now make the API call to zap the room
1149 if (CtdlForgetThisRoom() == 0) {
1150 cprintf("%s OK UNSUBSCRIBE completed\r\n", parms
[0]);
1153 ("%s NO You may not unsubscribe from this folder.\r\n",
1158 * If another folder is selected, go back to that room so we can resume
1159 * our happy day without violent explosions.
1161 if (IMAP
->selected
) {
1162 usergoto(savedroom
, 0, 0, &msgs
, &new);
1169 * Implements the DELETE command
1172 void imap_delete(int num_parms
, char *parms
[])
1175 char roomname
[ROOMNAMELEN
];
1176 char savedroom
[ROOMNAMELEN
];
1179 ret
= imap_grabroom(roomname
, parms
[2], 1);
1181 cprintf("%s NO Invalid mailbox name, or access denied\r\n",
1187 * usergoto() formally takes us to the desired room, happily returning
1188 * the number of messages and number of new messages. (If another
1189 * folder is selected, save its name so we can return there!!!!!)
1191 if (IMAP
->selected
) {
1192 strcpy(savedroom
, CC
->room
.QRname
);
1194 usergoto(roomname
, 0, 0, &msgs
, &new);
1197 * Now delete the room.
1199 if (CtdlDoIHavePermissionToDeleteThisRoom(&CC
->room
)) {
1200 schedule_room_for_deletion(&CC
->room
);
1201 cprintf("%s OK DELETE completed\r\n", parms
[0]);
1203 cprintf("%s NO Can't delete this folder.\r\n", parms
[0]);
1207 * If another folder is selected, go back to that room so we can resume
1208 * our happy day without violent explosions.
1210 if (IMAP
->selected
) {
1211 usergoto(savedroom
, 0, 0, &msgs
, &new);
1217 * Back end function for imap_rename()
1219 void imap_rename_backend(struct ctdlroom
*qrbuf
, void *data
)
1221 char foldername
[SIZ
];
1222 char newfoldername
[SIZ
];
1223 char newroomname
[ROOMNAMELEN
];
1225 struct irl
*irlp
= NULL
; /* scratch pointer */
1226 struct irlparms
*irlparms
;
1228 irlparms
= (struct irlparms
*) data
;
1229 imap_mailboxname(foldername
, sizeof foldername
, qrbuf
);
1231 /* Rename subfolders */
1232 if ((!strncasecmp(foldername
, irlparms
->oldname
,
1233 strlen(irlparms
->oldname
))
1234 && (foldername
[strlen(irlparms
->oldname
)] == '/'))) {
1236 sprintf(newfoldername
, "%s/%s",
1238 &foldername
[strlen(irlparms
->oldname
) + 1]
1241 newfloor
= imap_roomname(newroomname
,
1243 newfoldername
) & 0xFF;
1245 irlp
= (struct irl
*) malloc(sizeof(struct irl
));
1246 strcpy(irlp
->irl_newroom
, newroomname
);
1247 strcpy(irlp
->irl_oldroom
, qrbuf
->QRname
);
1248 irlp
->irl_newfloor
= newfloor
;
1249 irlp
->next
= *(irlparms
->irl
);
1250 *(irlparms
->irl
) = irlp
;
1256 * Implements the RENAME command
1259 void imap_rename(int num_parms
, char *parms
[])
1261 char old_room
[ROOMNAMELEN
];
1262 char new_room
[ROOMNAMELEN
];
1266 struct irl
*irl
= NULL
; /* the list */
1267 struct irl
*irlp
= NULL
; /* scratch pointer */
1268 struct irlparms irlparms
;
1271 if (strchr(parms
[3], '\\') != NULL
) {
1272 cprintf("%s NO Invalid character in folder name\r\n",
1277 oldr
= imap_roomname(old_room
, sizeof old_room
, parms
[2]);
1278 newr
= imap_roomname(new_room
, sizeof new_room
, parms
[3]);
1279 new_floor
= (newr
& 0xFF);
1281 r
= CtdlRenameRoom(old_room
, new_room
, new_floor
);
1283 if (r
== crr_room_not_found
) {
1284 cprintf("%s NO Could not locate this folder\r\n",
1288 if (r
== crr_already_exists
) {
1289 cprintf("%s NO '%s' already exists.\r\n", parms
[0], parms
[2]);
1292 if (r
== crr_noneditable
) {
1293 cprintf("%s NO This folder is not editable.\r\n", parms
[0]);
1296 if (r
== crr_invalid_floor
) {
1297 cprintf("%s NO Folder root does not exist.\r\n", parms
[0]);
1300 if (r
== crr_access_denied
) {
1301 cprintf("%s NO You do not have permission to edit this folder.\r\n",
1306 cprintf("%s NO Rename failed - undefined error %d\r\n",
1311 /* If this is the INBOX, then RFC2060 says we have to just move the
1312 * contents. In a Citadel environment it's easier to rename the room
1313 * (already did that) and create a new inbox.
1315 if (!strcasecmp(parms
[2], "INBOX")) {
1316 create_room(MAILROOM
, 4, "", 0, 1, 0, VIEW_MAILBOX
);
1319 /* Otherwise, do the subfolders. Build a list of rooms to rename... */
1321 irlparms
.oldname
= parms
[2];
1322 irlparms
.newname
= parms
[3];
1323 irlparms
.irl
= &irl
;
1324 ForEachRoom(imap_rename_backend
, (void *) &irlparms
);
1326 /* ... and now rename them. */
1327 while (irl
!= NULL
) {
1328 r
= CtdlRenameRoom(irl
->irl_oldroom
,
1332 /* FIXME handle error returns better */
1333 CtdlLogPrintf(CTDL_ERR
, "CtdlRenameRoom() error %d\n", r
);
1341 snprintf(buf
, sizeof buf
, "IMAP folder \"%s\" renamed to \"%s\" by %s\n",
1346 aide_message(buf
, "IMAP folder rename");
1348 cprintf("%s OK RENAME completed\r\n", parms
[0]);
1355 * Main command loop for IMAP sessions.
1357 void imap_command_loop(void)
1362 struct timeval tv1
, tv2
;
1363 suseconds_t total_time
= 0;
1364 int untagged_ok
= 1;
1366 gettimeofday(&tv1
, NULL
);
1367 CC
->lastcmd
= time(NULL
);
1368 memset(cmdbuf
, 0, sizeof cmdbuf
); /* Clear it, just in case */
1370 if (client_getln(cmdbuf
, sizeof cmdbuf
) < 1) {
1371 CtdlLogPrintf(CTDL_ERR
, "Client disconnected: ending session.\r\n");
1376 if (IMAP
->authstate
== imap_as_expecting_password
) {
1377 CtdlLogPrintf(CTDL_INFO
, "IMAP: <password>\n");
1379 else if (IMAP
->authstate
== imap_as_expecting_plainauth
) {
1380 CtdlLogPrintf(CTDL_INFO
, "IMAP: <plain_auth>\n");
1382 else if (bmstrcasestr(cmdbuf
, " LOGIN ")) {
1383 CtdlLogPrintf(CTDL_INFO
, "IMAP: LOGIN...\n");
1386 CtdlLogPrintf(CTDL_INFO
, "IMAP: %s\n", cmdbuf
);
1389 while (strlen(cmdbuf
) < 5)
1390 strcat(cmdbuf
, " ");
1392 /* strip off l/t whitespace and CRLF */
1393 if (cmdbuf
[strlen(cmdbuf
) - 1] == '\n')
1394 cmdbuf
[strlen(cmdbuf
) - 1] = 0;
1395 if (cmdbuf
[strlen(cmdbuf
) - 1] == '\r')
1396 cmdbuf
[strlen(cmdbuf
) - 1] = 0;
1399 /* If we're in the middle of a multi-line command, handle that */
1400 if (IMAP
->authstate
== imap_as_expecting_username
) {
1401 imap_auth_login_user(cmdbuf
);
1404 if (IMAP
->authstate
== imap_as_expecting_plainauth
) {
1405 imap_auth_plain(cmdbuf
);
1408 if (IMAP
->authstate
== imap_as_expecting_password
) {
1409 imap_auth_login_pass(cmdbuf
);
1414 /* Ok, at this point we're in normal command mode.
1415 * If the command just submitted does not contain a literal, we
1416 * might think about delivering some untagged stuff...
1418 if (cmdbuf
[strlen(cmdbuf
)-1] == '}') {
1422 /* Grab the tag, command, and parameters. */
1423 num_parms
= imap_parameterize(parms
, cmdbuf
);
1425 /* RFC3501 says that we cannot output untagged data during these commands */
1426 if (num_parms
>= 2) {
1427 if ( (!strcasecmp(parms
[1], "FETCH"))
1428 || (!strcasecmp(parms
[1], "STORE"))
1429 || (!strcasecmp(parms
[1], "SEARCH"))
1437 /* we can put any additional untagged stuff right here in the future */
1440 * Before processing the command that was just entered... if we happen
1441 * to have a folder selected, we'd like to rescan that folder for new
1442 * messages, and for deletions/changes of existing messages. This
1443 * could probably be optimized better with some deep thought...
1445 if (IMAP
->selected
) {
1446 imap_rescan_msgids();
1450 /* Now for the command set. */
1452 if (num_parms
< 2) {
1453 cprintf("BAD syntax error\r\n");
1456 /* The commands below may be executed in any state */
1458 else if ((!strcasecmp(parms
[1], "NOOP"))
1459 || (!strcasecmp(parms
[1], "CHECK"))) {
1460 cprintf("%s OK No operation\r\n",
1464 else if (!strcasecmp(parms
[1], "ID")) {
1465 imap_id(num_parms
, parms
);
1469 else if (!strcasecmp(parms
[1], "LOGOUT")) {
1470 if (IMAP
->selected
) {
1471 imap_do_expunge(); /* yes, we auto-expunge at logout */
1473 cprintf("* BYE %s logging out\r\n", config
.c_fqdn
);
1474 cprintf("%s OK Citadel IMAP session ended.\r\n",
1480 else if (!strcasecmp(parms
[1], "LOGIN")) {
1481 imap_login(num_parms
, parms
);
1484 else if (!strcasecmp(parms
[1], "AUTHENTICATE")) {
1485 imap_authenticate(num_parms
, parms
);
1488 else if (!strcasecmp(parms
[1], "CAPABILITY")) {
1489 imap_capability(num_parms
, parms
);
1492 else if (!strcasecmp(parms
[1], "STARTTLS")) {
1493 imap_starttls(num_parms
, parms
);
1496 else if (!CC
->logged_in
) {
1497 cprintf("%s BAD Not logged in.\r\n", parms
[0]);
1500 /* The commans below require a logged-in state */
1502 else if (!strcasecmp(parms
[1], "SELECT")) {
1503 imap_select(num_parms
, parms
);
1506 else if (!strcasecmp(parms
[1], "EXAMINE")) {
1507 imap_select(num_parms
, parms
);
1510 else if (!strcasecmp(parms
[1], "LSUB")) {
1511 imap_list(num_parms
, parms
);
1514 else if (!strcasecmp(parms
[1], "LIST")) {
1515 imap_list(num_parms
, parms
);
1518 else if (!strcasecmp(parms
[1], "CREATE")) {
1519 imap_create(num_parms
, parms
);
1522 else if (!strcasecmp(parms
[1], "DELETE")) {
1523 imap_delete(num_parms
, parms
);
1526 else if (!strcasecmp(parms
[1], "RENAME")) {
1527 imap_rename(num_parms
, parms
);
1530 else if (!strcasecmp(parms
[1], "STATUS")) {
1531 imap_status(num_parms
, parms
);
1534 else if (!strcasecmp(parms
[1], "SUBSCRIBE")) {
1535 imap_subscribe(num_parms
, parms
);
1538 else if (!strcasecmp(parms
[1], "UNSUBSCRIBE")) {
1539 imap_unsubscribe(num_parms
, parms
);
1542 else if (!strcasecmp(parms
[1], "APPEND")) {
1543 imap_append(num_parms
, parms
);
1546 else if (!strcasecmp(parms
[1], "NAMESPACE")) {
1547 imap_namespace(num_parms
, parms
);
1550 else if (!strcasecmp(parms
[1], "SETACL")) {
1551 imap_setacl(num_parms
, parms
);
1554 else if (!strcasecmp(parms
[1], "DELETEACL")) {
1555 imap_deleteacl(num_parms
, parms
);
1558 else if (!strcasecmp(parms
[1], "GETACL")) {
1559 imap_getacl(num_parms
, parms
);
1562 else if (!strcasecmp(parms
[1], "LISTRIGHTS")) {
1563 imap_listrights(num_parms
, parms
);
1566 else if (!strcasecmp(parms
[1], "MYRIGHTS")) {
1567 imap_myrights(num_parms
, parms
);
1570 else if (!strcasecmp(parms
[1], "GETMETADATA")) {
1571 imap_getmetadata(num_parms
, parms
);
1574 else if (!strcasecmp(parms
[1], "SETMETADATA")) {
1575 imap_setmetadata(num_parms
, parms
);
1578 else if (IMAP
->selected
== 0) {
1579 cprintf("%s BAD no folder selected\r\n", parms
[0]);
1582 /* The commands below require the SELECT state on a mailbox */
1584 else if (!strcasecmp(parms
[1], "FETCH")) {
1585 imap_fetch(num_parms
, parms
);
1588 else if ((!strcasecmp(parms
[1], "UID"))
1589 && (!strcasecmp(parms
[2], "FETCH"))) {
1590 imap_uidfetch(num_parms
, parms
);
1593 else if (!strcasecmp(parms
[1], "SEARCH")) {
1594 imap_search(num_parms
, parms
);
1597 else if ((!strcasecmp(parms
[1], "UID"))
1598 && (!strcasecmp(parms
[2], "SEARCH"))) {
1599 imap_uidsearch(num_parms
, parms
);
1602 else if (!strcasecmp(parms
[1], "STORE")) {
1603 imap_store(num_parms
, parms
);
1606 else if ((!strcasecmp(parms
[1], "UID"))
1607 && (!strcasecmp(parms
[2], "STORE"))) {
1608 imap_uidstore(num_parms
, parms
);
1611 else if (!strcasecmp(parms
[1], "COPY")) {
1612 imap_copy(num_parms
, parms
);
1615 else if ((!strcasecmp(parms
[1], "UID")) && (!strcasecmp(parms
[2], "COPY"))) {
1616 imap_uidcopy(num_parms
, parms
);
1619 else if (!strcasecmp(parms
[1], "EXPUNGE")) {
1620 imap_expunge(num_parms
, parms
);
1623 else if ((!strcasecmp(parms
[1], "UID")) && (!strcasecmp(parms
[2], "EXPUNGE"))) {
1624 imap_expunge(num_parms
, parms
);
1627 else if (!strcasecmp(parms
[1], "CLOSE")) {
1628 imap_close(num_parms
, parms
);
1631 /* End of commands. If we get here, the command is either invalid
1636 cprintf("%s BAD command unrecognized\r\n", parms
[0]);
1639 /* If the client transmitted a message we can free it now */
1640 imap_free_transmitted_message();
1642 gettimeofday(&tv2
, NULL
);
1643 total_time
= (tv2
.tv_usec
+ (tv2
.tv_sec
* 1000000)) - (tv1
.tv_usec
+ (tv1
.tv_sec
* 1000000));
1644 CtdlLogPrintf(CTDL_INFO
, "IMAP: %s\n", cmdbuf
); // FIXME FIXME FIXME REMOVE THIS NOW
1645 CtdlLogPrintf(CTDL_DEBUG
, "IMAP command completed in %ld.%ld seconds\n",
1646 (total_time
/ 1000000),
1647 (total_time
% 1000000)
1652 const char *CitadelServiceIMAP
="IMAP";
1653 const char *CitadelServiceIMAPS
="IMAPS";
1656 * This function is called to register the IMAP extension with Citadel.
1658 CTDL_MODULE_INIT(imap
)
1662 CtdlRegisterServiceHook(config
.c_imap_port
,
1663 NULL
, imap_greeting
, imap_command_loop
, NULL
, CitadelServiceIMAP
);
1665 CtdlRegisterServiceHook(config
.c_imaps_port
,
1666 NULL
, imaps_greeting
, imap_command_loop
, NULL
, CitadelServiceIMAPS
);
1668 CtdlRegisterSessionHook(imap_cleanup_function
, EVT_STOP
);
1671 /* return our Subversion id for the Log */