3131#include "nets/net_imap/imap.h"
3232#include "nets/net_imap/imap_client.h"
3333
34+ /* Per the RFC, don't idle for more than 30 minutes at a time */
35+ #define MAX_IDLE_SECS 1800
36+
3437extern unsigned int maxuserproxies ;
3538
3639/* Check client pointers for integrity before/after various operations.
@@ -204,6 +207,11 @@ int imap_poll(struct imap_session *imap, int ms, struct imap_client **clientout)
204207
205208int imap_client_idle_start (struct imap_client * client )
206209{
210+ bbs_debug (5 , "Starting IDLE for client %s\n" , client -> name );
211+ if (bbs_assertion_failed (!client -> idling )) {
212+ bbs_warning ("Client %s already idling\n" , client -> name );
213+ return -1 ;
214+ }
207215 /* Now, IDLE on it so we get updates for that mailbox */
208216 if (SWRITE (client -> client .wfd , "idle IDLE\r\n" ) < 0 ) {
209217 return -1 ;
@@ -220,6 +228,11 @@ int imap_client_idle_start(struct imap_client *client)
220228
221229int imap_client_idle_stop (struct imap_client * client )
222230{
231+ bbs_debug (5 , "Stopping IDLE for client %s\n" , client -> name );
232+ if (bbs_assertion_failed (client -> idling )) {
233+ bbs_warning ("Client %s not currently idling\n" , client -> name );
234+ return -1 ;
235+ }
223236 if (SWRITE (client -> client .wfd , "DONE\r\n" ) < 0 ) {
224237 bbs_error ("Failed to write to idling client '%s', must be dead\n" , client -> name );
225238 return -1 ;
@@ -283,6 +296,8 @@ static struct imap_client *create_client_after_purge(struct imap_session *imap,
283296 * in which case, this will fail. In that case, just let it go. */
284297 if (exists ) {
285298 bbs_warning ("Failed to recreate %s client %s\n" , foreground ? "foreground" : "background" , name );
299+ } else {
300+ bbs_debug (3 , "Client '%s' does not exist any more in configuration\n" , name );
286301 }
287302 return NULL ;
288303 }
@@ -299,24 +314,47 @@ int imap_recreate_client(struct imap_session *imap, struct imap_client *client)
299314 int was_idling = client -> idling ;
300315 int foreground = imap -> client == client ;
301316 char name [256 ];
317+ char mb_name [256 ];
302318
303319 safe_strncpy (name , client -> name , sizeof (name )); /* Store name before destroying the client */
304320
321+ /* If a particular mailbox is selected currently, we need to re-SELECT it after recreation */
322+ if (foreground ) {
323+ const char * remotename = remote_mailbox_name (imap -> client , imap -> folder );
324+ safe_strncpy (mb_name , remotename , sizeof (remotename ));
325+ }
326+
305327 /* Destroy old client before creating it again. */
306328 if (foreground ) {
307329 imap -> client = NULL ;
308330 }
309331 imap_client_unlink (imap , client ); /* returns void, so can't check success */
332+ client = NULL ;
310333
311334 /* Now, create it again. */
312- newclient = create_client_after_purge (imap , name , foreground , was_idling );
313- if (newclient && foreground ) {
335+ newclient = create_client_after_purge (imap , name , foreground , 0 ); /* Don't resume idling just yet, if we need to SELECT. Do so at the end. */
336+ if (!newclient ) {
337+ bbs_warning ("Failed to recreate client %s\n" , name );
338+ return -1 ;
339+ }
340+ if (foreground ) {
341+ /* Reselect the mailbox that was created.
342+ * Since the client is being recreated transparently, the actual local client may go ahead
343+ * and perform a mailbox operation (e.g. FETCH), so we need to make sure the server is in the
344+ * same state after the recreation as it was before. */
345+ bbs_debug (5 , "Reselecting previously selected mailbox '%s'\n" , mb_name );
346+ /* Do not pass anything through from the remote server, since this is supposed to completely transparent.
347+ * None of the untagged SELECT responses should pass through, prior to the tagged IDLE response. */
348+ imap_client_send_wait_response_noecho (newclient , -1 , 5000 , "%s \"%s\"\r\n" , "SELECT" , mb_name );
314349 imap -> client = newclient ; /* At this point, the new client has been swapped in, and the rest of the module is none the wiser. */
315350 }
316- return newclient ? 0 : -1 ;
351+ if (was_idling ) {
352+ imap_client_idle_start (newclient ); /* Resume idling, if that's what it was doing before */
353+ }
354+ return 0 ;
317355}
318356
319- void imap_clients_renew_idle (struct imap_session * imap )
357+ void imap_clients_renew_idle (struct imap_session * imap , struct imap_client * except )
320358{
321359 struct stringlist deadclients ;
322360 char * clientname ;
@@ -330,7 +368,7 @@ void imap_clients_renew_idle(struct imap_session *imap)
330368 RWLIST_WRLOCK (& imap -> clients );
331369 RWLIST_TRAVERSE_SAFE_BEGIN (& imap -> clients , client , entry ) {
332370 time_t maxage ;
333- if (!client -> idling || client -> dead ) {
371+ if (!client -> idling || client -> dead || client == except ) {
334372 continue ;
335373 }
336374 /* This is when the connection may be terminated.
@@ -348,7 +386,10 @@ void imap_clients_renew_idle(struct imap_session *imap)
348386 /* The list is locked at the moment,
349387 * and creating a client entails acquiring that lock,
350388 * so for now, just keep track of what clients we removed,
351- * and recreate them afterwards. */
389+ * and recreate them afterwards.
390+ *
391+ * The selected mailbox only matters for the foreground client,
392+ * so we don't need to care about it for these. */
352393 stringlist_push_tail (& deadclients , client -> name );
353394 client_destroy (client );
354395 }
@@ -582,9 +623,8 @@ ssize_t __imap_client_send_log(struct imap_client *client, int log, const char *
582623 return -1 ;
583624 }
584625
585- if (client -> idling ) {
626+ if (bbs_assertion_failed (! client -> idling )) { /* Could stop idle now if this were to happen, but that would just mask a bug */
586627 bbs_warning ("Client is currently idling while attempting to write '%.*s'" , len , buf );
587- bbs_soft_assert (0 ); /* Could stop idle now if this were to happen, but that would just mask a bug */
588628 }
589629
590630 if (log ) {
@@ -600,12 +640,10 @@ int __imap_client_wait_response(struct imap_client *client, int fd, int ms, int
600640 int taglen ;
601641 const char * tag = "tag" ;
602642
603- if (! client -> imap ) {
643+ if (bbs_assertion_failed ( client -> imap != NULL ) ) {
604644 bbs_warning ("No active IMAP client?\n" ); /* Shouldn't happen... */
605- bbs_soft_assert (0 );
606- } else if (strlen_zero (client -> imap -> tag )) {
645+ } else if (bbs_assertion_failed (!strlen_zero (client -> imap -> tag ))) {
607646 bbs_warning ("No active IMAP tag, using generic one\n" );
608- bbs_soft_assert (0 );
609647 } else {
610648 tag = client -> imap -> tag ;
611649 }
@@ -627,12 +665,10 @@ int __imap_client_send_wait_response(struct imap_client *client, int fd, int ms,
627665 va_list ap ;
628666 const char * tag = "tag" ;
629667
630- if (! client -> imap ) {
668+ if (bbs_assertion_failed ( client -> imap != NULL ) ) {
631669 bbs_warning ("No active IMAP client?\n" ); /* Shouldn't happen... */
632- bbs_soft_assert (0 );
633- } else if (strlen_zero (client -> imap -> tag )) {
670+ } else if (bbs_assertion_failed (!strlen_zero (client -> imap -> tag ))) {
634671 bbs_warning ("No active IMAP tag, using generic one\n" );
635- bbs_soft_assert (0 );
636672 } else {
637673 tag = client -> imap -> tag ;
638674 }
@@ -652,17 +688,26 @@ int __imap_client_send_wait_response(struct imap_client *client, int fd, int ms,
652688
653689#if 0
654690 /* Somewhat redundant since there's another debug right after */
655- bbs_debug (6 , "Passing through command %s (line %d) to remotely mapped '%s'\n" , tag , lineno , client -> virtprefix );
691+ bbs_debug (8 , "Passing through command %s (line %d) to remotely mapped '%s'\n" , tag , lineno , client -> virtprefix );
656692#else
657693 UNUSED (lineno );
658694#endif
659695
660- /* Only include this check if it's not the active client,
661- * since a lot of pass through command code uses imap_client_send,
662- * in which case this would be a false positive otherwise. */
663- if (client != client -> imap -> client && client -> idling ) {
664- bbs_warning ("Client is currently idling while attempting to write '%s%s'" , tagbuf , buf );
665- bbs_soft_assert (0 ); /* Could stop idle now if this were to happen, but that would just mask a bug */
696+ if (bbs_assertion_failed (!client -> idling )) {
697+ /* We shouldn't be idling. If we are, it means we're trying to send a command while IDLE,
698+ * which will fail since the only valid input to the server is "DONE".
699+ * For background clients, it's very clear it shouldn't, as it means we failed to call imap_client_idle_stop somewhere.
700+ * Even for foreground clients, it shouldn't happen. Although we do passthrough using imap_client_send to some extent,
701+ * imap_client_idle_start and imap_client_idle_stop are still called on paths where it's the foreground client,
702+ * instead of directly proxying it. The only time this should be true is after imap_poll returns. */
703+ bbs_warning ("%s client %s was idling while attempting to write '%s%s'" ,
704+ client == client -> imap -> client ? "Foreground" : "Background" , client -> virtprefix , tagbuf , buf );
705+ /* Could simply stop idle now if this were to happen, but that would just mask a bug,
706+ * hence the assertion, but afterwards, try to rectify and fix so we can proceed. */
707+ if (imap_client_idle_stop (client )) {
708+ return -1 ;
709+ }
710+ /* Okay, now the client is no longer idling for sure */
666711 }
667712
668713 if (bbs_write (client -> client .wfd , tagbuf , (unsigned int ) taglen ) < 0 ) {
@@ -924,7 +969,7 @@ struct imap_client *__imap_client_get_by_url(struct imap_session *imap, const ch
924969 * to keep these connections alive... */
925970 client -> maxidlesec = 65 ; /* ~1 minute */
926971 } else {
927- client -> maxidlesec = 1800 ; /* 30 minutes */
972+ client -> maxidlesec = MAX_IDLE_SECS ; /* 30 minutes */
928973 }
929974
930975 client -> lastactive = time (NULL ); /* Mark as active since we just successfully did I/O with it */
@@ -1118,7 +1163,7 @@ int mailbox_remotely_mapped(struct imap_session *imap, const char *path)
11181163 return exists ;
11191164}
11201165
1121- char * remote_mailbox_name (struct imap_client * client , char * restrict mailbox )
1166+ const char * remote_mailbox_name (struct imap_client * client , char * restrict mailbox )
11221167{
11231168 char * tmp , * remotename = mailbox + client -> virtprefixlen + 1 ;
11241169 /* This is some other server's problem to handle.
0 commit comments