Beispiel #1
0
void SieveProduction::addExtensions( const EStringList * list )
{
    if ( !list || list->isEmpty() )
        return;

    SieveProduction * p = this;
    EStringList already;
    while ( p ) {
        EStringList::Iterator i( p->addedExtensions() );
        while ( i ) {
            already.append( i );
            ++i;
        }
        p = p->parent();
    }

    EStringList::Iterator i( list );
    while ( i ) {
        if ( !already.contains( *i ) ) {
            if ( !d->addedExtensions )
                d->addedExtensions = new EStringList;
            d->addedExtensions->append( i );
        }
        ++i;
    }
}
Beispiel #2
0
EString MimeField::rfc822() const
{
    EString s = baseValue();
    uint lineLength = name().length() + 2 + s.length();

    EStringList words;
    List< MimeFieldData::Parameter >::Iterator it( d->parameters );
    while ( it ) {
        EString s = it->value;
        if ( !s.boring( EString::MIME ) )
            s = s.quoted();
        words.append( it->name + "=" + s );
        ++it;
    }

    while ( !words.isEmpty() ) {
        EStringList::Iterator i( words );
        while ( i && lineLength + 2 + i->length() > 78 )
            ++i;
        if ( i ) {
            s.append( "; " );
            lineLength += 2;
        }
        else {
            i = words;
            s.append( ";\r\n " );
            lineLength = 1;
        }
        s.append( *i ); // XXX need more elaboration for 2231
        lineLength += i->length();
        words.take( i );
    }
    return s;
}
Beispiel #3
0
EStringList *MimeField::parameters() const
{
    EStringList *l = new EStringList;
    List< MimeFieldData::Parameter >::Iterator it( d->parameters );
    while ( it ) {
        l->append( new EString( it->name ) );
        ++it;
    }
    return l;
}
Beispiel #4
0
static EString badFields( Header * h )
{
    EStringList bad;
    List<HeaderField>::Iterator hf( h->fields() );
    while ( hf ) {
        if ( !hf->valid() )
            bad.append( hf->unparsedValue() );
        ++hf;
    }
    return bad.join( "\n" );
}
Beispiel #5
0
void MaildirMailbox::readSubDir( const EString & sub )
{
    DIR * dir = opendir( ( d->path + "/" + sub ).cstr() );
    if ( !dir )
        return;

    Map<EStringList> files;
    IntegerSet times;

    struct dirent * de = readdir( dir );
    while ( de ) {
        if ( de->d_name[0] >= '0' && de->d_name[0] <= '9' ) {
            EString n( de->d_name );
            int i = n.find( '.' );
            bool ok = false;
            if ( i > 0 )
                i = n.mid( 0, i ).number( &ok );
            if ( ok ) {
                EString * f = new EString ( sub );
                f->append( "/" );
                f->append( n );
                EStringList * l = 0;
                l = files.find( i );
                if ( !l ) {
                    l = new EStringList;
                    files.insert( i, l );
                    times.add( i ); // XXX: how very evil
                }
                l->append( f );
            }
            else {
                // no dot in the name... what is it?
            }
        }
        de = readdir( dir );
    }
    closedir( dir );

    while ( !times.isEmpty() ) {
        uint n = times.smallest();
        times.remove( n );
        EStringList * l = files.find( n );
        EStringList::Iterator i( *l );
        while ( i ) {
            d->messages.append( *i );
            ++i;
        }
    }
}
Beispiel #6
0
 EString text() const {
     if ( *limit >= Flag::largestId() &&
          ( !creator || !creator->inserted() ) )
         return "";
     EString x;
     if ( permahack )
         x.append( "OK [PERMANENT" );
     x.append( "FLAGS (" );
     EStringList all = Flag::allFlags();
     if ( creator )
         all.append( *creator->allFlags() );
     all.removeDuplicates( false );
     x.append( all.sorted()->join( " " ) );
     if ( permahack )
         x.append( " \\*" );
     x.append( ")" );
     if ( permahack )
         x.append( "] permanent flags" );
     return x;
 }
Beispiel #7
0
static void headerSummary( Header * h, int n )
{
    EStringList l;

    ContentType * ct = h->contentType();
    if ( ct )
        l.append( ct->type() + "/" + ct->subtype() );

    ContentTransferEncoding * cte = h->contentTransferEncoding();
    if ( cte ) {
        EString s;
        switch ( cte->encoding() ) {
        case EString::QP:
            s = "quoted-printable";
            break;
        case EString::Base64:
            s = "base64";
            break;
        case EString::Uuencode:
            s = "x-uuencode";
            break;
        case EString::Binary:
            s = "7bit";
            break;
        }
        l.append( s );
    }

    HeaderField * cd = h->field( HeaderField::ContentDescription );
    if ( cd )
        l.append( cd->rfc822( false ) );

    if ( !l.isEmpty() ) {
        spaces( n );
        fprintf( stderr, "%s\n", l.join( ";" ).cstr() );
    }
}
Beispiel #8
0
void POP::parse()
{
    Buffer *b = readBuffer();

    while ( b->size() > 0 ) {
        if ( !d->reader ) {
            if ( d->reserved )
                break;

            EString * s = b->removeLine( 255 );

            if ( !s && b->size() < 255 )
                return;

            if ( !s ) {
                log( "Connection closed due to overlong line (" +
                     fn( b->size() ) + " bytes)", Log::Error );
                err( "Line too long. Closing connection." );
                Connection::setState( Closing );
                return;
            }

            bool unknown = false;

            EStringList * args = EStringList::split( ' ', *s );
            EString cmd = args->take( args->first() )->lower();

            if ( d->sawUser && !( cmd == "quit" || cmd == "pass" ) ) {
                d->sawUser = false;
                unknown = true;
            }
            else if ( cmd == "quit" && args->isEmpty() ) {
                newCommand( d->commands, this, PopCommand::Quit );
            }
            else if ( cmd == "capa" && args->isEmpty() ) {
                newCommand( d->commands, this, PopCommand::Capa );
            }
            else if ( d->state == Authorization ) {
                if ( cmd == "stls" ) {
                    if ( hasTls() )
                        err( "Nested STLS" );
                    else
                        newCommand( d->commands, this, PopCommand::Stls );
                }
                else if ( cmd == "auth" ) {
                    newCommand( d->commands, this, PopCommand::Auth, args );
                }
                else if ( cmd == "user" && args->count() == 1 ) {
                    d->sawUser = true;
                    newCommand( d->commands, this, PopCommand::User, args );
                }
                else if ( d->sawUser && cmd == "pass" && args->count() >= 1 ) {
                    d->sawUser = false;
                    newCommand( d->commands, this, PopCommand::Pass, args );
                }
                else if ( cmd == "apop" && args->count() == 2 ) {
                    newCommand( d->commands, this, PopCommand::Apop, args );
                }
                else {
                    unknown = true;
                }
            }
            else if ( d->state == Transaction ) {
                if ( cmd == "stat" && args->isEmpty() ) {
                    newCommand( d->commands, this, PopCommand::Stat );
                }
                else if ( cmd == "list" && args->count() < 2 ) {
                    newCommand( d->commands, this, PopCommand::List, args );
                }
                else if ( cmd == "top" && args->count() == 2 ) {
                    newCommand( d->commands, this, PopCommand::Top, args );
                }
                else if ( cmd == "retr" && args->count() == 1 ) {
                    newCommand( d->commands, this, PopCommand::Retr, args );
                }
                else if ( cmd == "dele" && args->count() == 1 ) {
                    newCommand( d->commands, this, PopCommand::Dele, args );
                }
                else if ( cmd == "noop" && args->isEmpty() ) {
                    newCommand( d->commands, this, PopCommand::Noop );
                }
                else if ( cmd == "rset" && args->isEmpty() ) {
                    newCommand( d->commands, this, PopCommand::Rset );
                }
                else if ( cmd == "uidl" && args->count() < 2 ) {
                    newCommand( d->commands, this, PopCommand::Uidl, args );
                }
                else {
                    unknown = true;
                }
            }
            else {
                unknown = true;
            }

            if ( unknown ) {
                err( "Bad command" );
                recordSyntaxError();
            }
        }
        else {
            d->reader->read();
        }

        runCommands();
    }
}
Beispiel #9
0
void Exporter::execute()
{
    if ( Mailbox::refreshing() ) {
        Database::notifyWhenIdle( this );
        return;
    }

    if ( !d->mailbox && !d->sourceName.isEmpty() ) {
        d->mailbox = Mailbox::find( d->sourceName );
        if ( !d->mailbox ) {
            log( "No such mailbox: " + d->sourceName.utf8(),
                 Log::Disaster );
            return;
        }
    }

    if ( !d->find ) {
        EStringList wanted;
        wanted.append( "message" );
        d->find = d->selector->query( 0, d->mailbox, 0, this,
                                      true, &wanted, false );
        d->find->execute();
    }

    if ( !d->find->done() )
        return;

    if ( !d->fetcher ) {
        d->messages = new List<Message>;
        while ( d->find->hasResults() ) {
            Row * r = d->find->nextRow();
            Message * m = new Message;
            m->setDatabaseId( r->getInt( "message" ) );
            d->messages->append( m );
        }
        d->fetcher = new Fetcher( d->messages, this, 0 );
        d->fetcher->fetch( Fetcher::Addresses );
        d->fetcher->fetch( Fetcher::OtherHeader );
        d->fetcher->fetch( Fetcher::Body );
        d->fetcher->fetch( Fetcher::Trivia );
        d->fetcher->execute();
    }

    while ( !d->messages->isEmpty() ) {
        Message * m = d->messages->firstElement();
        if ( !m->hasAddresses() )
            return;
        if ( !m->hasHeaders() )
            return;
        if ( !m->hasBodies() )
            return;
        if ( !m->hasTrivia() )
            return;
        d->messages->shift();
        EString from = "From ";
        Header * h = m->header();
        List<Address> * rp = 0;
        if ( h ) {
            rp = h->addresses( HeaderField::ReturnPath );
            if ( !rp )
                rp = h->addresses( HeaderField::Sender );
            if ( !rp )
                rp = h->addresses( HeaderField::From );
        }
        if ( rp )
            from.append( rp->firstElement()->lpdomain() );
        else
            from.append( "*****@*****.**" );
        from.append( "  " );
        Date id;
        if ( m->internalDate() )
            id.setUnixTime( m->internalDate() );
        else if ( m->header()->date() )
            id = *m->header()->date();
        // Tue Jul 23 19:39:23 2002
        from.append( weekdays[id.weekday()] );
        from.append( " " );
        from.append( months[id.month()-1] );
        from.append( " " );
        from.appendNumber( id.day() );
        from.append( " " );
        from.appendNumber( id.hour() );
        from.append( ":" );
        if ( id.minute() < 10 )
            from.append( "0" );
        from.appendNumber( id.minute() );
        from.append( ":" );
        if ( id.second() < 10 )
            from.append( "0" );
        from.appendNumber( id.second() );
        from.append( " " );
        from.appendNumber( id.year() );
        from.append( "\r\n" );
        EString rfc822 = m->rfc822( false );
        int r = ::write( 1, from.data(), from.length() ) +
                ::write( 1, rfc822.data(), rfc822.length() );
        // we don't really care whether the write succeeds or not, so
        // just fool the compiler.
        r = r;
    }

    EventLoop::global()->stop();
}
Beispiel #10
0
EString Recipient::dsnParagraph() const
{
    if ( !valid() )
        return "";

    EStringList l;
    EString s;

    // [ original-recipient-field CRLF ]
    if ( originalRecipient() && originalRecipient() != finalRecipient() )
        l.append( "Original-Recipient: rfc822;" +
                  originalRecipient()->lpdomain() );

    // final-recipient-field CRLF
    if ( finalRecipient() )
        l.append( "Final-Recipient: rfc822;" +
                  finalRecipient()->lpdomain() );

    // action-field CRLF
    switch ( action() ) {
    case Unknown:
        l.append( "Action: unknown" );
        break;
    case Failed:
        l.append( "Action: failed" );
        break;
    case Delayed:
        l.append( "Action: delayed" );
        break;
    case Delivered:
        l.append( "Action: delivered" );
        break;
    case Relayed:
        l.append( "Action: relayed" );
        break;
    case Expanded:
        l.append( "Action: expanded" );
        break;
    }

    // status-field CRLF
    if ( !status().isEmpty() )
        l.append( "Status: " + status() );
    // [ remote-mta-field CRLF ]
    if ( !remoteMTA().isEmpty() )
        l.append( "Remote-Mta: dns;" + remoteMTA() );


    // [ diagnostic-code-field CRLF ]
    if ( !diagnosticCode().isEmpty() )
        l.append( "Diagnostic-Code: smtp;" + diagnosticCode() );

    // [ last-attempt-date-field CRLF ]
    if ( lastAttempt() )
        l.append( "Last-Attempt-Date: " + lastAttempt()->rfc822() );

    // [ final-log-id-field CRLF ]
    if ( !finalLogId().isEmpty() )
        l.append( "Final-Log-Id: smtp;" + finalLogId() );

    // we don't set will-retry-until. it only applies to delay dsns,
    // which we don't send.

    return l.join( "\n" );
}
Beispiel #11
0
int main( int argc, char ** argv )
{
    Scope global;
    EventLoop::setup();

    const char * error = 0;
    bool ok = true;
    if ( argc != 5 ) {
        error = "Wrong number of arguments";
        ok = false;
    }

    uint port = 0;
    if ( ok ) {
        port = EString( argv[1] ).number( &ok );
        if ( !ok )
            error = "Could not parse own port number";
    }
    if ( ok ) {
        Listener<RecorderServer> * l4
            = new Listener<RecorderServer>( Endpoint( "0.0.0.0", port ),
                                            "recording relay/4" );
        Allocator::addEternal( l4, "recording listener" );
        Listener<RecorderServer> * l6
            = new Listener<RecorderServer>( Endpoint( "::", port ),
                                            "recording relay/6" );
        Allocator::addEternal( l6, "recording listener" );

        if ( l4->state() != Connection::Listening &&
             l6->state() != Connection::Listening )
            error = "Could not listen for connections";
    }

    if ( ok ) {
        port = EString( argv[3] ).number( &ok );
        if ( !ok )
            error = "Could not parse server's port number";
    }

    if ( ok ) {
        EStringList l = Resolver::resolve( argv[2] );
        if ( l.isEmpty() ) {
            ok = false;
            error = (EString("Cannot resolve ") + argv[2] +
                     ": " + Resolver::errors().join( ", " ) ).cstr();
        }
        else {
            ep = new Endpoint( *l.first(), port );
            Allocator::addEternal( ep, "target server endpoint" );
        }
        if ( ep && !ep->valid() ) {
            ok = false;
            error = "Invalid server address";
        }
    }

    if ( !ok ) {
        fprintf( stderr,
                 "Error: %s\n"
                 "Usage: recorder port address port filebase\n"
                 "       First port: The recorder's own port.\n"
                 "       Address: The IP address of the server to forward to.\n"
                 "       Second port: The server port to forward to.\n"
                 "       Filebase: The filename base (.<blah> is added).\n",
                 error );
        exit( 1 );
    }

    ::base = new EString( argv[4] );
    Allocator::addEternal( ::base, "base of recorded file names" );

    global.setLog( new Log );
    EventLoop::global()->start();
}
Beispiel #12
0
void Status::execute()
{
    if ( state() != Executing )
        return;

    // first part. set up.
    if ( !permitted() )
        return;

    Session * session = imap()->session();
    Mailbox * current = 0;
    if ( session )
        current = session->mailbox();

    // second part. see if anything has happened, and feed the cache if
    // so. make sure we feed the cache at once.
    if ( d->unseenCount || d->recentCount || d->messageCount ) {
        if ( d->unseenCount && !d->unseenCount->done() )
            return;
        if ( d->messageCount && !d->messageCount->done() )
            return;
        if ( d->recentCount && !d->recentCount->done() )
            return;
    }
    if ( !::cache )
        ::cache = new StatusData::StatusCache;

    if ( d->unseenCount ) {
        while ( d->unseenCount->hasResults() ) {
            Row * r = d->unseenCount->nextRow();
            StatusData::CacheItem * ci =
                ::cache->find( r->getInt( "mailbox" ) );
            if ( ci ) {
                ci->hasUnseen = true;
                ci->unseen = r->getInt( "unseen" );
            }
        }
    }
    if ( d->recentCount ) {
        while ( d->recentCount->hasResults() ) {
            Row * r = d->recentCount->nextRow();
            StatusData::CacheItem * ci =
                ::cache->find( r->getInt( "mailbox" ) );
            if ( ci ) {
                ci->hasRecent = true;
                ci->recent = r->getInt( "recent" );
            }
        }
    }
    if ( d->messageCount ) {
        while ( d->messageCount->hasResults() ) {
            Row * r = d->messageCount->nextRow();
            StatusData::CacheItem * ci =
                ::cache->find( r->getInt( "mailbox" ) );
            if ( ci ) {
                ci->hasMessages = true;
                ci->messages = r->getInt( "messages" );
            }
        }
    }

    // third part. are we processing the first command in a STATUS
    // loop? if so, see if we ought to preload the cache.
    if ( mailboxGroup() && d->cacheState < 3 ) {
        IntegerSet mailboxes;
        if ( d->cacheState < 1 ) {
            // cache state 0: decide which messages
            List<Mailbox>::Iterator i( mailboxGroup()->contents() );
            while ( i ) {
                StatusData::CacheItem * ci = ::cache->provide( i );
                bool need = false;
                if ( d->unseen || d->recent || d->messages )
                    need = true;
                if ( ci->hasUnseen || ci->hasRecent || ci->hasMessages )
                    need = false;
                if ( need )
                    mailboxes.add( i->id() );
                ++i;
            }
            if ( mailboxes.count() < 3 )
                d->cacheState = 3;
        }
        if ( d->cacheState == 1 ) {
            // state 1: send queries
            if ( d->unseen ) {
                d->unseenCount
                    = new Query( "select mailbox, count(uid)::int as unseen "
                                 "from mailbox_messages "
                                 "where mailbox=any($1) and not seen "
                                 "group by mailbox", this );
                d->unseenCount->bind( 1, mailboxes );
                d->unseenCount->execute();
            }
            if ( d->recent ) {
                d->recentCount
                    = new Query( "select id as mailbox, "
                                 "uidnext-first_recent as recent "
                                 "from mailboxes where id=any($1)", this );
                d->recentCount->bind( 1, mailboxes );
                d->recentCount->execute();
            }
            if ( d->messages ) {
                d->messageCount
                    = new Query( "select count(*)::int as messages, mailbox "
                                 "from mailbox_messages where mailbox=any($1) "
                                 "group by mailbox", this );
                d->messageCount->bind( 1, mailboxes );
                d->messageCount->execute();
            }
            d->cacheState = 2;
        }
        if ( d->cacheState == 2 ) {
            // state 2: mark the cache as complete.
            IntegerSet mailboxes;
            List<Mailbox>::Iterator i( mailboxGroup()->contents() );
            while ( i ) {
                StatusData::CacheItem * ci = ::cache->find( i->id() );
                if ( ci && d->unseenCount )
                    ci->hasUnseen = true;
                if ( ci && d->recentCount )
                    ci->hasRecent = true;
                if ( ci && d->messageCount )
                    ci->hasMessages = true;
                ++i;
            }
            // and drop the queries
            d->cacheState = 3;
            d->unseenCount = 0;
            d->recentCount = 0;
            d->messageCount = 0;
        }
    }

    // the cache item we'll actually read from
    StatusData::CacheItem * i = ::cache->provide( d->mailbox );

    // fourth part: send individual queries if there's anything we need
    if ( d->unseen && !d->unseenCount && !i->hasUnseen ) {
        d->unseenCount
            = new Query( "select $1::int as mailbox, "
                         "count(uid)::int as unseen "
                         "from mailbox_messages "
                         "where mailbox=$1 and not seen", this );
        d->unseenCount->bind( 1, d->mailbox->id() );
        d->unseenCount->execute();
    }

    if ( !d->recent ) {
        // nothing doing
    }
    else if ( d->mailbox == current ) {
        // we'll pick it up from the session
    }
    else if ( i->hasRecent ) {
        // the cache has it
    }
    else if ( !d->recentCount ) {
        d->recentCount
            = new Query( "select id as mailbox, "
                         "uidnext-first_recent as recent "
                         "from mailboxes where id=$1", this );
        d->recentCount->bind( 1, d->mailbox->id() );
        d->recentCount->execute();
    }

    if ( !d->messages ) {
        // we don't need to collect
    }
    else if ( d->mailbox == current ) {
        // we'll pick it up
    }
    else if ( i->hasMessages ) {
        // the cache has it
    }
    else if ( d->messages && !d->messageCount ) {
        d->messageCount
            = new Query( "select count(*)::int as messages, "
                         "$1::int as mailbox "
                         "from mailbox_messages where mailbox=$1", this );
        d->messageCount->bind( 1, d->mailbox->id() );
        d->messageCount->execute();
    }

    if ( d->unseenCount || d->recentCount || d->messageCount ) {
        if ( d->unseenCount && !d->unseenCount->done() )
            return;
        if ( d->messageCount && !d->messageCount->done() )
            return;
        if ( d->recentCount && !d->recentCount->done() )
            return;
    }

    // fifth part: return the payload.
    EStringList status;

    if ( d->messages && i->hasMessages )
        status.append( "MESSAGES " + fn( i->messages ) );
    else if ( d->messages && d->mailbox == current )
        status.append( "MESSAGES " + fn( session->messages().count() ) );

    if ( d->recent && i->hasRecent )
        status.append( "RECENT " + fn( i->recent ) );
    else if ( d->recent && d->mailbox == current )
        status.append( "RECENT " + fn( session->recent().count() ) );

    if ( d->uidnext )
        status.append( "UIDNEXT " + fn( d->mailbox->uidnext() ) );

    if ( d->uidvalidity )
        status.append( "UIDVALIDITY " + fn( d->mailbox->uidvalidity() ) );

    if ( d->unseen && i->hasUnseen )
        status.append( "UNSEEN " + fn( i->unseen ) );

    if ( d->modseq ) {
        int64 hms = d->mailbox->nextModSeq();
        // don't like this. an empty mailbox will have a STATUS HMS of
        // 1 before it receives its first message, and also 1 after.
        if ( hms > 1 )
            hms--;
        status.append( "HIGHESTMODSEQ " + fn( hms ) );
    }

    respond( "STATUS " + imapQuoted( d->mailbox ) +
             " (" + status.join( " " ) + ")" );
    finish();
}
Beispiel #13
0
int main( int ac, char ** av )
{
    Scope global;
    bool bad = false;

    if ( ac < 1 )
        bad = true;

    Configuration::setup( "archiveopteryx.conf" );

    EventLoop::setup();
    Log * l = new Log;
    Allocator::addEternal( l, "aoxexport log" );
    global.setLog( l );
    LogClient::setup( "aoxexport" );

    Configuration::report();

    int i = 1;
    while( i < ac && *av[i] == '-' ) {
        uint j = 1;
        while ( av[i][j] ) {
            switch( av[i][j] ) {
            case 'v':
                verbosity++;
                break;
            case 'q':
                if ( verbosity )
                    verbosity--;
                break;
            default:
                bad = true;
                break;
            }
            j++;
        }
        i++;
    }

    Utf8Codec c;
    UString source;
    if ( i >= ac )
        bad = true;
    else if ( av[i][0] == '/' )
        source = c.toUnicode( av[i++] );

    Selector * which;
    if ( i < ac ) {
        EStringList args;
        while ( i < ac )
            args.append( new EString( av[i++] ) );
        which = parseSelector( &args );
    }
    else {
        which = new Selector( Selector::NoField, Selector::All, 0 );
    }

    if ( bad ) {
        fprintf( stderr,
                 "Usage: %s [-vq] [mailbox] [search]"
                 "See aoxexport(8) or "
                 "http://aox.org/aoxexport/ for details.\n", av[0] );
        exit( -1 );
    }

    if ( !c.valid() ) {
        fprintf( stderr,
                 "%s: Mailbox name could not be converted from UTF-8: %s\n",
                 av[0],
                 c.error().cstr() );
        exit( -1 );
    }

    Entropy::setup();
    Database::setup();

    Exporter * e = new Exporter( source, which );

    Mailbox::setup( e );

    EventLoop::global()->start();

    return 0;
}
Beispiel #14
0
            void execute()
            {
                if ( !r ) {
                    r = new RetentionSelector( mailbox, this );
                    r->execute();
                }

                if ( !t ) {
                    t = new ::Transaction( this );
                    nms = new Query( "select nextmodseq from mailboxes "
                                     "where id=$1 for update", this );
                    nms->bind( 1, mailbox->id() );
                    t->enqueue( nms );
                    t->execute();
                }

                if ( nms ) {
                    if ( !r->done() )
                        return;

                    if ( !nms->done() )
                        return;

                    ms = nms->nextRow()->getBigint( "nextmodseq" );
                    nms = 0;

                    Selector * s = new Selector;
                    if ( r->retains() ) {
                        Selector * n = new Selector( Selector::Not );
                        s->add( n );
                        n->add( r->retains() );
                    }
                    s->add( new Selector( this->s ) );
                    s->simplify();
                    EStringList wanted;
                    wanted.append( "mailbox" );
                    wanted.append( "uid" );
                    wanted.append( "message" );
                    iq = s->query( 0, mailbox, 0, this, false,
                                   &wanted, false );
                    int i = iq->string().find( " from " );
                    uint msb = s->placeHolder();
                    uint ub = s->placeHolder();
                    uint rb = s->placeHolder();
                    iq->setString(
                        "insert into deleted_messages "
                        "(mailbox,uid,message,modseq,deleted_by,reason) " +
                        iq->string().mid( 0, i ) + ", $" + fn( msb ) +", $" +
                        fn( ub ) + ", $" + fn( rb ) + iq->string().mid( i ) );
                    iq->bind( msb, ms );
                    iq->bind( ub, user->id() );
                    iq->bind( rb,
                             "POP delete " + Scope::current()->log()->id() );
                    t->enqueue( iq );
                    t->execute();
                }

                if ( iq ) {
                    if ( !iq->done() )
                        return;

                    if ( iq->rows() ) {
                        // at least one message was deleted
                        Query * q = new Query( "update mailboxes set "
                                               "nextmodseq=$1 where id=$2",
                                               this );
                        q->bind( 1, ms+1 );
                        q->bind( 2, mailbox->id() );
                        t->enqueue( q );
                        Mailbox::refreshMailboxes( t );
                    }
                    iq = 0;
                    t->commit();
                }

                if ( !t->done() )
                    return;

                if ( t->failed() )
                    log( "Error deleting messages: " + t->error() );
            }
Beispiel #15
0
void SieveScript::parse( const EString & script )
{
    d->source = script;
    SieveParser p( script );
    d->script = p.commands();

    // if we're not yet at the end, treat whatever follows as another
    // command, which will have a nice big error message.
    p.whitespace();
    if ( !p.atEnd() ) {
        SieveCommand * sc = p.command();
        sc->setError( "Junk at end of script" );
        d->script->append( sc );
    }

    // require is only permitted at the start
    List<SieveCommand>::Iterator s( d->script );
    while ( s && s->identifier() == "require" ) {
        s->setRequirePermitted( true );
        ++s;
    }

    // do the semantic bits of parsing
    s = d->script->first();
    EString prev;
    while ( s ) {
        s->setParent( this );
        s->parse( prev );
        prev = s->identifier();
        ++s;
    }

    // check that require lists the right extensions
    EStringList * extensions = p.extensionsNeeded();
    EStringList declared;
    s = d->script->first();
    while ( s && s->identifier() == "require" ) {
        if ( s->error().isEmpty() ) {
            UStringList * r = 0;
            if ( s->arguments() &&
                 s->arguments()->arguments() &&
                 s->arguments()->arguments()->first() )
                r = s->arguments()->arguments()->first()->stringList();
            UStringList::Iterator i( r );
            while ( i ) {
                if ( i->isAscii() )
                    declared.append( i->ascii() );
                ++i;
            }
        }
        ++s;
    }
    declared.append( "comparator-i;octet" );
    declared.append( "comparator-i;ascii-casemap" );
    declared.append( "fileinto" );
    declared.append( "reject" );
    EStringList::Iterator i( extensions );
    EStringList undeclared;
    while ( i ) {
        if ( !declared.contains( *i ) )
            undeclared.append( i->quoted() );
        ++i;
    }
    if ( !undeclared.isEmpty() ) {
        SieveCommand * f = d->script->first();
        if ( f->identifier() == "require" )
            f->setError( "Extensions used but not declared: " +
                         undeclared.join( ", " ) );
        else
            f->setError( "Missing require: require [ " +
                         undeclared.join( ", " ) + " ];" );
    }

    // and find all the errors
    d->errors = p.bad( this );
}
Beispiel #16
0
void SieveCommand::parse( const EString & previous )
{
    if ( identifier().isEmpty() )
        setError( "Command name is empty" );

    bool test = false;
    bool blk = false;

    EString i = identifier();
    if ( i == "if" || i == "elsif" ) {
        test = true;
        blk = true;
        if ( i == "elsif" && previous != "if" && previous != "elsif" )
            setError( "elsif is only permitted after if/elsif" );
    }
    else if ( i == "else" ) {
        blk = true;
        if ( previous != "if" && previous != "elsif" )
            setError( "else is only permitted after if/elsif" );
    }
    else if ( i == "require" ) {
        arguments()->numberRemainingArguments();
        UStringList::Iterator i( arguments()->takeStringList( 1 ) );
        EStringList a;
        EStringList e;
        while ( i ) {
            if ( supportedExtensions()->contains( i->ascii() ) )
                a.append( i->ascii().quoted() );
            else
                e.append( i->ascii().quoted() );
            ++i;
        }
        if ( !e.isEmpty() )
            setError( "Each string must be a supported "
                      "sieve extension. "
                      "These are not: " + e.join( ", " ) );
        if ( !d->require )
            setError( "require is only permitted as the first command." );
        else if ( parent() )
            parent()->addExtensions( &a );
    }
    else if ( i == "stop" ) {
        // nothing needed
    }
    else if ( i == "reject" ) {
        require( "reject" );
        if ( arguments()->arguments()->isEmpty() ) {
            // we accept reject without reason
        }
        else {
            // if there is an argument, it must be a string
            arguments()->numberRemainingArguments();
            (void)arguments()->takeString( 1 );
        }
    }
    else if ( i == "ereject" ) {
        require( "reject" );
        arguments()->numberRemainingArguments();
        (void)arguments()->takeString( 1 );
    }
    else if ( i == "fileinto" ) {
        require( "fileinto" );
        if ( arguments()->findTag( ":copy" ) )
            require( "copy" );
        if ( arguments()->findTag( ":flags" ) ) {
            require( "imap4flags" );
            (void)arguments()->takeTaggedStringList( ":copy" );
        }
        arguments()->numberRemainingArguments();
        UString mailbox = arguments()->takeString( 1 );
        UString p;
        p.append( "/" );
        p.append( mailbox );

        if ( !Mailbox::validName( mailbox ) && !Mailbox::validName( p ) ) {
            setError( "Expected mailbox name, but got: " + mailbox.utf8() );
        }
        else if ( mailbox.startsWith( "INBOX." ) ) {
            // a sieve script which wants to reference a
            // mailbox called INBOX.X must use lower case
            // (inbox.x).
            UString aox =
                UStringList::split( '.', mailbox.mid( 6 ) )->join( "/" );
            setError( mailbox.utf8().quoted() +
                      " is Cyrus syntax. Archiveopteryx uses " +
                      aox.utf8().quoted() );
        }
    }
    else if ( i == "redirect" ) {
        (void)arguments()->findTag( ":copy" );
        arguments()->numberRemainingArguments();
        EString s = arguments()->takeString( 1 ).utf8();
        AddressParser ap( s );
        ap.assertSingleAddress();
        if ( !ap.error().isEmpty() )
            setError( "Expected one normal address (local@domain), but got: "
                      + s );
    }
    else if ( i == "keep" ) {
        // nothing needed
    }
    else if ( i == "discard" ) {
        // nothing needed
    }
    else if ( i == "vacation" ) {
        // vacation [":days" number] [":subject" string]
        //          [":from" string] [":addresses" string-list]
        //          [":mime"] [":handle" string] <reason: string>

        require( "vacation" );

        // :days
        uint days = 7;
        if ( arguments()->findTag( ":days" ) )
            days = arguments()->takeTaggedNumber( ":days" );
        if ( days < 1 || days > 365 )
            arguments()->tagError( ":days", "Number must be 1..365" );

        // :subject
        (void)arguments()->takeTaggedString( ":subject" );
        // anything is acceptable, right?

        // :from
        if ( arguments()->findTag( ":from" ) ) {
            parseAsAddress( arguments()->takeTaggedString( ":from" ),
                            ":from" );
            // XXX we don't enforce its being a local address.
        }

        // :addresses
        if ( arguments()->findTag( ":addresses" ) ) {
            UStringList * addresses
                = arguments()->takeTaggedStringList( ":addresses" );
            UStringList::Iterator i( addresses );
            while ( i ) {
                parseAsAddress( *i, ":addresses" );
                ++i;
            }
        }

        // :mime
        bool mime = false;
        if ( arguments()->findTag( ":mime" ) )
            mime = true;

        // :handle
        (void)arguments()->takeTaggedString( ":handle" );

        // reason
        arguments()->numberRemainingArguments();
        UString reason = arguments()->takeString( 1 );
        if ( mime ) {
            if ( !reason.isAscii() )
                setError( ":mime bodies must be all-ASCII, "
                          "8-bit text is not permitted" ); // so says the RFC
            EString x = reason.utf8();
            uint i = 0;
            Header * h = Message::parseHeader( i, x.length(),
                                               x, Header::Mime );
            Bodypart * bp = Bodypart::parseBodypart( i, x.length(),
                                                     x, h, 0 );
            if ( !h->error().isEmpty() )
                setError( "While parsing MIME header: " + h->error() );
            else if ( !bp->error().isEmpty() )
                setError( "While parsing MIME bodypart: " + bp->error() );

            List<HeaderField>::Iterator f( h->fields() );
            while ( f ) {
                if ( !f->name().startsWith( "Content-" ) )
                    setError( "Header field not permitted: " + f->name() );
                ++f;
            }

            if ( bp->children()->isEmpty() && bp->text().isEmpty() )
                setError( "Vacation reply does not contain any text" );
        }
        else {
            if ( reason.isEmpty() )
                setError( "Empty vacation text does not make sense" );
        }
    }
    else if ( i == "setflag" ||
              i == "addflags" ||
              i == "removeflag" ) {
        arguments()->numberRemainingArguments();
        (void)arguments()->takeStringList( 1 );
    }
    else if ( i == "notify" ) {
        require( "enotify" );
        UString from;
        if ( arguments()->findTag( ":from" ))
            from = arguments()->takeTaggedString( ":from" );

        UString importance;
        importance.append( "2" );
        if ( arguments()->findTag( ":importance" ) )
            importance = arguments()->takeTaggedString( ":from" );
        uint c = importance[0];
        if ( c < '1' || c > '3' )
            arguments()->tagError( ":importance",
                                   "Importance must be 1, 2 or 3" );

        UStringList * options;
        if ( arguments()->findTag( ":options" ) )
            options = arguments()->takeTaggedStringList( ":options" );

        UString message;
        if ( arguments()->findTag( ":message" ) )
            message = arguments()->takeTaggedString( ":message" );

        arguments()->numberRemainingArguments();
        UString method = arguments()->takeString( 1 );

        SieveNotifyMethod * m
            = new SieveNotifyMethod( method,
                                     arguments()->takeArgument( 1 ),
                                     this );

        if ( m->valid() ) {
            if ( arguments()->findTag( ":from" ) )
                m->setFrom( from, arguments()->findTag( ":from" ) );
            if ( arguments()->findTag( ":message" ) )
                m->setMessage( message, arguments()->findTag( ":message" ) );
        }
    }
    else {
        setError( "Command unknown: " + identifier() );
    }

    arguments()->flagUnparsedAsBad();

    if ( test ) {
        // we must have a test
        if ( !arguments() || arguments()->tests()->count() != 1 )
            setError( "Command " + identifier() + " requires one test" );
        if ( arguments() ) {
            List<SieveTest>::Iterator i( arguments()->tests() );
            while ( i ) {
                i->parse();
                if ( blk && block() ) {
                    if ( i->ihaveFailed() )
                        block()->setIhaveFailed();
                    else
                        block()->addExtensions( i->addedExtensions() );
                }
                ++i;
            }
        }
    }
    else {
        // we cannot have a test
        if ( arguments() && arguments()->tests()->isEmpty() ) {
            List<SieveTest>::Iterator i( arguments()->tests() );
            while ( i ) {
                i->setError( "Command " + identifier() +
                             " does not use tests" );
                ++i;
            }
        }
    }

    if ( blk ) {
        // we must have a subsidiary block
        if ( !block() ) {
            setError( "Command " + identifier() +
                      " requires a subsidiary {..} block" );
        }
        else {
            EString prev;
            List<SieveCommand>::Iterator i( block()->commands() );
            while ( i ) {
                i->parse( prev );
                prev = i->identifier();
                ++i;
            }
        }
    }
    else {
        // we cannot have a subsidiary block
        if ( block() )
            block()->setError( "Command " + identifier() +
                               " does not use a subsidiary command block" );
        // in this case we don't even bother syntax-checking the test
        // or block
    }
}
Beispiel #17
0
EStringList * SieveProduction::supportedExtensions()
{
    EStringList * r = new EStringList;
    // sorted by name, please
    r->append( "body" );
    EStringList::Iterator c( Collation::supported() );
    while ( c ) {
        EString name = "comparator-";
        name.append( *c );
        r->append( name );
        ++c;
    }
    r->append( "copy" );
    r->append( "date" );
    r->append( "ereject" );
    r->append( "envelope" );
    r->append( "fileinto" );
    r->append( "ihave" );
    r->append( "imap4flags" );
    r->append( "reject" );
    r->append( "relational" );
    r->append( "subaddress" );
    r->append( "vacation" );
    return r;
}
Beispiel #18
0
void SieveTest::parse()
{
    if ( identifier() == "address" ) {
        findComparator();
        findMatchType();
        findAddressPart();
        arguments()->numberRemainingArguments();
        d->headers = takeHeaderFieldList( 1 );
        d->keys = arguments()->takeStringList( 2 );
    }
    else if ( identifier() == "allof" ||
              identifier() == "anyof" ) {
        if ( !arguments()->arguments()->isEmpty() )
            setError( "Test '" +
                      identifier() +
                      "' does not accept arguments, only a list of tests" );
        bool any = false;
        List<SieveTest>::Iterator i( arguments()->tests() );
        while ( i ) {
            any = true;
            i->parse();
            if ( i->ihaveFailed() )
                setIhaveFailed();
            addExtensions( i->addedExtensions() );
            ++i;
        }
        if ( !any )
            setError( "Need at least one subsidiary test" );
    }
    else if ( identifier() == "envelope" ) {
        require( "envelope" );
        findComparator();
        findMatchType();
        findAddressPart();
        arguments()->numberRemainingArguments();
        d->envelopeParts = arguments()->takeStringList( 1 );
        d->keys = arguments()->takeStringList( 2 );
        UStringList::Iterator i( d->envelopeParts );
        while ( i ) {
            EString s = i->utf8().lower();
            if ( s == "from" || s == "to" ) {
                Utf8Codec c;
                *i = c.toUnicode( s );
            }
            // else if and blah for extensions - extensions are only
            // valid after the right require
            else {
                // better if we could setError on the right item, but it's gone
                setError( "Unsupported envelope part: " + i->utf8() );
            }
            ++i;
        }
    }
    else if ( identifier() == "exists" ) {
        arguments()->numberRemainingArguments();
        d->headers = takeHeaderFieldList( 1 );
    }
    else if ( identifier() == "false" ) {
        // I wish all the tests were this easy
    }
    else if ( identifier() == "header" ) {
        findComparator();
        findMatchType();
        arguments()->numberRemainingArguments();
        d->headers = takeHeaderFieldList( 1 );
        d->keys = arguments()->takeStringList( 2 );
    }
    else if ( identifier() == "date" ||
              identifier() == "currentdate" )
    {
        require( "date" );
        findComparator();
        findMatchType();

        d->zone = arguments()->takeTaggedString( ":zone" );
        if ( d->zone.isEmpty() &&
             arguments()->findTag( ":originalzone" ) )
            d->zone.append( "-0000" );

        arguments()->numberRemainingArguments();

        uint n = 1;

        if ( identifier() == "date" ) {
            d->headers = takeHeaderFieldList( n++ );
            if ( d->headers && d->headers->count() != 1 )
                setError( "Only one date field may be specified" );
        }

        d->datePart = arguments()->takeString( n++ );
        d->keys = arguments()->takeStringList( n );
    }
    else if ( identifier() == "not" ) {
        if ( !arguments()->arguments()->isEmpty() )
            setError( "Test 'not' does not accept arguments, only a test" );
        if ( !arguments()->tests() ||
             arguments()->tests()->count() != 1 )
            setError( "Test 'not' needs exactly one subsidiary test" );
        else
            arguments()->tests()->first()->parse();
    }
    else if ( identifier() == "size" ) {
        arguments()->allowOneTag( ":over", ":under" );
        if ( arguments()->findTag( ":over" ) ) {
            d->sizeOver = true;
            d->sizeLimit = arguments()->takeTaggedNumber( ":over" );
        }
        else if ( arguments()->findTag( ":under" ) ) {
            d->sizeOver = false;
            d->sizeLimit = arguments()->takeTaggedNumber( ":under" );
        }
    }
    else if ( identifier() == "true" ) {
        // much like false.
    }
    else if ( identifier() == "body" ) {
        require( "body" );
        findComparator();
        findMatchType();
        arguments()->allowOneTag( ":raw", ":text", ":content" );
        if ( arguments()->findTag( ":raw" ) ) {
            d->bodyMatchType = Rfc822;
        }
        else if ( arguments()->findTag( ":text" ) ) {
            d->bodyMatchType = Text;
        }
        else if ( arguments()->findTag( ":content" ) ) {
            d->bodyMatchType = SpecifiedTypes;
            d->contentTypes = arguments()->takeTaggedStringList( ":content" );
        }
        arguments()->numberRemainingArguments();
        d->keys = arguments()->takeStringList( 1 );
    }
    else if ( identifier() == "ihave" ) {
        require( "ihave" );
        arguments()->numberRemainingArguments();
        arguments()->takeStringList( 1 );
    }
    else if ( identifier() == "valid_notify_method" ) {
        require( "enotify" );
        arguments()->numberRemainingArguments();
        UStringList * urls = arguments()->takeStringList( 1 );
        if ( !urls || urls->isEmpty() )
            setError( "No URLs" );
    }
    else if ( identifier() == "notify_method_capability" ) {
        require( "enotify" );
        findComparator();
        findMatchType();
        arguments()->numberRemainingArguments();
        (void)new SieveNotifyMethod( arguments()->takeString( 1 ),
                                     arguments()->takeArgument( 1 ),
                                     this );
        (void)arguments()->takeString( 2 ).utf8().lower();
        d->keys = arguments()->takeStringList( 3 );
    }
    else {
        setError( "Unknown test: " + identifier() );
    }

    if ( arguments() )
        arguments()->flagUnparsedAsBad();

    // if the ihave was correctly parsed and names something we don't
    // support, then we have to suppress some errors.
    if ( identifier() == "ihave" && error().isEmpty() ) {
        UStringList::Iterator i( arguments()->takeStringList( 1 ) );
        EStringList x;
        while ( i && supportedExtensions()->contains( i->ascii() ) ) {
            x.append( i->ascii() );
            ++i;
        }

        if ( i )
            setIhaveFailed();
        else
            addExtensions( &x );
    }
}
Beispiel #19
0
void Thread::execute()
{
    if ( state() != Executing )
        return;

    if ( !d->session )
        d->session = session();

    if ( !d->find ) {
        EStringList * want = new EStringList;
        want->append( "uid" );
        want->append( "message" );
        want->append( "m.idate" );
        want->append( "m.thread_root" );
        want->append( "tmid.value as messageid" );
        want->append( "tref.value as references" );
        EString ts;
        if ( d->threadAlg == ThreadData::References ) {
            want->append( "tsubj.value as subject" );
            ts = "left join header_fields tsubj on"
                 " (m.id=tsubj.message and"
                 " tsubj.field=" + fn( HeaderField::Subject ) +
                 " and tsubj.part='') ";
        }

        d->find = d->s->query( imap()->user(),
                               d->session->mailbox(), d->session,
                               this, false, want );
        EString j = d->find->string();

        // we need to get the References and Message-Id fields as well
        const char * x = "left join";
        if ( !j.contains( x ) )
            x = "where";
        j.replace( x,
                   "left join header_fields tref on"
                   " (m.id=tref.message and"
                   " tref.field=" + fn( HeaderField::References ) +
                   " and tref.part='') "
                   "left join header_fields tmid on"
                   " (m.id=tmid.message and"
                   " tmid.field=" + fn( HeaderField::MessageId ) +
                   " and tmid.part='') " + ts + x );

        d->find->setString( j );

        d->find->execute();
        return;
    }

    while ( d->find->hasResults() ) {
        Row * r = d->find->nextRow();
        ThreadData::Node * n = new ThreadData::Node;
        n->uid = r->getInt( "uid" );
        n->idate = r->getInt( "idate" );
        if ( !r->isNull( "thread_root" ) )
            n->threadRoot = r->getInt( "thread_root" );
        if ( !r->isNull( "references" ) )
            n->references = r->getEString( "references" );
        if ( !r->isNull( "messageid" ) )
            n->messageId = r->getEString( "messageid" );
        if ( !r->isNull( "subject" ) )
            n->subject = Message::baseSubject( r->getUString( "subject" ) );

        d->result.append( n );
        if ( !n->messageId.isEmpty() )
            d->nodes.insert( n->messageId, n );
    }

    if ( !d->find->done() )
        return;

    List<ThreadData::Node>::Iterator ri( d->result );
    if ( d->threadAlg == ThreadData::OrderedSubject ) {
        UDict<ThreadData::Node> roots;
        while ( ri ) {
            ThreadData::Node * n = ri;
            ++ri;

            ThreadData::Node * root = roots.find( n->subject );
            if ( root )
                n->parent = root;
            else
                roots.insert( n->subject, n );
        }
    }
    else {
        while ( ri ) {
            ThreadData::Node * n = ri;
            ++ri;

            EStringList l;
            int lt = 0;
            while ( lt >= 0 ) {
                lt = n->references.find( '<', lt );
                if ( lt >= 0 ) {
                    int gt = n->references.find( '>', lt );
                    if ( gt > 0 )
                        l.append( n->references.mid( lt, gt + 1 - lt ) );
                    lt = gt;
                }
            }
            l.append( n->messageId );

            EStringList::Iterator s( l );
            ThreadData::Node * parent = 0;
            while ( s ) {
                if ( !s->isEmpty() ) {
                    ThreadData::Node * n = d->nodes.find( *s );
                    if ( !n ) {
                        n = new ThreadData::Node;
                        n->messageId = *s;
                        n->threadRoot = n->threadRoot;
                        d->nodes.insert( *s, n );
                    }
                    if ( parent && !n->parent && parent->root() != n )
                        n->parent = parent;
                    parent = n;
                }
                ++s;
            };
        }

        // merge big threads where the start has been deleted, or
        // isn't part of the search expression.
        Dict<ThreadData::Node>::Iterator i( d->nodes );
        Map<ThreadData::Node> roots;
        while ( i ) {
            ThreadData::Node * n = i;
            ++i;
            if ( !n->parent ) {
                ThreadData::Node * found = roots.find( n->threadRoot );
                if ( !found )
                    roots.insert( n->threadRoot, n );
                else if ( found && n != found )
                    n->parent = found;
            }
        }

        // if thread=references is used, we need to jump through extra hoops
        if ( d->threadAlg == ThreadData::References ) {
            Dict<ThreadData::Node>::Iterator i( d->nodes );
            UDict<ThreadData::Node> subjects;
            while ( i ) {
                if ( !i->parent ) {
                    ThreadData::Node * potential = subjects.find( i->subject );
                    if ( potential )
                        i->parent = potential;
                    else
                        subjects.insert( i->subject, i );
                }
                ++i;
            }
        }
    }

    // set up child lists and the root list
    Dict<ThreadData::Node>::Iterator i( d->nodes );
    while ( i ) {
        ThreadData::Node * n = i;
        ++i;
        while ( n ) {
            if ( !n->added ) {
                n->added = true;
                if ( n->parent )
                    n->parent->children.append( n );
                else
                    d->roots.append( n );
            }
            n = n->parent;
        }
    }

    // we need to sort root nodes (and children) by idate, so we
    // extend the definition until sorting works: a non-message's
    // idate is the oldest idate of a direct descendant.
    i = Dict<ThreadData::Node>::Iterator( d->nodes );
    while ( i ) {
        ThreadData::Node * n = i;
        ++i;

        uint idate = n->idate;
        while ( n ) {
            if ( n->uid )
                idate = n->idate;
            else if ( !n->idate || n->idate > idate )
                n->idate = idate;
            n = n->parent;
        }
    }

    waitFor( new ThreadResponse( d ) );
    finish();
}
Beispiel #20
0
EString Capability::capabilities( IMAP * i, bool all )
{
    EStringList c;

    c.append( "IMAP4rev1" );

    bool login = true;
    if ( i->state() == IMAP::NotAuthenticated )
        login = false;

    // the remainder of the capabilities are kept sorted by name

    // ugly X-... prefixes are disregarded when sorting by name

    if ( all || ( !login && i->accessPermitted() ) )
        c.append( SaslMechanism::allowedMechanisms( "AUTH=", i->hasTls() ) );

    if ( all || login ) {
        c.append( "ACL" );
        c.append( "ANNOTATE-EXPERIMENT-1" );
        c.append( "BINARY" );
        c.append( "CATENATE" );
        c.append( "CHILDREN" );
    }
    // should we advertise COMPRESS only if not compressed?
    //c.append( "COMPRESS=DEFLATE" );
    if ( all || login )
        c.append( "CONDSTORE" );
    c.append( "ENABLE" );
    if ( all || login ) {
        c.append( "ESEARCH" );
        c.append( "I18NLEVEL=1" );
    }
    c.append( "ID" );
    if ( all || login )
        c.append( "IDLE" );
    if ( all || login )
        c.append( "LIST-EXTENDED" );
    c.append( "LITERAL+" );
    if ( ( all || !login ) &&
         !SaslMechanism::allowed( SaslMechanism::Plain, i->hasTls() ) )
        c.append( "LOGINDISABLED" );
    if ( all || login ) {
        c.append( "MOVE" );
        c.append( "MULTIAPPEND" );
        c.append( "NAMESPACE" );
        //c.append( "NOTIFY" );
    }
    if ( all || login ) {
        if ( Configuration::toggle( Configuration::UseImapQuota ) )
            c.append( "QUOTA" );
        c.append( "QRESYNC" );
        c.append( "RIGHTS=ekntx" );
    }
    if ( all || !login )
        c.append( "SASL-IR" );
    if ( all || login ) {
        c.append( "SORT" );
        c.append( "SORT=DISPLAY" ); // draft-ietf-morg-sortdisplay
    }
    if ( Configuration::toggle( Configuration::UseTls ) && !i->hasTls() )
        c.append( "STARTTLS" );
    if ( all || login ) {
        c.append( "THREAD=ORDEREDSUBJECT" );
        c.append( "THREAD=REFS" );
        c.append( "THREAD=REFERENCES" );
        c.append( "UIDPLUS" );
        c.append( "UNSELECT" );
        c.append( "URLAUTH" );
        c.append( "UTF8=ACCEPT" );
        c.append( "WITHIN" );
        c.append( "X-AOX-GM-1" );
    }

    return c.join( " " );
}