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; } }
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; }
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; }
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" ); }
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; } } }
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; }
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() ); } }
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(); } }
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(); }
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" ); }
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(); }
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(); }
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; }
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() ); }
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 ); }
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 } }
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; }
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 ); } }
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(); }
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( " " ); }