Merge branch 'master' of ssh://down/oryx/aox
[aox:aox.git] / sasl / ldaprelay.cpp
1 // Copyright Oryx Mail Systems GmbH. All enquiries to info@oryx.com, please.
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
46
47
48 /*! Constructs an LdapRelay to verify whatever \a mechanism needs. */
49
50 LdapRelay::LdapRelay( SaslMechanism * mechanism )
51     : Connection( Connection::socket( server().protocol() ),
52                   Connection::LdapRelay ),
53       d ( new LdapRelayData )
54 {
55     d->mechanism = mechanism;
56     setTimeoutAfter( 30 );
57     connect( server() );
58     EventLoop::global()->addConnection( this );
59 }
60
61
62 /*! Reacts to incoming packets from the LDAP server, changes the
63     object's state, and eventually notifies the Mechanism. \a e is as
64     for Connection::react().
65 */
66
67 void LdapRelay::react( Event e )
68 {
69     if ( d->state != Working )
70         return;
71
72     switch( e ) {
73     case Read:
74         parse();
75         break;
76
77     case Connection::Timeout:
78         fail( "LDAP server timeout" );
79         break;
80
81     case Connect:
82         bind();
83         break;
84
85     case Error:
86         fail( "Unexpected error" );
87         break;
88
89     case Close:
90         fail( "Unexpected close by LDAP server" );
91         break;
92
93     case Shutdown:
94         break;
95     }
96
97     if ( d->state == Working )
98         return;
99
100     setState( Closing );
101     d->mechanism->execute();
102 }
103
104
105 /*! Returns the address of the LDAP server used. */
106
107 Endpoint LdapRelay::server()
108 {
109     return Endpoint(
110         Configuration::text( Configuration::LdapServerAddress ),
111         Configuration::scalar( Configuration::LdapServerPort ) );
112
113 }
114
115
116 /*! Parses the response the server sends, which has to be a bind
117     response.
118 */
119
120 void LdapRelay::parse()
121 {
122     Buffer * r = readBuffer();
123     uint byte;
124     if ( !d->haveReadType ) {
125         if ( r->size() < 2 )
126             return;
127         // LDAPMessage magic bytes (30 xx)
128         //     30 -> universal context-specific zero
129         //     0c -> length 12
130         byte = (*r)[0];
131         if ( byte != 0x30 ) {
132             fail( "Expected LDAP type byte 0x30, received 0x" +
133                   EString::fromNumber( byte, 16 ).lower() );
134             return;
135         }
136         d->responseLength = (*r)[1];
137         d->haveReadType = true;
138         r->remove( 2 );
139     }
140
141     if ( r->size() < d->responseLength )
142         return;
143
144     //  message-id (02 01 01)
145     //     02 -> integer
146     //     01 -> length
147     //     01 -> message-id
148     if ( (*r)[0] != 2 || (*r)[1] != 1 || (*r)[2] != 1 ) {
149         fail( "Expected LDAP message-id to have type 2 length 1 ID 1, "
150               "received type " + fn( (*r)[0] ) + " length " + fn( (*r)[1] ) +
151               " ID " + fn( (*r)[2] ) );
152         return;
153     }
154     r->remove( 3 );
155
156     //  bindresponse (61 07)
157     //     61 -> APPLICATION 1, BindResponse
158     //     07 -> length of remaining bytes
159     if ( (*r)[0] != 0x61 ) {
160         fail( "Expected LDAP response type 0x61, received type " +
161               EString::fromNumber( (*r)[0] ) );
162         return;
163     }
164     //uint bindResponseLength = (*r)[1];
165     r->remove( 2 );
166
167     //   resultcode
168     //     0a -> enum
169     //     01 -> length?
170     //     00 -> success
171     if ( (*r)[0] != 10 || (*r)[1] != 1 ) {
172         fail( "Expected LDAP result code to have type 10 length 1, "
173               "received type " + fn( (*r)[0] ) + " length " + fn( (*r)[1] ) );
174         return;
175     }
176     uint resultCode = (*r)[2];
177     r->remove( 3 );
178     if ( resultCode != 0 )
179         fail( "LDAP server refused authentication with result code " +
180               fn( resultCode ) );
181     else
182         succeed();
183
184     // I think we don't care about the rest of the data.
185
186     //   matchedDN
187     //     04 -> octetstring
188     //     00 -> length
189     if ( (*r)[1] + 2 >= (int)r->size() )
190         return;
191     r->remove( (*r)[1] + 2 );
192
193     //   errorMessage
194     //     04 -> octetstring
195     //     00 -> length
196
197     if ( (*r)[0] != 4 || (*r)[1] >= r->size() )
198         return;
199     uint l = (*r)[1];
200     r->remove( 2 );
201     EString e( r->string( l ) );
202     if ( !e.isEmpty() )
203         log( "Note: LDAP server returned error message: " + e );
204
205     if ( d->state != BindFailed )
206         unbind();
207 }
208
209
210 /*! Sends a single bind request. */
211
212 void LdapRelay::bind()
213 {
214     // LDAP message
215     //     30 -> LDAP message
216     //     nn -> number of remaining bytes
217
218     EString m;
219     m.append( 0x30 );
220
221     // Message id
222     //     02 -> integer
223     //     01 -> length
224     //     01 -> message-id
225
226     EString id;
227     id.append( "\002\001\001" );
228
229     // Bind request
230     //     60 -> APPLICATION 0, i.e. bind request
231     //     nn -> number of remaining bytes
232
233     EString h;
234     h.append( 0x60 );
235
236     //   version (03)
237     //    02 -> integer
238     //    01 -> length
239     //    03 -> version
240
241     EString s;
242     s.append( "\002\001\003" );
243
244     //   name (?)
245     //    04 -> octetstring
246     //    nn -> length
247     //    s* -> DN
248
249     s.append( 0x04 );
250     EString dn;
251     if ( d->mechanism->user() )
252         dn = d->mechanism->user()->ldapdn().utf8();
253     s.append( (char)dn.length() );
254     s.append( dn );
255
256     //   authentication
257     //    80 -> type: context-specific universal zero, and zero is "password"
258     //    nn -> length
259     //    s* -> password
260
261     s.append( 0x80 );
262     EString pw = d->mechanism->secret().utf8();
263     s.append( (char)pw.length() );
264     s.append( pw );
265
266     // Fill in the length fields
267
268     h.append( (char)s.length() );
269     m.append( (char)( id.length() + h.length() + s.length() ) );
270
271     enqueue( m );
272     enqueue( id );
273     enqueue( h );
274     enqueue( s );
275 }
276
277
278 /*! Sends an unbind request. */
279
280 void LdapRelay::unbind()
281 {
282     EString m;
283     m.append( 0x30 );
284     m.append( 0x05 );
285     m.append( 0x02 );
286     m.append( 0x01 );
287     m.append( 0x03 );
288     m.append( 0x42 );
289     m.append( "\000", 1 );
290     enqueue( m );
291 }
292
293
294 /*! This private helper sets the state and logs \a error. */
295
296 void LdapRelay::fail( const EString & error )
297 {
298     if ( d->state != Working )
299         return;
300
301     d->state = BindFailed;
302     log( error );
303 }
304
305
306 /*! This private helper sets the state and logs. */
307
308 void LdapRelay::succeed()
309 {
310     if ( d->state != Working )
311         return;
312
313     d->state = BindSucceeded;
314     log( "LDAP authentication succeeded" );
315 }
316
317
318 /*! Returns the relay object's current state. */
319
320 LdapRelay::State LdapRelay::state() const
321 {
322     return d->state;
323 }