Merge branch 'next' of ssh://down.oryx.com/oryx/aox into next
[aox:aox.git] / sasl / mechanism.cpp
1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "mechanism.h"
4
5 #include "event.h"
6 #include "connection.h"
7 #include "configuration.h"
8 #include "saslconnection.h"
9 #include "estringlist.h"
10 #include "ldaprelay.h"
11 #include "mailbox.h"
12 #include "scope.h"
13 #include "graph.h"
14 #include "query.h"
15 #include "user.h"
16 #include "utf.h"
17
18 // Supported authentication mechanisms, for create().
19 // (Keep these alphabetical.)
20 #include "anonymous.h"
21 #include "cram-md5.h"
22 #include "digest-md5.h"
23 #include "plain.h"
24 #include "sasllogin.h"
25
26
27 class SaslData
28     : public Garbage
29 {
30 public:
31     SaslData()
32         : state( SaslMechanism::IssuingChallenge ),
33           command( 0 ), user( 0 ),
34           l( 0 ), type( SaslMechanism::Plain ),
35           connection( 0 ), ldapRelay( 0 )
36     {}
37
38     SaslMechanism::State state;
39     EventHandler *command;
40
41     User * user;
42     UString login;
43     UString secret;
44     UString storedSecret;
45     Log *l;
46     SaslMechanism::Type type;
47     SaslConnection * connection;
48     LdapRelay * ldapRelay;
49 };
50
51
52 /*! \class SaslMechanism mechanism.h
53     A generic SASL authentication mechanism (RFC 2222)
54
55     This abstract base class represents a SASL authentication mechanism.
56
57     Each mechanism handler is implemented as a state machine, starting
58     in the IssuingChallenge state, entering the AwaitingResponse state
59     after a challenge() has been issued, reading the client's response
60     with readResponse(), entering the Authenticating state in execute(),
61     and entering either the Succeeded or Failed state when verify() is
62     able to make a final decision.
63
64     The caller is expected to retrieve and send the challenge() to the
65     client when the handler is in the IssuingChallenge state; to call
66     the readResponse() function when the client sends a response, and
67     to call execute() to begin verification. The mechanism will call
68     its owner back when it is done().
69
70     If the mechanism supports a SASL initial response, it starts in the
71     AwaitingInitialResponse state, and the caller may choose to either
72     call readResponse() with the initial response, or change into the
73     IssuingChallenge state and proceed as normal.
74
75     SaslMechanism subclasses must implement challenge(), readResponse(),
76     and verify(). The default implementation of challenge() and verify()
77     is suitable for Anonymous and Plain authentication.
78
79     The create() function returns a pointer to a newly-created handler
80     for a named SASL authentication mechanism.
81 */
82
83
84 /*! This static method creates and returns a pointer to a handler for
85     the named \a mechanism on behalf of \a command and \a connection.
86     Returns 0 if the \a mechanism is unsupported or not allowed.
87     Ignores case in comparing the name.
88 */
89
90 SaslMechanism * SaslMechanism::create( const EString & mechanism,
91                                        EventHandler * command,
92                                        SaslConnection * connection )
93 {
94     EString s( mechanism.lower() );
95     SaslMechanism * m = 0;
96
97     if ( !connection->accessPermitted() )
98         return 0;
99
100     if ( s == "anonymous" )
101         m = new ::Anonymous( command );
102     else if ( s == "plain" )
103         m = new ::Plain( command );
104     else if ( s == "login" )
105         m = new ::SaslLogin( command );
106     else if ( s == "cram-md5" )
107         m = new ::CramMD5( command );
108     else if ( s == "digest-md5" )
109         m = new ::DigestMD5( command );
110
111     if ( !m )
112         return 0;
113
114     if ( !allowed( m->type(), connection->hasTls() ) ) {
115         m->log( "SASL mechanism not allowed by policy: " + s, Log::Debug );
116         return 0;
117     }
118
119     Scope x( m->d->l );
120     m->d->connection = connection;
121     return m;
122 }
123
124
125 /*! Constructs an SaslMechanism of \a type in ChallengeNeeded mode on
126     behalf of \a cmd.
127 */
128
129 SaslMechanism::SaslMechanism( EventHandler * cmd, Type type )
130     : d( new SaslData )
131 {
132     d->l = new Log;
133     d->command = cmd;
134     d->type = type;
135 }
136
137
138 /*! \fn SaslMechanism::~SaslMechanism()
139     This virtual destructor exists only to facilitate safe inheritance.
140 */
141
142
143 /*! Returns this SaslMechanism's state, which is one of the following:
144
145     1. IssuingChallenge: Wants the server to issue another challenge().
146     2. AwaitingResponse: Waiting for readResponse() to be called.
147     3. Authenticating: Waiting for execute() to hear from the database.
148     4. Succeeded: The authentication request succeeded.
149     5. Failed: The authentication request failed.
150     6. Terminated: The exchange was terminated by client request.
151
152     The initial value is IssuingChallenge.
153 */
154
155 SaslMechanism::State SaslMechanism::state() const
156 {
157     return d->state;
158 }
159
160
161 /*! Sets this authenticator's state to \a newState. */
162
163 void SaslMechanism::setState( State newState )
164 {
165     if ( d->state == newState )
166         return;
167     d->state = newState;
168     switch ( newState ) {
169     case AwaitingInitialResponse:
170         // no logging necessary
171         break;
172     case IssuingChallenge:
173         log( "Issuing challenge", Log::Debug );
174         break;
175     case AwaitingResponse:
176         log( "Waiting for client response", Log::Debug );
177         break;
178     case Authenticating:
179         log( "Verifying client response", Log::Debug );
180         break;
181     case Succeeded:
182         log( "Authenticated: " + d->login.utf8().quoted(), Log::Debug );
183         break;
184     case Failed:
185         if ( d->connection )
186             d->connection->recordAuthenticationFailure();
187         log( "Authentication failed for: " + d->login.utf8().quoted(),
188              Log::Debug );
189         break;
190     case Terminated:
191         log( "Authentication terminated", Log::Debug );
192         break;
193     }
194 }
195
196
197 /*! This virtual function returns a challenge when the SaslMechanism is
198     in IssuingChallenge mode. The caller must send the challenge to the
199     client, and set the SaslMechanism to the AwaitingResponse state (so
200     that reimplementations of this function don't need to).
201
202     The return value should be a simple string, neither Base64-encoded,
203     nor prefixed with "+". The default implementation is suitable for
204     challenge-less authentication.
205 */
206
207 EString SaslMechanism::challenge()
208 {
209     return "";
210 }
211
212
213 /*! \fn void SaslMechanism::parseResponse( const EString & response )
214
215     This pure virtual function handles a client response. \a response
216     is the decoded representation of the client's response. \a
217     response may contain NULs.
218 */
219
220
221 /*! Reads an initial response from \a r, which may be 0 to indicate that
222     no initial-response was supplied.
223 */
224
225 void SaslMechanism::readInitialResponse( const EString * r )
226 {
227     Scope x( d->l );
228     if ( r ) {
229         if ( state() == AwaitingInitialResponse ) {
230             if ( *r == "=" )
231                 parseResponse( "" );
232             else
233                 parseResponse( r->de64() );
234         }
235         else {
236             setState( Failed );
237         }
238     }
239     else {
240         setState( IssuingChallenge );
241         execute();
242     }
243 }
244
245
246 /*! Reads a response from \a r, which may be 0 to indicate that no
247     response is available.
248 */
249
250 void SaslMechanism::readResponse( const EString * r )
251 {
252     Scope x( d->l );
253     if ( state() == AwaitingResponse ) {
254         if ( !r )
255             return;
256         if ( *r == "*" ) {
257             setState( Terminated );
258             execute();
259         }
260         else {
261             parseResponse( r->de64() );
262         }
263     }
264     else if ( r ) {
265         if ( state() != Failed )
266             log( "SASL negotiation failed due to unexpected response.",
267                  Log::Debug );
268         setState( Failed );
269         execute();
270     }
271 }
272
273
274 /*! This function expects to be called after setLogin() and
275     setSecret(), which are typically called during readResponse(). It
276     obtains the user's data from the database, checks the
277     user-submitted password against the correct one, and enters the
278     Authenticating state. It expects its parent Command to call it
279     each time a Query notification occurs. It remains in the same
280     state until it has enough data to make a decision.
281
282     If the login() name does not exist, this function sets the state to
283     Failed. Otherwise, it calls verify(), which is expected to validate
284     the request and set the state appropriately.
285 */
286
287 void SaslMechanism::execute()
288 {
289     if ( !d->command )
290         return;
291
292     Scope x( d->l );
293
294     if ( state() == IssuingChallenge ) {
295         d->connection->sendChallenge( challenge().e64() );
296         setState( AwaitingResponse );
297     }
298
299     if ( state() == AwaitingResponse )
300         return;
301
302     if ( state() == Authenticating ) {
303         if ( !d->user  ) {
304             d->user = new User;
305             d->user->setLogin( d->login );
306             d->user->refresh( this );
307         }
308
309         if ( Mailbox::refreshing() ) {
310             log( "Cannot complete login until the Mailbox tree is back",
311                  Log::Debug );
312             Database::notifyWhenIdle( this );
313             return;
314         }
315
316         switch ( d->user->state() ) {
317         case User::Unverified:
318             return;
319             break;
320             
321         case User::Nonexistent:
322             setState( Failed );
323             break;
324             
325         case User::Refreshed:
326             setStoredSecret( d->user->secret() );
327             verify();
328             break;
329         }
330
331         tick();
332     }
333
334     if ( done() ) {
335         d->command->execute();
336         d->command = 0;
337     }
338 }
339
340
341 static GraphableCounter * logins = 0;
342 static GraphableCounter * loginFailures = 0;
343 static GraphableCounter * anonLogins = 0;
344
345
346
347 /*! Calls GraphableCounter::tick() on the right object to account for
348     a login failure or success. Does nothing if none of the tickers
349     are appropriate.
350 */
351
352 void SaslMechanism::tick()
353 {
354     if ( d->state != Succeeded && d->state != Failed )
355         return;
356
357     if ( !logins ) {
358         logins = new GraphableCounter( "successful-logins" );
359         loginFailures = new GraphableCounter( "login-failures" );
360         anonLogins = new GraphableCounter( "anonymous-logins" );
361     }
362
363     if ( d->state == Failed )
364         loginFailures->tick();
365     else if ( d->user->login() == "anonymous" &&
366               Configuration::toggle( Configuration::AuthAnonymous ) )
367         anonLogins->tick();
368     else
369         logins->tick();
370 }
371
372
373 /*! This virtual function returns true if the secret() supplied by the
374     client corresponds to the storedSecret() on the server. It expects
375     to be called by execute() after all relevant information has been
376     obtained.
377
378     The default implementation is suitable for Anonymous or Plain text
379     authentication. It returns true if the stored secret is empty, or
380     matches the client-supplied secret, or if the user is trying to
381     log in as anonymous and that's permitted.
382 */
383
384 void SaslMechanism::verify()
385 {
386     if ( d->user && d->user->login() == "anonymous" ) {
387         if ( Configuration::toggle( Configuration::AuthAnonymous ) )
388             setState( Succeeded );
389         else
390             setState( Failed );
391     }
392     else if ( d->ldapRelay ) {
393         switch ( d->ldapRelay->state() ) {
394         case LdapRelay::BindSucceeded:
395             setState( Succeeded );
396             break;
397         case LdapRelay::BindFailed:
398             setState( Failed );
399             break;
400         case LdapRelay::Working:
401             break;
402         }
403     }
404     else if ( d->user && !d->user->ldapdn().isEmpty() ) {
405         d->ldapRelay = new LdapRelay( this );
406     }
407     else if ( storedSecret().isEmpty() || storedSecret() == secret() ) {
408         setState( Succeeded );
409     }
410     else {
411         setState( Failed );
412     }
413 }
414
415
416 /*! Returns true if this SaslMechanism has reached a final decision
417     about the current authentication request.
418 */
419
420 bool SaslMechanism::done() const
421 {
422     return ( d->state == Failed || d->state == Succeeded ||
423              d->state == Terminated );
424 }
425
426
427 /*! Returns the login name supplied by the client, or the empty string
428     if no login has been set with setLogin().
429 */
430
431 UString SaslMechanism::login() const
432 {
433     return d->login;
434 }
435
436
437 /*! This function tells the SaslMechanism that the client supplied the
438     \a name as its authorization identity. This is usually called by
439     readResponse(), and the value is used by execute().
440 */
441
442 void SaslMechanism::setLogin( const UString &name )
443 {
444     d->login = name;
445 }
446
447
448 /*! Like the other setLogin(), except that it converts \a name from
449     UTF-8 to unicode first. If \a name is not valid UTF-8, setLogin()
450     sets the name to an empty string and logs the problem.
451 */
452
453 void SaslMechanism::setLogin( const EString &name )
454 {
455     Utf8Codec u;
456     d->login = u.toUnicode( name );
457     if ( u.valid() )
458         return;
459     d->login.truncate();
460     log( "Client login was not valid UTF-8: " + u.error(), Log::Error );
461 }
462
463
464 /*! Returns the secret supplied by the client, or the empty string if no
465     secret has been set with setSecret().
466 */
467
468 UString SaslMechanism::secret() const
469 {
470     return d->secret;
471 }
472
473
474 /*! This function tells the SaslMechanism that the client supplied the
475     \a secret with its credentials. Usually called by readResponse(),
476     and the value is used by execute().
477 */
478
479 void SaslMechanism::setSecret( const UString &secret )
480 {
481     d->secret = secret;
482 }
483
484
485 /*! Like the other setSecret(), except that it converts \a secret from
486     UTF-8 to unicode first. If \a secret is not valid UTF-8,
487     setSecret() sets the secret to an empty string and logs the
488     problem.
489 */
490
491 void SaslMechanism::setSecret( const EString &secret )
492 {
493     Utf8Codec u;
494     d->secret = u.toUnicode( secret );
495     if ( u.valid() )
496         return;
497     d->secret.truncate();
498     log( "Client secret was not valid UTF-8: " + u.error() );
499 }
500
501
502 /*! Returns the secret stored on the server for the login name supplied
503     by the client. This function expects to be called by verify(), i.e.
504     after execute() has obtained the stored secret from the database.
505 */
506
507 UString SaslMechanism::storedSecret() const
508 {
509     return d->storedSecret;
510 }
511
512
513 /*! This function is only meant to be used while testing SaslMechanism
514     subclasses. It sets the stored secret to \a s, rather than waiting
515     for it to be retrieved from the database by execute().
516 */
517
518 void SaslMechanism::setStoredSecret( const UString &s )
519 {
520     d->storedSecret = s;
521 }
522
523
524
525 /*! \fn void SaslMechanism::setChallenge( const EString & c )
526
527     This function is only meant to be used while testing SaslMechanism
528     subclasses. This implementation does nothing; if a subclass uses a
529     non-default challenge(), it should also reimplement this and use
530     \a c as challenge.
531 */
532
533 void SaslMechanism::setChallenge( const EString & )
534 {
535 }
536
537
538 /*! Logs message \a m with severity \a s.
539 */
540
541 void SaslMechanism::log( const EString &m, Log::Severity s )
542 {
543     d->l->log( m, s );
544 }
545
546
547 /*! Returns the user logged in by this mechanism, or a null pointer if
548     authentication has not succeeded (yet).
549 */
550
551 User * SaslMechanism::user() const
552 {
553     return d->user;
554 }
555
556
557 /*! Returns true if \a mechanism is currently allowed, and false if
558     not. If \a privacy is true, allowed() assumes that the connection
559     does not use plain-text transmission.
560 */
561
562 bool SaslMechanism::allowed( Type mechanism, bool privacy )
563 {
564     bool a = false;
565     bool pt = false;
566     switch( mechanism ) {
567     case Anonymous:
568         a = Configuration::toggle( Configuration::AuthAnonymous );
569         break;
570     case Plain:
571         a = Configuration::toggle( Configuration::AuthPlain );
572         pt = true;
573         break;
574     case Login:
575         a = Configuration::toggle( Configuration::AuthLogin );
576         pt = true;
577         break;
578     case CramMD5:
579         a = Configuration::toggle( Configuration::AuthCramMd5 );
580         break;
581     case DigestMD5:
582         a = Configuration::toggle( Configuration::AuthDigestMd5 );
583         break;
584     }
585
586     if ( pt && !privacy ) {
587         Configuration::Text p = Configuration::AllowPlaintextPasswords;
588         EString s = Configuration::text( p ).lower();
589         if ( s == "never" )
590             a = false;
591         // XXX add "warn" etc. here
592     }
593
594     return a;
595 }
596
597
598 /*! Returns a list of space-separated allowed mechanisms.  If \a
599     privacy is false and plain-text passwords disallowed, such
600     mechanisms are not included.
601
602     Each mechanism is prefixed by \a prefix.
603 */
604
605 EString SaslMechanism::allowedMechanisms( const EString & prefix, bool privacy )
606 {
607     EStringList l;
608     if ( allowed( Anonymous, privacy ) )
609         l.append( "ANONYMOUS" );
610     if ( allowed( CramMD5, privacy ) )
611         l.append( "CRAM-MD5" );
612     if ( allowed( DigestMD5, privacy ) )
613         l.append( "DIGEST-MD5" );
614     if ( allowed( Plain, privacy ) )
615         l.append( "PLAIN" );
616     if ( allowed( Login, privacy ) )
617         l.append( "LOGIN" );
618     if ( l.isEmpty() )
619         return "";
620     return prefix + l.join( " " + prefix );
621 }
622
623
624 /*! Returns this object's SASL type, as set by the constructor. */
625
626 SaslMechanism::Type SaslMechanism::type() const
627 {
628     return d->type;
629 }
630
631
632 /*! Returns the canonical name of this object's SASL type, in lower
633     case. For example, "cram-md5" in the case of CramMD5.
634 */
635
636 EString SaslMechanism::name() const
637 {
638     EString r;
639     switch( d->type ) {
640     case Anonymous:
641         r = "anonymous";
642         break;
643     case Plain:
644         r = "plain";
645         break;
646     case Login:
647         r = "login";
648         break;
649     case CramMD5:
650         r = "cram-md5";
651         break;
652     case DigestMD5:
653         r = "digest-md5";
654         break;
655     }
656     return r;
657 }