We need at least one nonwhitespace character in the human-readble text
[aox:aox.git] / imap / imap.cpp
1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "imap.h"
4
5 #include "log.h"
6 #include "list.h"
7 #include "timer.h"
8 #include "query.h"
9 #include "scope.h"
10 #include "buffer.h"
11 #include "estring.h"
12 #include "mailbox.h"
13 #include "selector.h"
14 #include "eventloop.h"
15 #include "transaction.h"
16 #include "imapsession.h"
17 #include "configuration.h"
18 #include "handlers/capability.h"
19 #include "mailboxgroup.h"
20 #include "imapparser.h"
21 #include "database.h"
22 #include "eventmap.h"
23 #include "command.h"
24 #include "cache.h"
25 #include "date.h"
26 #include "user.h"
27
28 #include "time.h"
29
30
31 static bool endsWithLiteral( const EString *, uint *, bool * );
32
33
34 class IMAPData
35     : public Garbage
36 {
37 public:
38     IMAPData()
39         : state( IMAP::NotAuthenticated ), reader( 0 ),
40           prefersAbsoluteMailboxes( false ),
41           runningCommands( false ), runCommandsAgain( false ),
42           readingLiteral( false ),
43           literalSize( 0 ), mailbox( 0 ),
44           bytesArrived( 0 ),
45           eventMap( new EventMap ),
46           lastBadTime( 0 ),
47           nextOkTime( 0 )
48     {
49         uint i = 0;
50         while ( i < IMAP::NumClientCapabilities )
51             clientCapabilities[i++] = false;
52         i = 0;
53         while ( i < IMAP::NumClientBugs )
54             clientBugs[i++] = false;
55         EventFilterSpec * normal = new EventFilterSpec;
56         normal->setNotificationWanted( EventFilterSpec::FlagChange, true );
57         normal->setNotificationWanted( EventFilterSpec::NewMessage, true );
58         normal->setNotificationWanted( EventFilterSpec::Expunge, true );
59         eventMap->add( normal );
60     }
61
62     IMAP::State state;
63
64     Command * reader;
65
66     EString str;
67
68     bool prefersAbsoluteMailboxes;
69     bool runningCommands;
70     bool runCommandsAgain;
71     bool readingLiteral;
72     uint literalSize;
73
74     List<Command> commands;
75     List<ImapResponse> responses;
76
77     Mailbox *mailbox;
78
79     uint bytesArrived;
80
81     bool clientCapabilities[IMAP::NumClientCapabilities];
82     bool clientBugs[IMAP::NumClientBugs];
83
84     List<MailboxGroup> possibleGroups;
85
86     EventMap * eventMap;
87
88     uint lastBadTime;
89
90     class BadBouncer
91         : public EventHandler
92     {
93     public:
94         BadBouncer( IMAP * owner ) : i( owner ) {}
95
96         void execute() { i->unblockCommands(); }
97
98         IMAP * i;
99     };
100
101     class NatDefeater
102         : public EventHandler
103     {
104     public:
105         NatDefeater( IMAP * owner ) : i( owner ) {}
106
107         void execute() { i->defeatNat(); }
108
109         IMAP * i;
110     };
111
112     uint nextOkTime;
113 };
114
115
116 /*! \class IMAP imap.h
117     This class implements the IMAP server as seen by clients.
118
119     This class is responsible for interacting with IMAP clients, and for
120     overseeing the operation of individual command handlers. It looks at
121     client input to decide which Command to defer the real work to, and
122     ensures that the handler is called at the appropriate times.
123
124     Each IMAP object has a state() (RFC 3501 section 3), and may possess
125     other state information, such as the user() logged in or a
126     session(). The Idle state (RFC 2177) is also kept here.
127
128     The IMAP class parses incoming commands as soon as possible and
129     may keep several commands executing at a time, if the client
130     issues that. It depends on Command::group() to decide whether each
131     parsed Command can be executed concurrently with the already
132     running Command objects.
133 */
134
135 /*! This setup function expects to be called from ::main().
136
137     It reads and validates any relevant configuration variables, and
138     logs a disaster if it encounters an error.
139 */
140
141 void IMAP::setup()
142 {
143 }
144
145
146 /*! Creates an IMAP server on file descriptor \a s, and sends an
147     initial OK[CAPABILITY...] response to the client.
148 */
149
150 IMAP::IMAP( int s )
151     : SaslConnection( s, Connection::ImapServer ), d( new IMAPData )
152 {
153     if ( s < 0 )
154         return;
155
156     EString banner = "* OK [CAPABILITY " +
157                     Capability::capabilities( this ) + "] " +
158                     Configuration::hostname() +
159                     " Archiveopteryx IMAP Server";
160     if ( !Configuration::toggle( Configuration::Security ) )
161         banner.append( " (security checking disabled)" );
162     banner.append( "\r\n" );
163     enqueue( banner );
164     setTimeoutAfter( 120 );
165     EventLoop::global()->addConnection( this );
166 }
167
168
169 /*! Handles the incoming event \a e as appropriate for its type. */
170
171 void IMAP::react( Event e )
172 {
173     d->bytesArrived += readBuffer()->size();
174     switch ( e ) {
175     case Read:
176         parse();
177         if ( d->bytesArrived > 32768 && state() == NotAuthenticated ) {
178             log( ">32k received before login" );
179             enqueue( "* BYE overlong login sequence\r\n" );
180             Connection::setState( Closing );
181             if ( d->reader ) {
182                 Scope s( d->reader->log() );
183                 d->reader->read();
184             }
185         }
186         break;
187
188     case Timeout:
189         if ( state() != Logout ) {
190             log( "Idle timeout" );
191             enqueue( "* BYE Tempus fugit\r\n" );
192         }
193         Connection::setState( Closing );
194         if ( d->reader ) {
195             Scope s( d->reader->log() );
196             d->reader->read();
197         }
198         setSession( 0 );
199         break;
200
201     case Connect:
202         break;
203
204     case Error:
205     case Close:
206         if ( session() ) {
207             log( "Unexpected close by client" );
208             setSession( 0 );
209         }
210         if ( !d->commands.isEmpty() ) {
211             List<Command>::Iterator i( d->commands );
212             while ( i ) {
213                 Command * c = i;
214                 ++i;
215                 if ( c->state() == Command::Unparsed ||
216                      c->state() == Command::Blocked ||
217                      c->state() == Command::Executing )
218                     c->error( Command::No,
219                               "Unexpected close by client" );
220             }
221         }
222         break;
223
224     case Shutdown:
225         enqueue( "* BYE server shutdown\r\n" );
226         if ( session() && d->commands.isEmpty() )
227             setSession( 0 );
228         break;
229     }
230
231     runCommands();
232
233     d->bytesArrived -= readBuffer()->size();
234
235     if ( timeout() == 0 ||
236          ( e == Read && state() != NotAuthenticated ) ) {
237         switch ( state() ) {
238         case NotAuthenticated:
239             setTimeoutAfter( 120 );
240             break;
241         case Authenticated:
242         case Selected:
243             if ( idle() )
244                 setTimeoutAfter( 3600 ); // one hour while IDLE
245             else
246                 setTimeoutAfter( 1860 ); // a half-hour without
247             break;
248         case Logout:
249             break;
250         }
251
252     }
253 }
254
255
256 /*! Reads input from the client, and feeds it to the appropriate Command
257     handlers.
258 */
259
260 void IMAP::parse()
261 {
262     Scope s;
263     Buffer * r = readBuffer();
264
265     while ( true ) {
266         // We read a line of client input, possibly including literals,
267         // and create a Command to deal with it.
268         if ( !d->readingLiteral && !d->reader ) {
269             bool plus = false;
270             EString * s;
271             uint n;
272
273             // Do we have a complete line yet?
274             s = r->removeLine();
275             if ( !s )
276                 return;
277
278             d->str.append( *s );
279
280             if ( endsWithLiteral( s, &n, &plus ) ) {
281                 d->str.append( "\r\n" );
282                 d->readingLiteral = true;
283                 d->literalSize = n;
284
285                 if ( !plus )
286                     enqueue( "+ reading literal\r\n" );
287             }
288
289             // Have we finished reading the entire command?
290             if ( !d->readingLiteral ) {
291                 addCommand();
292                 d->str.truncate();
293             }
294         }
295         else if ( d->readingLiteral ) {
296             // Have we finished reading a complete literal?
297             if ( r->size() < d->literalSize )
298                 return;
299
300             d->str.append( r->string( d->literalSize ) );
301             r->remove( d->literalSize );
302             d->readingLiteral = false;
303         }
304         else if ( d->reader ) {
305             // If a Command has reserve()d input, we just feed it.
306             Scope s( d->reader->log() );
307             d->reader->read();
308             if ( d->reader )
309                 return;
310         }
311     }
312 }
313
314
315 /*! This function parses enough of the command line to create a Command,
316     and then uses it to parse the rest of the input.
317 */
318
319 void IMAP::addCommand()
320 {
321     // I love this feature
322     if ( d->str == "quit" )
323         d->str = "arnt logout";
324
325     ImapParser * p = new ImapParser( d->str );
326
327     EString tag = p->tag();
328     if ( !p->ok() ) {
329         enqueue( "* BAD " + p->error() + "\r\n" );
330         recordSyntaxError();
331         log( p->error(), Log::Info );
332         return;
333     }
334
335     p->require( " " );
336
337     EString name = p->command();
338     if ( !p->ok() ) {
339         enqueue( "* BAD " + p->error() + "\r\n" );
340         recordSyntaxError();
341         log( p->error(), Log::Error );
342         return;
343     }
344
345     if ( EventLoop::global()->inShutdown() && name != "logout" ) {
346         uint n = 0;
347         List< Command >::Iterator i( d->commands );
348         while ( i ) {
349             if ( i->state() == Command::Executing )
350                 n++;
351             ++i;
352         }
353
354         if ( !n ) {
355             enqueue( "* BYE Server or process shutdown\r\n" );
356             Connection::setState( Closing );
357         }
358
359         enqueue( tag + " NO May not be started during server shutdown\r\n" );
360         return;
361     }
362
363     Command * cmd = Command::create( this, tag, name, p );
364
365     if ( !cmd ) {
366         if ( Command::create( this, tag, tag, p ) )
367             enqueue( "* OK  Hint: An IMAP command is prefixed by a tag. "
368                      "The command is the\r\n"
369                      "* OK  second word on the line, after the tag. In "
370                      "your command, " + name.quoted() + "\r\n"
371                      "* OK  is the command and " + tag.quoted() +
372                      " is the tag.\r\n" );
373         recordSyntaxError();
374         enqueue( tag + " BAD No such command: " + name + "\r\n" );
375         log( "Unknown command. Line: " + p->firstLine().quoted(),
376              Log::Error );
377         return;
378     }
379
380     d->commands.append( cmd );
381     d->nextOkTime = time( 0 ) + 117;
382
383     Scope x( cmd->log() );
384     if ( name.lower() != "login" && name.lower() != "authenticate" )
385         ::log( "First line: " + p->firstLine(), Log::Debug );
386 }
387
388
389 /*! Returns the current state of this IMAP session, which is one of
390     NotAuthenticated, Authenticated, Selected and Logout.
391 */
392
393 IMAP::State IMAP::state() const
394 {
395     return d->state;
396 }
397
398
399 /*! Sets this IMAP connection to be in state \a s. The initial value
400     is NotAuthenticated.
401 */
402
403 void IMAP::setState( State s )
404 {
405     if ( s == d->state )
406         return;
407     d->state = s;
408     EString name;
409     switch ( s ) {
410     case NotAuthenticated:
411         name = "not authenticated";
412         break;
413     case Authenticated:
414         name = "authenticated";
415         break;
416     case Selected:
417         name = "selected";
418         break;
419     case Logout:
420         name = "logout";
421         break;
422     };
423     log( "Changed to " + name + " state", Log::Debug );
424 }
425
426
427 /*! Returns true if the server has no particular work to do to server
428     the peer(), and false if it's currently working on behalf of peer().
429
430     If there are no commands, a connection is idle(). If the command
431     currently being executed is Idle, the connection is also idle.
432 */
433
434 bool IMAP::idle() const
435 {
436     List<Command>::Iterator i( d->commands );
437     while ( i ) {
438         Command * c = i;
439         ++i;
440         switch ( c->state() ) {
441         case Command::Unparsed:
442             return false;
443             break;
444         case Command::Blocked:
445             return false;
446             break;
447         case Command::Executing:
448             if ( c->name() != "idle" )
449                 return false;
450             break;
451         case Command::Finished:
452             return false;
453             break;
454         case Command::Retired:
455             break;
456         }
457     }
458
459     return true;
460 }
461
462
463 /*! Notifies the IMAP object that \a user was successfully
464     authenticated by way of \a mechanism. This changes the state() of
465     the IMAP object to Authenticated.
466 */
467
468 void IMAP::setUser( User * user, const EString & mechanism )
469 {
470     log( "Authenticated as " + user->login().ascii() + " using " +
471          mechanism, Log::Significant );
472     SaslConnection::setUser( user, mechanism );
473     setState( Authenticated );
474
475     bool possiblyOutlook = true;
476     List< Command >::Iterator i( d->commands );
477     while ( i && possiblyOutlook ) {
478         EString tag = i->tag();
479         ++i;
480         if ( tag.length() != 4 || tag.contains( '.' ) )
481             possiblyOutlook = false;
482     }
483     if ( possiblyOutlook )
484         setClientBug( Nat );
485     setTimeoutAfter( 1860 );
486 }
487
488
489 /*! Reserves input from the connection for \a command.
490
491     When more input is available, Command::read() is
492     called. Command::finish() releases control.
493 */
494
495 void IMAP::reserve( Command * command )
496 {
497     d->reader = command;
498 }
499
500
501 /*! Causes any blocked commands to be executed if possible.
502 */
503
504 void IMAP::unblockCommands()
505 {
506     if ( d->state != NotAuthenticated )
507         while ( d->commands.firstElement() &&
508                 d->commands.firstElement()->state() == Command::Retired )
509             d->commands.shift();
510     if ( d->runningCommands )
511         d->runCommandsAgain = true;
512     else
513         runCommands();
514 }
515
516
517 /*! Calls Command::execute() on all currently operating commands, and
518     if possible calls Command::emitResponses() and retires those which
519     can be retired.
520 */
521
522 void IMAP::runCommands()
523 {
524     d->runningCommands = true;
525     d->runCommandsAgain = true;
526
527     while ( d->runCommandsAgain ) {
528         d->runCommandsAgain = false;
529         log( "IMAP::runCommands, " + fn( d->commands.count() ) + " commands",
530              Log::Debug );
531
532         // run all currently executing commands once
533         uint n = 0;
534         List< Command >::Iterator i( d->commands );
535         while ( i ) {
536             Command * c = i;
537             ++i;
538             Scope s( c->log() );
539             if ( c->state() == Command::Executing ) {
540                 if ( c->ok() )
541                     c->execute();
542                 else
543                     c->finish();
544                 n++;
545             }
546         }
547
548         // emit responses for zero or more finished commands and
549         // retire them.
550         n = 0;
551         i = d->commands.first();
552         while ( i && i->state() == Command::Finished ) {
553             Command * c = i;
554             ++i;
555             if ( d->reader == c )
556                 d->reader = 0;
557             c->emitResponses();
558             n++;
559         }
560
561         // slow down the command rate if the client is sending
562         // errors. specificaly, if we've sent a NO/BAD, then we don't
563         // start any new commands for n seconds, where n is the number
564         // of NO/BADs we've sent, bounded at 16.
565
566         int delayNeeded = (int)syntaxErrors();
567         if ( delayNeeded > 16 )
568             delayNeeded = 16;
569         delayNeeded = (int)d->lastBadTime + delayNeeded - (int)::time(0);
570         if ( delayNeeded < 0 )
571             delayNeeded = 0;
572         if ( user() && !user()->inbox() && delayNeeded < 4 )
573             delayNeeded = 4;
574         if ( delayNeeded > 0 && !d->commands.isEmpty() ) {
575             log( "Delaying next IMAP command for " + fn( delayNeeded ) +
576                  " seconds (because of " + fn( syntaxErrors() ) +
577                  " syntax errors)" );
578             (void)new Timer( new IMAPData::BadBouncer( this ), delayNeeded );
579             d->runningCommands = false;
580             return;
581         }
582
583         // we may be able to start new commands.
584         i = d->commands.first();
585         Command * first = i;
586         if ( first && first->state() != Command::Retired ) {
587             Scope x( first->log() );
588             ++i;
589             if ( first->state() == Command::Unparsed )
590                 first->parse();
591             if ( !first->ok() )
592                 first->setState( Command::Finished );
593             else if ( first->state() == Command::Unparsed ||
594                       first->state() == Command::Blocked )
595                 first->setState( Command::Executing );
596             if ( first->state() != Command::Executing )
597                 first = 0;
598         }
599
600         // if we have a leading command, we can parse and execute
601         // followers in the same group.
602         if ( first && first->group() ) {
603             while ( first && i && first->state() == i->state() ) {
604                 Command * c = i;
605                 Scope x( c->log() );
606                 ++i;
607                 if ( c->state() == Command::Unparsed )
608                     c->parse();
609                 if ( !c->ok() )
610                     c->setState( Command::Finished );
611                 else if ( c->state() == Command::Unparsed ||
612                           c->state() == Command::Blocked )
613                     c->setState( Command::Executing );
614                 if ( c->group() != first->group() &&
615                      c->state() == Command::Executing ) {
616                     first = 0;
617                     c->setState( Command::Blocked );
618                 }
619             }
620         }
621     }
622
623     d->runningCommands = false;
624
625     List< Command >::Iterator i( d->commands );
626     while ( i ) {
627         if ( i->state() == Command::Retired )
628             d->commands.take( i );
629         else
630             ++i;
631     }
632     if ( d->commands.isEmpty() ) {
633         if ( EventLoop::global()->inShutdown() &&
634              Connection::state() == Connected )
635             Connection::setState( Closing );
636         else
637             restartNatDefeater();
638     }
639 }
640
641
642 /*! Executes \a c once, provided it's in the right state, and emits its
643     responses.
644 */
645
646 void IMAP::run( Command * c )
647 {
648     if ( c->state() != Command::Executing )
649         return;
650
651     Scope s( c->log() );
652
653     if ( c->ok() )
654         c->execute();
655     else
656         c->finish();
657 }
658
659
660 /*  This static helper function returns true if \a s ends with an IMAP
661     literal specification. If so, it sets \a *n to the number of bytes
662     in the literal, and \a *plus to true if the number had a trailing
663     '+' (for LITERAL+). Returns false if it couldn't find a literal.
664 */
665
666 static bool endsWithLiteral( const EString *s, uint *n, bool *plus )
667 {
668     if ( !s->endsWith( "}" ) )
669         return false;
670
671     uint i = s->length() - 2;
672     if ( (*s)[i] == '+' ) {
673         *plus = true;
674         i--;
675     }
676
677     uint j = i;
678     while ( i > 0 && (*s)[i] >= '0' && (*s)[i] <= '9' )
679         i--;
680
681     if ( (*s)[i] != '{' )
682         return false;
683
684     bool ok;
685     *n = s->mid( i+1, j-i ).number( &ok );
686
687     return ok;
688 }
689
690
691 /*! Switches to Selected state and operates on the mailbox session \a
692     s. If the object already had a session, ends the previous session.
693 */
694
695 void IMAP::setSession( Session * s )
696 {
697     if ( !s && !session() )
698         return;
699
700     if ( session() ) {
701         (void)new ImapResponse( this, "OK [CLOSED] Ita, missa est" );
702     }
703     Connection::setSession( s );
704     if ( s ) {
705         setState( Selected );
706         log( "Starting session on mailbox " + s->mailbox()->name().ascii() );
707     }
708     else {
709         setState( Authenticated );
710     }
711 }
712
713
714 /*! \class IMAPS imap.h
715
716     The IMAPS class implements the old wrapper trick still commonly
717     used on port 993. As befits a hack, it is a bit of a hack, and
718     depends on the ability to empty its writeBuffer().
719 */
720
721 /*! Constructs an IMAPS server on file descriptor \a s, and starts to
722     negotiate TLS immediately.
723 */
724
725 IMAPS::IMAPS( int s )
726     : IMAP( s )
727 {
728     EString * tmp = writeBuffer()->removeLine();
729     startTls();
730     enqueue( *tmp + "\r\n" );
731 }
732
733
734 /*! Returns true if the client has shown that it supports a given \a
735     capability, and false if this is still unknown.
736 */
737
738 bool IMAP::clientSupports( ClientCapability capability ) const
739 {
740     return d->clientCapabilities[capability];
741 }
742
743
744 /*! Records that the client supports \a capability. The initial value
745     is valse for all capabilities, and there is no way to disable a
746     capability once enabled.
747 */
748
749 void IMAP::setClientSupports( ClientCapability capability )
750 {
751     d->clientCapabilities[capability] = true;
752     if ( capability == QResync )
753         d->clientCapabilities[Condstore] = true;
754 }
755
756
757 /*! Returns true if the server thinks the client may have \a bug, and
758     false otherwise.
759 */
760
761 bool IMAP::clientHasBug( ClientBug bug ) const
762 {
763     return d->clientBugs[bug];
764 }
765
766
767 static const char * clientBugMessages[IMAP::NumClientBugs] = {
768    "Mishandling of unsolicited responses",
769    "NAT"
770 };
771
772 /*! Records that the client is presumed to suffer from \a bug. */
773
774 void IMAP::setClientBug( ClientBug bug )
775 {
776     d->clientBugs[bug] = true;
777     (void)new ImapResponse( this,
778                             EString( "OK Activating workaround for: " ) +
779                                      clientBugMessages[bug] );
780 }
781
782
783 /*! Returns a list of all Command objects currently known by this IMAP
784     server. First received command first. Commands in all states may
785     be in the list, except Retired.
786
787 */
788
789 List<Command> * IMAP::commands() const
790 {
791     while ( d->commands.firstElement() &&
792             d->commands.firstElement()->state() == Command::Retired )
793         d->commands.shift();
794     return &d->commands;
795 }
796
797
798 void IMAP::sendChallenge( const EString &s )
799 {
800     enqueue( "+ "+ s +"\r\n" );
801 }
802
803
804 /*! Records that the IMAP client likes to see its mailbox names in
805     absolute form (ie. /users/kiki/lists/mja instead of lists/mja)
806     if \a b is true, and that it prefers relative names otherwise.
807     The initial value is false.
808 */
809
810 void IMAP::setPrefersAbsoluteMailboxes( bool b )
811 {
812     d->prefersAbsoluteMailboxes = b;
813 }
814
815
816 /*! Returns whatever setPrefersAbsoluteMailboxes() set. */
817
818 bool IMAP::prefersAbsoluteMailboxes() const
819 {
820     return d->prefersAbsoluteMailboxes;
821 }
822
823
824 /*! Records that \a response needs to be sent at the earliest possible
825     date. When is the earliest possible date? Well, it depends on \a
826     response, on the commands active and so on.
827 */
828
829 void IMAP::respond( class ImapResponse * response )
830 {
831     d->responses.append( response );
832 }
833
834
835 /*! Emits those responses which can be emitted at this time. */
836
837 void IMAP::emitResponses()
838 {
839     if ( clientHasBug( NoUnsolicitedResponses ) && commands()->isEmpty() )
840         return;
841
842     // first, see if expunges are permitted
843     bool can = false;
844     bool cannot = false;
845     List<Command>::Iterator c( commands() );
846
847     while ( c && !cannot ) {
848         // expunges are permitted in idle mode
849         if ( c->state() == Command::Executing && c->name() == "idle" )
850             can = true;
851         // we cannot send an expunge while a command is being
852         // executed (not without NOTIFY at least...)
853         else if ( c->state() == Command::Executing )
854             cannot = true;
855         // group 2 contains commands during which we may not send
856         // expunge, group 3 contains all commands that change
857         // flags.
858         else if ( c->group() == 2 || c->group() == 3 )
859             cannot = true;
860         // if there are MSNs in the pipeline we cannot send
861         // expunge. the copy rule is due to RFC 2180 section
862         // 4.4.1/2
863         else if ( c->usesMsn() && c->name() != "copy" )
864             cannot = true;
865         // if another command is finished, we can.
866         else if ( c->state() == Command::Finished && !c->tag().isEmpty() )
867             can = true;
868         ++c;
869     }
870     if ( cannot )
871         can = false;
872
873     bool any = false;
874
875     Buffer * w = writeBuffer();
876     List<ImapResponse>::Iterator r( d->responses );
877     uint n = 0;
878     while ( r ) {
879         if ( !r->meaningful() ) {
880             r->setSent();
881         }
882         else if ( !r->sent() && ( can || !r->changesMsn() ) ) {
883             EString t = r->text();
884             if ( !t.isEmpty() ) {
885                 w->append( "* ", 2 );
886                 w->append( t );
887                 w->append( "\r\n", 2 );
888                 n++;
889             }
890             r->setSent();
891             any = true;
892         }
893         if ( r->sent() )
894             d->responses.take( r );
895         else
896             ++r;
897     }
898
899     if ( !any )
900         return;
901
902     c = commands()->first();
903     while ( c ) {
904         c->checkUntaggedResponses();
905         ++c;
906     }
907 }
908
909
910 /*! Records that \a m is a (possibly) active mailbox group. */
911
912 void IMAP::addMailboxGroup( MailboxGroup * m )
913 {
914     d->possibleGroups.append( m );
915 }
916
917
918 /*! Records that \a m is no longer active. MailboxGroup calls this,
919     noone else needs to.
920 */
921
922 void IMAP::removeMailboxGroup( MailboxGroup * m )
923 {
924     d->possibleGroups.remove( m );
925 }
926
927
928 /*! Returns the MailboxGroup most likely to be the one the client is
929     working on, assuming that the client performs an operation on \a
930     m.
931
932     Returns a null pointer if the client doesn't seem to be working on
933     any easily defined group, or if it is working on one, but
934     MailboxGroup::hits() returns a value less than \a l.
935 */
936
937 MailboxGroup * IMAP::mostLikelyGroup( Mailbox * m, uint l )
938 {
939     List<MailboxGroup>::Iterator i( d->possibleGroups );
940     MailboxGroup * best = 0;
941     uint bestCount = 0;
942     while ( i ) {
943         MailboxGroup * g = i;
944         ++i;
945         if ( g->contains( m ) && g->hits() >= l ) {
946             uint count = g->count();
947             if ( !best || bestCount < count ) {
948                 best = g;
949                 bestCount = count;
950             }
951         }
952     }
953     return best;
954 }
955
956
957 /*! Returns a pointer to the event map currently in force. This is
958     never a null pointer; IMAP sets up a suitable map when it starts.
959 */
960
961 class EventMap * IMAP::eventMap() const
962 {
963     return d->eventMap;
964 }
965
966
967 /*! Records that IMAP should base its notification decisions on \a map
968     henceforth. \a map must not be null.
969
970 */
971
972 void IMAP::setEventMap( class EventMap * map )
973 {
974     if ( map )
975         d->eventMap = map;
976 }
977
978
979 /*! Reimplemented in order to record the time, so we can rate-limit
980     bad IMAP commands in runCommands();
981 */
982
983 void IMAP::recordSyntaxError()
984 {
985     SaslConnection::recordSyntaxError();
986     d->lastBadTime = time( 0 );
987 }
988
989
990 /*! Restarts the timing logic we use to send little OK response in
991     order to defeat too-quick NAT timeouts.
992 */
993
994 void IMAP::restartNatDefeater()
995 {
996     if ( !clientHasBug( Nat ) )
997         return;
998
999     if ( state() == NotAuthenticated || state() == Logout )
1000         return;
1001
1002     uint now = time( 0 );
1003     uint next = now + 4;
1004     // if we've already set up a suitable timer, just quit
1005     if ( d->nextOkTime >= next && d->nextOkTime < now + 6 )
1006         return;
1007     // otherwise, set one up
1008     d->nextOkTime = next;
1009     (void)new Timer( new IMAPData::NatDefeater( this ), 6 );
1010 }
1011
1012
1013 /*! Called regularly to ensure that we send an untagged OK every
1014     minute or so, in order to ensure a steady stream of packets. Some
1015     NAT gateways will kill the connection after as little as two
1016     minutes if no traffic is seen.
1017 */
1018
1019 void IMAP::defeatNat()
1020 {
1021     if ( !idle() )
1022         return;
1023     if ( Connection::state() != Connection::Connected )
1024         return;
1025     if ( state() == NotAuthenticated || state() == Logout )
1026         return;
1027
1028     uint now = time( 0 );
1029     if ( now < d->nextOkTime )
1030         return;
1031
1032     d->nextOkTime = now + 117;
1033     (void)new Timer( new IMAPData::NatDefeater( this ), d->nextOkTime - now );
1034     Date x;
1035     x.setUnixTime( now );
1036     enqueue( "* OK (NAT keepalive: " + x.isoTime() + ")\r\n" );
1037 }