Beispiel #1
0
bool PopCommand::apop()
{
    class Apop
        : public Plain
    {
    public:
        Apop( EventHandler * ev, const UString & s )
            : Plain( ev ), challenge( s )
        {}

        void verify()
        {
            UString s( challenge );
            s.append( storedSecret() );

            if ( storedSecret().isEmpty() ||
                 MD5::hash( s.utf8()  ).hex() == secret().utf8() )
            {
                setState( Succeeded );
            }
            else {
                setState( Failed );
            }
        }

    private:
        UString challenge;
    };

    if ( !d->m ) {
        log( "APOP Command" );

        Utf8Codec c;
        d->m = new Apop( this, c.toUnicode( d->pop->challenge() ) );
        d->m->setState( SaslMechanism::Authenticating );
        d->m->setLogin( c.toUnicode( nextArg() ) );
        d->m->setSecret( nextArg() );
        d->m->execute();
    }

    if ( !d->m->done() )
        return false;

    if ( d->m->state() == SaslMechanism::Succeeded ) {
        d->pop->setUser( d->m->user(), d->m->name() );
        d->cmd = Session;
        return session();
    }
    else {
        d->pop->err( "Authentication failed" );
    }

    return true;
}
Beispiel #2
0
UString SmtpParser::subDomain()
{
    EString e;
    char c = nextChar();
    if ( ( c >= 'a' && c <= 'z' ) ||
         ( c >= 'A' && c <= 'Z' ) ||
         ( c >= '0' && c <= '9' ) ||
         ( c >= 128 ) ) {
        do {
            e.append( c );
            step();
            c = nextChar();
        } while ( ( ( c >= 'a' && c <= 'z' ) ||
                    ( c >= 'A' && c <= 'Z' ) ||
                    ( c >= '0' && c <= '9' ) ||
                    ( c == '-' || c >= 128 ) ) );
    }
    if ( e.isEmpty() && c == '.' )
        setError( "Consecutive dots aren't permitted" );
    else if ( e.isEmpty() )
        setError( "Domain cannot end with a dot" );
    else if ( e[e.length()-1] == '-' )
        setError( "subdomain cannot end with hyphen (" + e + ")" );
    else if ( e.startsWith( "xn--" ) )
        setError( "subdomain cannot be an a-label (" + e + ")" );
    
    Utf8Codec u;
    UString r = u.toUnicode( e );
    if ( !u.valid() )
        setError( "Subdomain (" + e + ") is not valid UTF8: " + u.error() );
    return r;
}
Beispiel #3
0
UString SmtpParser::atom()
{
    char c = nextChar();
    EString r;
    while ( ( c >= 'a' && c <= 'z' ) ||
            ( c >= 'A' && c <= 'Z' ) ||
            ( c >= '0' && c <= '9' ) ||
            c == '!' || c == '#' ||
            c == '$' || c == '%' ||
            c == '&' || c == '\'' ||
            c == '*' || c == '+' ||
            c == '-' || c == '/' ||
            c == '=' || c == '?' ||
            c == '^' || c == '_' ||
            c == '`' || c == '{' ||
            c == '|' || c == '}' ||
            c == '~' || c >= 128 ) {
        r.append( c );
        step();
        c = nextChar();
    }
    if ( r.isEmpty() )
        setError( "Expected atom, saw: " + following() );
    Utf8Codec uc;
    UString u = uc.toUnicode( r );
    if ( !uc.valid() )
        setError( "Unicode error in atom (" + uc.error() + "): " + r );
    return u;
}
Beispiel #4
0
bool PopCommand::user()
{
    if ( !d->user ) {
        log( "USER Command" );
        if ( !d->pop->accessPermitted() ) {
            d->pop->err( "Must enable TLS before login" );
            return true;
        }
        d->user = new ::User;
        Utf8Codec c;
        d->user->setLogin( c.toUnicode( nextArg() ) );
        d->pop->setUser( d->user, "POP3 login" );
        if ( c.valid() ) {
            d->user->refresh( this );
        }
        else {
            d->pop->err( "Argument encoding error: " + c.error() );
            d->pop->badUser();
            return true;
        }
    }

    if ( d->user->state() == User::Unverified )
        return false;

    if ( d->user->state() == User::Nonexistent ) {
        d->pop->err( "No such user" );
        d->pop->badUser();
    }
    else {
        d->pop->ok( "Done" );
    }

    return true;
}
Beispiel #5
0
void ListAliases::execute()
{
    if ( !q ) {
        Utf8Codec c;
        UString pattern = c.toUnicode( next() );
        end();

        if ( !c.valid() )
            error( "Argument encoding: " + c.error() );

        database();
        EString s( "select (localpart||'@'||domain)::text as address, m.name "
                  "from aliases join addresses a on (address=a.id) "
                  "join mailboxes m on (mailbox=m.id)" );
        if ( !pattern.isEmpty() )
            s.append( " where localpart||'@'||domain like $1 or "
                      "m.name like $1" );
        q = new Query( s, this );
        if ( !pattern.isEmpty() )
            q->bind( 1, sqlPattern( pattern ) );
        q->execute();
    }

    while ( q->hasResults() ) {
        Row * r = q->nextRow();
        printf( "%s: %s\n",
                r->getEString( "address" ).cstr(),
                r->getUString( "name" ).utf8().cstr() );
    }

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

    finish();
}
Beispiel #6
0
UString MimeField::value() const
{
    Utf8Codec c;
    return c.toUnicode( rfc822() );
    // the best that can be said about this is that it corresponds to
    // HeaderField::assemble.
}
Beispiel #7
0
UString Bodypart::text() const
{
    if ( d->hasText )
        return d->text;

    Utf8Codec c;
    return c.toUnicode( d->data );
}
Beispiel #8
0
UString SmtpParser::quotedString()
{
    require( "\"" );
    EString r;
    while ( ok() && !atEnd() && nextChar() != '"' ) {
        if ( nextChar() == '\\' )
            step();
        r.append( nextChar() );
        step();
    }
    require( "\"" );
    Utf8Codec c;
    UString u = c.toUnicode( r );
    if ( !c.valid() )
        setError( "Unicode error in string (" + c.error() + "): " + r );
    return u;
}
Beispiel #9
0
UString SieveParser::quotedString()
{
    EString r;
    require( "\"" );
    while ( ok() && !atEnd() && nextChar() != '"' ) {
        if ( present( "\r\n" ) ) {
            r.append( "\r\n" );
        }
        else {
            if ( nextChar() == '\\' )
                step();
            r.append( nextChar() );
            step();
        }
    }
    require( "\"" );
    Utf8Codec c;
    UString u = c.toUnicode( r );
    if ( !c.valid() )
        setError( "Encoding error: " + c.error() );
    return u;
}
Beispiel #10
0
UString SieveParser::multiLine()
{
    EString r;
    require( "text:" );
    while ( ok() && ( nextChar() == ' ' || nextChar() == '\t' ) )
        step();
    if ( !present( "\r\n" ) )
        hashComment();
    while ( ok() && !atEnd() && !present( ".\r\n" ) ) {
        if ( nextChar() == '.' )
            step();
        while ( ok() && !atEnd() && nextChar() != '\r' ) {
            r.append( nextChar() );
            step();
        }
        require( "\r\n" );
        r.append( "\r\n" );
    }
    Utf8Codec c;
    UString u = c.toUnicode( r );
    if ( !c.valid() )
        setError( "Encoding error: " + c.error() );
    return u;
}
Beispiel #11
0
void CreateAlias::execute()
{
    if ( !d->address ) {
        Utf8Codec c;
        parseOptions();
        d->address = nextAsAddress();
        EString * first = args()->firstElement();
        if ( first && !first->startsWith( "/" ) && first->contains( "@" ) )
            d->destination = nextAsAddress();
        else
            d->mailboxName = c.toUnicode( next() );
        end();

        if ( !c.valid() )
            error( "Argument encoding: " + c.error() );

        database( true );
        if ( !d->mailboxName.isEmpty() )
            Mailbox::setup( this );
    }

    if ( !choresDone() )
        return;

    if ( !d->t ) {
        if ( !d->mailboxName.isEmpty() ) {
            d->mailbox = Mailbox::obtain( d->mailboxName, false );
            if ( !d->mailbox || d->mailbox->deleted() )
                error( "No mailbox named " + d->mailboxName.utf8() );
        }

        d->t = new Transaction( this );
        List< Address > l;
        l.append( d->address );
        if ( d->destination )
            l.append( d->destination );
        AddressCreator * ac = new AddressCreator( &l, d->t );
        ac->execute();
    }

    if ( !d->address->id() || ( d->destination && !d->destination->id() ) )
        return;

    if ( !d->q ) {
        if ( d->destination ) {
            d->q = new Query( "insert into aliases (address, mailbox) "
                              "select $1, mailbox from aliases al "
                              "join addresses a on (al.address=a.id) "
                              "where a.localpart=$2"
                              " and a.domain=$3 "
                              "limit 1",
                              this );
            d->q->bind( 1, d->address->id() );
            d->q->bind( 2, d->destination->localpart() );
            d->q->bind( 3, d->destination->domain() );
        }
        else {
            d->q = new Query( "insert into aliases (address, mailbox) "
                              "values ($1, $2)", this );
            d->q->bind( 1, d->address->id() );
            d->q->bind( 2, d->mailbox->id() );
        }
        d->t->enqueue( d->q );
        d->t->execute();
    }

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

    if ( d->q->failed() )
        error( "Couldn't create alias: " + d->q->error() );

    if ( d->q->rows() < 1 )
        error( "Could not locate destination for alias" );
    else if ( d->q->rows() > 1 )
        error( "Internal error: Inserted " + fn( d->q->rows() ) +
               " instead of 1. Not committing." );

    d->t->commit();

    finish();
}
Beispiel #12
0
void ImapUrl::parse( const EString & s )
{
    d->orig = s;
    ImapUrlParser * p = new ImapUrlParser( s );

    // imapurl = "imap://" iserver "/" icommand

    if ( !d->imap ) {
        if ( !p->present( "imap://" ) )
            return;

        // iserver = [ iuserauth "@" ] hostport
        // iuserauth = enc_user [iauth] / [enc_user] iauth

        if ( p->hasIuserauth() ) {
            d->user = new User;
            Utf8Codec c;
            d->user->setLogin( c.toUnicode( p->xchars() ) );
            if ( !c.valid() )
                return;
            if ( p->present( ";AUTH=" ) )
                d->auth = p->xchars();
            else if ( d->user->login().isEmpty() )
                return;
            if ( !p->present( "@" ) )
                return;
        }

        if ( !p->hostport( d->host, &d->port ) )
            return;
    }

    // icommand = enc_mailbox [uidvalidity] iuid [isection]

    if ( !( d->imap && d->imap->session() ) || !p->hasUid() ) {
        if ( !p->present( "/" ) )
            return;

        Utf8Codec c;
        d->mailbox = c.toUnicode( p->xchars( true ) );
        if ( d->mailbox.isEmpty() )
            return;
        if( !c.valid() )
            return;

        if ( p->present( ";uidvalidity=" ) ) {
            d->uidvalidity = p->nzNumber();
            if ( !p->ok() )
                return;
        }
    }

    p->require( "/;uid=" );
    d->uid = p->number();

    if ( p->present( "/;section=" ) )
        d->section = p->xchars( true );

    // RFC 4467 additions:
    // [ ";EXPIRE=" date-time ] ";URLAUTH=" access ":" mechanism ":" urlauth
    // (These clauses apply only to absolute URLs.)

    if ( !d->imap ) {
        if ( p->nextChar() == ';' ) {
            if ( p->present( ";expire=" ) )
                d->expires = p->isoTimestamp();
            p->require( ";urlauth=" );
            if ( p->present( "submit+" ) )
                d->access = "submit+" + p->xchars();
            else if ( p->present( "user+" ) )
                d->access = "user+" + p->xchars();
            else if ( p->present( "authuser" ) )
                d->access = "authuser";
            else if ( p->present( "anonymous" ) )
                d->access = "anonymous";
            else
                return;
            d->rumpEnd = p->pos();
            if ( p->present( ":" ) ) {
                p->require( "internal" );
                p->require( ":" );
                d->urlauth = p->urlauth();
                d->mechanism = "internal";
            }
            else {
                d->isRump = true;
            }
        }
    }

    p->end();
    if ( !p->ok() )
        return;

    d->valid = true;
}
Beispiel #13
0
void CreateView::execute()
{
    if ( d->name.isEmpty() ) {
        parseOptions();
        Utf8Codec c;
        d->name = c.toUnicode( next() );
        d->source = c.toUnicode( next() );
        UString owner( c.toUnicode( next() ) );
        d->selector = parseSelector( args() );

        if ( !c.valid() )
            error( "Argument encoding: " + c.error() );
        if ( d->name.isEmpty() )
            error( "No name supplied for the view." );
        if ( d->source.isEmpty() )
            error( "No source mailbox name supplied." );
        if ( !d->selector )
            error( "Invalid search expression supplied." );

        database( true );
        Mailbox::setup( this );

        if ( !owner.isEmpty() ) {
            d->user = new User;
            d->user->setLogin( owner );
            d->user->refresh( this );
        }
    }

    if ( !choresDone() )
        return;

    if ( !d->t ) {
        if ( d->user ) {
            if ( d->user->state() == User::Unverified )
                return;

            if ( d->user->state() == User::Nonexistent )
                error( "No user named " + d->user->login().utf8() );

            if ( !d->name.startsWith( "/" ) )
                d->name = d->user->home()->name() + "/" + d->name;

            if ( !d->source.startsWith( "/" ) )
                d->source = d->user->home()->name() + "/" + d->source;
        }

        d->ms = Mailbox::obtain( d->source );
        if ( !d->ms || d->ms->deleted() )
            error( "Can't create view on " + d->source.utf8() );

        d->t = new Transaction( this );

        d->mv = Mailbox::obtain( d->name );
        Query * q = d->mv->create( d->t, d->user );
        if ( !q )
            error( "Couldn't create view named " + d->name.utf8() );

        q = new Query( "insert into views "
                       "(view, selector, source, nextmodseq) values "
                       "((select id from mailboxes where name=$1),"
                       "$2, "
                       "((select id from mailboxes where name=$3), "
                       "1::bigint)", this );
        q->bind( 1, d->name );
        q->bind( 2, d->selector->string() );
        q->bind( 3, d->ms->name() );
        d->t->enqueue( q );
        d->t->commit();
    }

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

    if ( d->t->failed() )
        error( "Couldn't create view" );

    finish();
}
Beispiel #14
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 #15
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 #16
0
int main( int argc, char *argv[] )
{
    Scope global;

    EString sender;
    UString mailbox;
    EString recipient;
    EString filename;
    int verbose = 0;
    bool error = false;

    int n = 1;
    while ( n < argc ) {
        if ( argv[n][0] == '-' ) {
            switch ( argv[n][1] ) {
            case 'f':
                if ( argc - n > 1 )
                    sender = argv[++n];
                break;

            case 't':
                if ( argc - n > 1 ) {
                    Utf8Codec c;
                    mailbox = c.toUnicode( argv[++n] );
                    if ( !c.valid() )
                        error = true;
                }
                break;

            case 'v':
                {
                    int i = 1;
                    while ( argv[n][i] == 'v' ) {
                        verbose++;
                        i++;
                    }
                    if ( argv[n][i] != '\0' )
                        error = true;
                }
                break;

            default:
                error = true;
                break;
            }
        }
        else if ( recipient.isEmpty() ) {
            recipient = argv[n];
        }
        else if ( filename.isEmpty() ) {
            filename = argv[n];
        }
        else {
            error = true;
        }
        n++;
    }

    if ( error || recipient.isEmpty() ) {
        fprintf( stderr,
                 "Syntax: deliver [-v] [-f sender] recipient [filename]\n" );
        exit( -1 );
    }

    EString contents;
    if ( filename.isEmpty() ) {
        char s[128];
        while ( fgets( s, 128, stdin ) != 0 )
            contents.append( s );
    }
    else {
        File message( filename );
        if ( !message.valid() ) {
            fprintf( stderr, "Unable to open message file %s\n",
                     filename.cstr() );
            exit( -1 );
        }
        contents = message.contents();
    }

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

    Injectee * message = new Injectee;
    message->parse( contents );
    if ( !message->error().isEmpty() ) {
        fprintf( stderr,
                 "Message parsing failed: %s", message->error().cstr( ) );
        exit( EX_DATAERR );
    }

    if ( verbose > 0 )
        fprintf( stderr, "Sending to <%s>\n", recipient.cstr() );

    EventLoop::setup();
    Database::setup( 1 );
    Log * l = new Log;
    Allocator::addEternal( l, "delivery log" );
    global.setLog( l );
    Allocator::addEternal( new StderrLogger( "deliver", verbose ),
                           "log object" );

    Configuration::report();
    Mailbox::setup();
    Deliverator * d = new Deliverator( message, mailbox, recipient );
    EventLoop::global()->start();
    if ( !d->i || d->i->failed() )
        return EX_UNAVAILABLE;

    if ( verbose )
        fprintf( stderr,
                 "deliver: Stored in %s as UID %d\n",
                 d->mb->name().utf8().cstr(),
                 d->m->uid( d->mb ) );
    return 0;
}
bool ManageSieveCommand::explain()
{
    if ( !::x )
        ::x = new ExplainStuff;

    whitespace();
    while ( d->no.isEmpty() && d->pos < d->arg.length() ) {
        EString name = string();
        whitespace();
        EString value = string();
        whitespace();
        if ( name == "from" || name == "to" ) {
            if ( value.isEmpty() ) {
                if ( name == "from" )
                    ::x->from = 0;
                else
                    ::x->to = 0;
            }
            else {
                AddressParser ap( value );
                ap.assertSingleAddress();
                if ( ap.addresses()->count() != 1 )
                    no( "Need exactly one address for " + name );
                else if ( name == "from" )
                    ::x->from = ap.addresses()->first();
                else
                    ::x->to = ap.addresses()->first();
            }
        }
        else if ( name == "keep" ) {
            if ( value.isEmpty() ) {
                ::x->keep = 0;
            }
            else {
                Utf8Codec u;
                ::x->keep = Mailbox::find( u.toUnicode( value ) );
                if ( !::x->keep )
                    no( "No such mailbox: " + value );
            }
        }
        else if ( name == "script" ) {
            if ( value.isEmpty() ) {
                ::x->script = 0;
            }
            else {
                if ( !::x->script )
                    ::x->script = new SieveScript;
                ::x->script->parse( value );
                if ( ::x->script->isEmpty() )
                    no( "Script cannot be empty" );
                EString e = ::x->script->parseErrors();
                if ( !e.isEmpty() )
                    no( e );
            }
        }
        else if ( name == "message" ) {
            if ( value.isEmpty() ) {
                ::x->message = 0;
            }
            else {
                ::x->message = new Injectee;
                ::x->message->parse( value );
                ::x->message->setRfc822Size( ::x->message->rfc822().length() );
                if ( !::x->message->error().isEmpty() )
                    no( "Message parsing: " + ::x->message->error() );
            }
        }
        else {
            no( "Unknown name: " + name );
        }
    }

    if ( !::x->script )
        no( "No sieve (yet)" );
    if ( !::x->from )
        no( "No sender address (yet)" );
    if ( !::x->to )
        no( "No recipient address (yet)" );
    if ( !::x->keep )
        no( "No keep mailbox (yet)" );

    if ( !d->no.isEmpty() )
        return true;

    Sieve s;
    s.setSender( ::x->from );
    s.addRecipient( ::x->to, ::x->keep, d->sieve->user(), ::x->script );
    s.evaluate();
    uint a = s.actions( ::x->to )->count();
    bool m = false;
    if ( ::x->message && !s.done() ) {
        s.setMessage( ::x->message, new Date );
        s.evaluate();
        m = true;
    }
    if ( ::x->message && !m )
        d->sieve->send( "Script did not need the message" );
    else if ( !s.done() )
        d->sieve->send( "Script did not complete" );

    uint n = 0;
    List<SieveAction>::Iterator sa( s.actions( ::x->to ) );
    while ( sa ) {
        EString r( "Action: " );
        switch ( sa->type() ) {
        case SieveAction::Reject:
            r.append( "reject" );
            break;
        case SieveAction::FileInto:
            r.append( "fileinto " );
            r.append( sa->mailbox()->name().utf8() );
            break;
        case SieveAction::Redirect:
            r.append( "redirect " );
            r.append( sa->recipientAddress()->localpart() );
            r.append( "@" );
            r.append( sa->recipientAddress()->domain() );
            break;
        case SieveAction::MailtoNotification:
            r.append( "mailto notification to " );
            r.append( sa->recipientAddress()->lpdomain() );
            break;
        case SieveAction::Discard:
            r.append( "discard" );
            break;
        case SieveAction::Vacation:
            r.append( "send vacation message to " );
            r.append( sa->recipientAddress()->lpdomain() );
            r.append( " with subject " );
            r.append( sa->message()->header()->subject() );
            break;
        case SieveAction::Error:
            r = "Error: ";
            r.append( sa->errorMessage().simplified() );
            break;
        }
        if ( m && a && n<a )
            r.append( " (before seeing the message text)" );
        d->sieve->send( r );
        ++sa;
        n++;
    }

    return true;
}