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; }
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; }
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; }
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; }
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(); }
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. }
UString Bodypart::text() const { if ( d->hasText ) return d->text; Utf8Codec c; return c.toUnicode( d->data ); }
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; }
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; }
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; }
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(); }
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; }
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(); }
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 ); } }
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; }
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; }