Merge branch 'next' of ssh://down.oryx.com/oryx/aox into next
[aox:aox.git] / sasl / ldaprelay.cpp
1 // Copyright 2009 The Archiveopteryx Developers <info@aox.org>
2
3 #include "ldaprelay.h"
4
5 #include "configuration.h"
6 #include "eventloop.h"
7 #include "mechanism.h"
8 #include "buffer.h"
9 #include "user.h"
10
11
12 class LdapRelayData
13     : public Garbage
14 {
15 public:
16     LdapRelayData()
17         : mechanism( 0 ),
18           state( LdapRelay::Working ),
19           haveReadType( false ), responseLength( 0 )
20         {}
21
22     SaslMechanism * mechanism;
23
24     LdapRelay::State state;
25     bool haveReadType;
26     uint responseLength;
27 };
28
29
30 /*! \class LdapRelay ldaprelay.h
31
32     The LdapRelay class helps Mechanism relay SASL challenges and
33     responses to and from an LDAP server. If the LDAP server accepts
34     the authentication, then the user is accepted as an Archiveopteryx
35     user.
36
37     The LdapRelay state machine contains the following states:
38
39     Working: The LDAP server still hasn't answered.
40
41     BindFailed: We should reject this authentication.
42
43     BindSucceeded: We should accept this authentication.
44
45     The implementation is based on RFC 4511.
46 */
47
48
49
50 /*! Constructs an LdapRelay to verify whatever \a mechanism needs. */
51
52 LdapRelay::LdapRelay( SaslMechanism * mechanism )
53     : Connection( Connection::socket( server().protocol() ),
54                   Connection::LdapRelay ),
55       d ( new LdapRelayData )
56 {
57     d->mechanism = mechanism;
58     setTimeoutAfter( 30 );
59     connect( server() );
60     EventLoop::global()->addConnection( this );
61 }
62
63
64 /*! Reacts to incoming packets from the LDAP server, changes the
65     object's state, and eventually notifies the Mechanism. \a e is as
66     for Connection::react().
67 */
68
69 void LdapRelay::react( Event e )
70 {
71     if ( d->state != Working )
72         return;
73
74     switch( e ) {
75     case Read:
76         parse();
77         break;
78
79     case Connection::Timeout:
80         fail( "LDAP server timeout" );
81         break;
82
83     case Connect:
84         bind();
85         break;
86
87     case Error:
88         fail( "Unexpected error" );
89         break;
90
91     case Close:
92         fail( "Unexpected close by LDAP server" );
93         break;
94
95     case Shutdown:
96         break;
97     }
98
99     if ( d->state == Working )
100         return;
101
102     setState( Closing );
103     d->mechanism->execute();
104 }
105
106
107 /*! Returns the address of the LDAP server used. */
108
109 Endpoint LdapRelay::server()
110 {
111     return Endpoint(
112         Configuration::text( Configuration::LdapServerAddress ),
113         Configuration::scalar( Configuration::LdapServerPort ) );
114
115 }
116
117
118 static bool hasTypeAndLength( Buffer * r )
119 {
120     uint l = (*r)[1];
121
122     if ( r->size() >= 2 && ( l < 0x80 || r->size() >= 2+(l&0x7f) ) )
123         return true;
124     return false;
125 }
126
127
128 static uint removeLength( Buffer * r )
129 {
130     uint length = (*r)[0];
131     r->remove( 1 );
132
133     // Handle the indefinite form encoding of length (generated by
134     // e.g. Active Directory, but not permitted by RFC4511, p.42).
135     // <http://www.w3.org/Protocols/HTTP-NG/asn1.html>
136
137     if ( length >= 0x80 ) {
138         uint lengthLength = length & 0x7f;
139         length = 0;
140         uint i = 0;
141         while ( i < lengthLength ) {
142             length *= 256;
143             length += (*r)[i];
144             ++i;
145         }
146         r->remove( lengthLength );
147     }
148
149     return length;
150 }
151
152
153 /*! Parses the response the server sends, which has to be a bind
154     response.
155 */
156
157 void LdapRelay::parse()
158 {
159     Buffer * r = readBuffer();
160
161     if ( !d->haveReadType ) {
162         // LDAPMessage magic bytes (30 xx)
163         //     30 -> universal context-specific zero
164         //     xx -> message length
165
166         if ( !hasTypeAndLength( r ) )
167             return;
168
169         uint byte = (*r)[0];
170         if ( byte != 0x30 ) {
171             fail( "Expected LDAP type byte 0x30, received 0x" +
172                   EString::fromNumber( byte, 16 ).lower() );
173             return;
174         }
175         r->remove( 1 );
176
177         d->responseLength = removeLength( r );
178         if ( d->responseLength < 8 ) {
179             fail( "Expected LDAP response of at least 8 bytes, received "
180                   "only " + fn( d->responseLength ) + " bytes" );
181             return;
182         }
183
184         d->haveReadType = true;
185     }
186
187     if ( r->size() < d->responseLength )
188         return;
189
190     //  message-id (02 01 01)
191     //     02 -> integer
192     //     01 -> length
193     //     01 -> message-id
194     if ( (*r)[0] != 2 || (*r)[1] != 1 || (*r)[2] != 1 ) {
195         fail( "Expected LDAP message-id to have type 2 length 1 ID 1, "
196               "received type " + fn( (*r)[0] ) + " length " + fn( (*r)[1] ) +
197               " ID " + fn( (*r)[2] ) );
198         return;
199     }
200     r->remove( 3 );
201
202     //  bindresponse (61 07)
203     //     61 -> APPLICATION 1, BindResponse
204     //     07 -> length of remaining bytes
205     if ( (*r)[0] != 0x61 ) {
206         fail( "Expected LDAP response type 0x61, received type " +
207               EString::fromNumber( (*r)[0] ) );
208         return;
209     }
210     r->remove( 1 );
211
212     // The next three bytes should be the LDAP result code.
213     uint bindResponseLength = removeLength( r );
214     if ( bindResponseLength < 3 ) {
215         fail( "Expected bind response with length >= 3, received "
216               "only " + fn( bindResponseLength ) + " bytes" );
217         return;
218     }
219
220     //   resultcode
221     //     0a -> enum
222     //     01 -> length?
223     //     00 -> success
224     if ( (*r)[0] != 10 || (*r)[1] != 1 ) {
225         fail( "Expected LDAP result code to have type 10 length 1, "
226               "received type " + fn( (*r)[0] ) + " length " + fn( (*r)[1] ) );
227         return;
228     }
229     uint resultCode = (*r)[2];
230     r->remove( 3 );
231     if ( resultCode != 0 )
232         fail( "LDAP server refused authentication with result code " +
233               fn( resultCode ) );
234     else
235         succeed();
236
237     // I think we don't care about the rest of the data.
238
239     //   matchedDN
240     //     04 -> octetstring
241     //     00 -> length
242     if ( (*r)[1] + 2 >= (int)r->size() )
243         return;
244     r->remove( (*r)[1] + 2 );
245
246     //   errorMessage
247     //     04 -> octetstring
248     //     00 -> length
249
250     if ( (*r)[0] != 4 || (*r)[1] >= r->size() )
251         return;
252     uint l = (*r)[1];
253     r->remove( 2 );
254     EString e( r->string( l ) );
255     if ( !e.isEmpty() )
256         log( "Note: LDAP server returned error message: " + e );
257
258     if ( d->state != BindFailed )
259         unbind();
260 }
261
262
263 /*! Sends a single bind request. */
264
265 void LdapRelay::bind()
266 {
267     // LDAP message
268     //     30 -> LDAP message
269     //     nn -> number of remaining bytes
270
271     EString m;
272     m.append( 0x30 );
273
274     // Message id
275     //     02 -> integer
276     //     01 -> length
277     //     01 -> message-id
278
279     EString id;
280     id.append( "\002\001\001" );
281
282     // Bind request
283     //     60 -> APPLICATION 0, i.e. bind request
284     //     nn -> number of remaining bytes
285
286     EString h;
287     h.append( 0x60 );
288
289     //   version (03)
290     //    02 -> integer
291     //    01 -> length
292     //    03 -> version
293
294     EString s;
295     s.append( "\002\001\003" );
296
297     //   name (?)
298     //    04 -> octetstring
299     //    nn -> length
300     //    s* -> DN
301
302     s.append( 0x04 );
303     EString dn;
304     if ( d->mechanism->user() )
305         dn = d->mechanism->user()->ldapdn().utf8();
306     s.append( (char)dn.length() );
307     s.append( dn );
308
309     //   authentication
310     //    80 -> type: context-specific universal zero, and zero is "password"
311     //    nn -> length
312     //    s* -> password
313
314     s.append( 0x80 );
315     EString pw = d->mechanism->secret().utf8();
316     s.append( (char)pw.length() );
317     s.append( pw );
318
319     // Fill in the length fields
320
321     h.append( (char)s.length() );
322     m.append( (char)( id.length() + h.length() + s.length() ) );
323
324     enqueue( m );
325     enqueue( id );
326     enqueue( h );
327     enqueue( s );
328 }
329
330
331 /*! Sends an unbind request. */
332
333 void LdapRelay::unbind()
334 {
335     EString m;
336     m.append( 0x30 );
337     m.append( 0x05 );
338     m.append( 0x02 );
339     m.append( 0x01 );
340     m.append( 0x03 );
341     m.append( 0x42 );
342     m.append( "\000", 1 );
343     enqueue( m );
344 }
345
346
347 /*! This private helper sets the state and logs \a error. */
348
349 void LdapRelay::fail( const EString & error )
350 {
351     if ( d->state != Working )
352         return;
353
354     d->state = BindFailed;
355     log( error );
356 }
357
358
359 /*! This private helper sets the state and logs. */
360
361 void LdapRelay::succeed()
362 {
363     if ( d->state != Working )
364         return;
365
366     d->state = BindSucceeded;
367     log( "LDAP authentication succeeded" );
368 }
369
370
371 /*! Returns the relay object's current state. */
372
373 LdapRelay::State LdapRelay::state() const
374 {
375     return d->state;
376 }