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