r1360: - remove unused state SPNEGO_CLIENT_SEND_MECHS
[samba.git] / source4 / libcli / auth / spnego.c
1 /* 
2    Unix SMB/CIFS implementation.
3
4    RFC2478 Compliant SPNEGO implementation
5    
6    Copyright (C) Jim McDonough <jmcd@us.ibm.com>      2003
7    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2004
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13    
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    
20    You should have received a copy of the GNU General Public License
21    along with this program; if not, write to the Free Software
22    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 */
24
25 #include "includes.h"
26
27 #undef DBGC_CLASS
28 #define DBGC_CLASS DBGC_AUTH
29
30 enum spnego_state_position {
31         SPNEGO_SERVER_START,
32         SPNEGO_CLIENT_START,
33         SPNEGO_TARG,
34         SPNEGO_FALLBACK,
35         SPNEGO_DONE
36 };
37
38 struct spnego_state {
39         TALLOC_CTX *mem_ctx;
40         uint_t ref_count;
41         enum spnego_message_type expected_packet;
42         enum spnego_message_type state_position;
43         enum spnego_negResult result;
44         struct gensec_security *sub_sec_security;
45 };
46
47 static NTSTATUS gensec_spnego_client_start(struct gensec_security *gensec_security)
48 {
49         struct spnego_state *spnego_state;
50         TALLOC_CTX *mem_ctx = talloc_init("gensec_spengo_client_start");
51         if (!mem_ctx) {
52                 return NT_STATUS_NO_MEMORY;
53         }
54         spnego_state = talloc_p(mem_ctx, struct spnego_state);
55                 
56         if (!spnego_state) {
57                 return NT_STATUS_NO_MEMORY;
58         }
59
60         spnego_state->expected_packet = SPNEGO_NEG_TOKEN_INIT;
61         spnego_state->state_position = SPNEGO_CLIENT_START;
62         spnego_state->result = SPNEGO_ACCEPT_INCOMPLETE;
63         spnego_state->mem_ctx = mem_ctx;
64         spnego_state->sub_sec_security = NULL;
65
66         gensec_security->private_data = spnego_state;
67         return NT_STATUS_OK;
68 }
69
70 /*
71   wrappers for the spnego_*() functions
72 */
73 static NTSTATUS gensec_spnego_unseal_packet(struct gensec_security *gensec_security, 
74                                      TALLOC_CTX *mem_ctx, 
75                                      uint8_t *data, size_t length, DATA_BLOB *sig)
76 {
77         struct spnego_state *spnego_state = gensec_security->private_data;
78
79         if (spnego_state->state_position != SPNEGO_DONE 
80             && spnego_state->state_position != SPNEGO_FALLBACK) {
81                 return NT_STATUS_INVALID_PARAMETER;
82         }
83         
84         return gensec_unseal_packet(spnego_state->sub_sec_security, 
85                                     mem_ctx, data, length, sig); 
86 }
87
88 static NTSTATUS gensec_spnego_check_packet(struct gensec_security *gensec_security, 
89                                      TALLOC_CTX *mem_ctx, 
90                                      const uint8_t *data, size_t length, 
91                                      const DATA_BLOB *sig)
92 {
93         struct spnego_state *spnego_state = gensec_security->private_data;
94
95         return NT_STATUS_NOT_IMPLEMENTED;
96         if (spnego_state->state_position != SPNEGO_DONE 
97             && spnego_state->state_position != SPNEGO_FALLBACK) {
98                 return NT_STATUS_INVALID_PARAMETER;
99         }
100         
101         return gensec_check_packet(spnego_state->sub_sec_security, 
102                                 mem_ctx, data, length, sig);
103 }
104
105 static NTSTATUS gensec_spnego_seal_packet(struct gensec_security *gensec_security, 
106                                     TALLOC_CTX *mem_ctx, 
107                                     uint8_t *data, size_t length, 
108                                     DATA_BLOB *sig)
109 {
110         struct spnego_state *spnego_state = gensec_security->private_data;
111
112         return NT_STATUS_NOT_IMPLEMENTED;
113         if (spnego_state->state_position != SPNEGO_DONE 
114             && spnego_state->state_position != SPNEGO_FALLBACK) {
115                 return NT_STATUS_INVALID_PARAMETER;
116         }
117         
118         return gensec_seal_packet(spnego_state->sub_sec_security, 
119                                   mem_ctx, data, length, sig);
120 }
121
122 static NTSTATUS gensec_spnego_sign_packet(struct gensec_security *gensec_security, 
123                                     TALLOC_CTX *mem_ctx, 
124                                     const uint8_t *data, size_t length, 
125                                     DATA_BLOB *sig)
126 {
127         struct spnego_state *spnego_state = gensec_security->private_data;
128
129         if (spnego_state->state_position != SPNEGO_DONE 
130             && spnego_state->state_position != SPNEGO_FALLBACK) {
131                 return NT_STATUS_INVALID_PARAMETER;
132         }
133         
134         return gensec_sign_packet(spnego_state->sub_sec_security, 
135                                   mem_ctx, data, length, sig);
136 }
137
138 static NTSTATUS gensec_spnego_session_key(struct gensec_security *gensec_security, 
139                                     DATA_BLOB *session_key)
140 {
141         struct spnego_state *spnego_state = gensec_security->private_data;
142         if (spnego_state->state_position != SPNEGO_DONE 
143             && spnego_state->state_position != SPNEGO_FALLBACK) {
144                 return NT_STATUS_INVALID_PARAMETER;
145         }
146         
147         return gensec_session_key(spnego_state->sub_sec_security, 
148                                   session_key);
149 }
150
151 /** Fallback to another GENSEC mechanism, based on magic strings 
152  *
153  * This is the 'fallback' case, where we don't get SPENGO, and have to
154  * try all the other options (and hope they all have a magic string
155  * they check)
156 */
157
158 static NTSTATUS gensec_spengo_server_try_fallback(struct gensec_security *gensec_security, 
159                                                   struct spnego_state *spnego_state,
160                                                   TALLOC_CTX *out_mem_ctx, 
161                                                   const DATA_BLOB in, DATA_BLOB *out) 
162 {
163         int i;
164         int num_ops;
165         const struct gensec_security_ops **all_ops = gensec_security_all(&num_ops);
166         for (i=0; i < num_ops; i++) {
167                 NTSTATUS nt_status;
168                 if (!all_ops[i]->oid) {
169                         continue;
170                 }
171                 nt_status = gensec_subcontext_start(gensec_security, 
172                                                     &spnego_state->sub_sec_security);
173                 if (!NT_STATUS_IS_OK(nt_status)) {
174                         return nt_status;
175                 }
176                 /* select the sub context */
177                 nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
178                                                      all_ops[i]->oid);
179                 if (!NT_STATUS_IS_OK(nt_status)) {
180                         gensec_end(&spnego_state->sub_sec_security);
181                                         continue;
182                 }
183                 nt_status = gensec_update(spnego_state->sub_sec_security,
184                                                           out_mem_ctx, in, out);
185                 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
186                         spnego_state->state_position = SPNEGO_FALLBACK;
187                         return nt_status;
188                 }
189                 gensec_end(&spnego_state->sub_sec_security);
190         }
191         DEBUG(1, ("Failed to parse SPENGO request\n"));
192         return NT_STATUS_INVALID_PARAMETER;
193         
194 }
195
196 static NTSTATUS gensec_spnego_update(struct gensec_security *gensec_security, TALLOC_CTX *out_mem_ctx, 
197                                const DATA_BLOB in, DATA_BLOB *out) 
198 {
199         struct spnego_state *spnego_state = gensec_security->private_data;
200         DATA_BLOB null_data_blob = data_blob(NULL, 0);
201         DATA_BLOB unwrapped_out;
202         struct spnego_data spnego_out;
203         struct spnego_data spnego;
204
205         ssize_t len;
206
207         if (!out_mem_ctx) {
208                 out_mem_ctx = spnego_state->mem_ctx;
209         }
210
211         /* and switch into the state machine */
212
213         switch (spnego_state->state_position) {
214         case SPNEGO_FALLBACK:
215                 return gensec_update(spnego_state->sub_sec_security,
216                                      out_mem_ctx, in, out);
217         case SPNEGO_SERVER_START:
218         {
219                 if (in.length) {
220                         len = spnego_read_data(in, &spnego);
221                         if (len == -1) {
222                                 return gensec_spengo_server_try_fallback(gensec_security, spnego_state, out_mem_ctx, in, out);
223                         } else {
224                                 /* client sent NegTargetInit */
225                         }
226                 } else {
227                         /* server needs to send NegTargetInit */
228                 }
229         }
230
231         case SPNEGO_CLIENT_START:
232         {
233                 /* The server offers a list of mechanisms */
234                 
235                 char **mechType;
236                 char *my_mechs[] = {NULL, NULL};
237                 int i;
238                 NTSTATUS nt_status;
239
240                 if (!in.length) {
241                         /* client to produce negTokenInit */
242                         return NT_STATUS_INVALID_PARAMETER;
243                 }
244                 
245                 len = spnego_read_data(in, &spnego);
246                 
247                 if (len == -1) {
248                         return NT_STATUS_INVALID_PARAMETER;
249                 }
250                 
251                 /* OK, so it's real SPNEGO, check the packet's the one we expect */
252                 if (spnego.type != spnego_state->expected_packet) {
253                         spnego_free_data(&spnego);
254                         DEBUG(1, ("Invalid SPENGO request: %d, expected %d\n", spnego.type, 
255                                   spnego_state->expected_packet));
256                         return NT_STATUS_INVALID_PARAMETER;
257                 }
258
259                 mechType = spnego.negTokenInit.mechTypes;
260                 for (i=0; mechType && mechType[i]; i++) {
261                         nt_status = gensec_client_start(&spnego_state->sub_sec_security);
262                         if (!NT_STATUS_IS_OK(nt_status)) {
263                                 break;
264                         }
265                         /* select the sub context */
266                         nt_status = gensec_start_mech_by_oid(spnego_state->sub_sec_security,
267                                                              mechType[i]);
268                         if (!NT_STATUS_IS_OK(nt_status)) {
269                                 gensec_end(&spnego_state->sub_sec_security);
270                                 continue;
271                         }
272                         
273                         if (i == 0) {
274                                 nt_status = gensec_update(spnego_state->sub_sec_security,
275                                                           out_mem_ctx, 
276                                                           spnego.negTokenInit.mechToken, 
277                                                           &unwrapped_out);
278                         } else {
279                                 /* only get the helping start blob for the first OID */
280                                 nt_status = gensec_update(spnego_state->sub_sec_security,
281                                                           out_mem_ctx, 
282                                                           null_data_blob, 
283                                                           &unwrapped_out);
284                         }
285                         if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
286                                 DEBUG(1, ("SPENGO(%s) NEG_TOKEN_INIT failed: %s\n", 
287                                           spnego_state->sub_sec_security->ops->name, nt_errstr(nt_status)));
288                                 gensec_end(&spnego_state->sub_sec_security);
289                         } else {
290                                 break;
291                         }
292                 }
293                 if (!mechType || !mechType[i]) {
294                         DEBUG(1, ("SPENGO: Could not find a suitable mechtype in NEG_TOKEN_INIT\n"));
295                 }
296                 
297                 spnego_free_data(&spnego);
298                 if (!NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
299                         return nt_status;
300                 }
301                 
302                 /* compose reply */
303                 my_mechs[0] = spnego_state->sub_sec_security->ops->oid;
304                 
305                 spnego_out.type = SPNEGO_NEG_TOKEN_INIT;
306                 spnego_out.negTokenInit.mechTypes = my_mechs;
307                 spnego_out.negTokenInit.reqFlags = 0;
308                 spnego_out.negTokenInit.mechListMIC = null_data_blob;
309                 spnego_out.negTokenInit.mechToken = unwrapped_out;
310                 
311                 if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
312                         DEBUG(1, ("Failed to write SPENGO reply to NEG_TOKEN_INIT\n"));
313                                 return NT_STATUS_INVALID_PARAMETER;
314                 }
315                 
316                 /* set next state */
317                 spnego_state->expected_packet = SPNEGO_NEG_TOKEN_TARG;
318                 spnego_state->state_position = SPNEGO_TARG;
319                 
320                 return nt_status;
321         }
322         case SPNEGO_TARG:
323         {
324                 NTSTATUS nt_status;
325                 if (!in.length) {
326                         return NT_STATUS_INVALID_PARAMETER;
327                 }
328                 
329                 len = spnego_read_data(in, &spnego);
330                 
331                 if (len == -1) {
332                         return NT_STATUS_INVALID_PARAMETER;
333                 }
334                 
335                 /* OK, so it's real SPNEGO, check the packet's the one we expect */
336                 if (spnego.type != spnego_state->expected_packet) {
337                         spnego_free_data(&spnego);
338                         DEBUG(1, ("Invalid SPENGO request: %d, expected %d\n", spnego.type, 
339                                   spnego_state->expected_packet));
340                         return NT_STATUS_INVALID_PARAMETER;
341                 }
342         
343                 if (spnego.negTokenTarg.negResult == SPNEGO_REJECT) {
344                         return NT_STATUS_ACCESS_DENIED;
345                 }
346                 
347                 if (spnego.negTokenTarg.responseToken.length) {
348                         nt_status = gensec_update(spnego_state->sub_sec_security,
349                                                   out_mem_ctx, 
350                                                   spnego.negTokenTarg.responseToken, 
351                                                   &unwrapped_out);
352                 } else {
353                         unwrapped_out = data_blob(NULL, 0);
354                         nt_status = NT_STATUS_OK;
355                 }
356                 
357                 if (NT_STATUS_IS_OK(nt_status) 
358                     && (spnego.negTokenTarg.negResult != SPNEGO_ACCEPT_COMPLETED)) {
359                         nt_status = NT_STATUS_INVALID_PARAMETER;
360                 }
361                 
362                 spnego_state->result = spnego.negTokenTarg.negResult;
363                 spnego_free_data(&spnego);
364                 
365                 if (NT_STATUS_EQUAL(nt_status, NT_STATUS_MORE_PROCESSING_REQUIRED)) {
366                         /* compose reply */
367                         spnego_out.type = SPNEGO_NEG_TOKEN_TARG;
368                         spnego_out.negTokenTarg.negResult = SPNEGO_ACCEPT_INCOMPLETE;
369                         spnego_out.negTokenTarg.supportedMech 
370                                 = spnego_state->sub_sec_security->ops->oid;
371                         spnego_out.negTokenTarg.responseToken = unwrapped_out;
372                         spnego_out.negTokenTarg.mechListMIC = null_data_blob;
373                         
374                         if (spnego_write_data(out_mem_ctx, out, &spnego_out) == -1) {
375                                 DEBUG(1, ("Failed to write SPENGO reply to NEG_TOKEN_TARG\n"));
376                                 return NT_STATUS_INVALID_PARAMETER;
377                         }
378                         spnego_state->state_position = SPNEGO_TARG;
379                 } else if (NT_STATUS_IS_OK(nt_status)) {
380                         spnego_state->state_position = SPNEGO_DONE;
381                 } else {
382                         DEBUG(1, ("SPENGO(%s) login failed: %s\n", 
383                                   spnego_state->sub_sec_security->ops->name, 
384                                   nt_errstr(nt_status)));
385                         return nt_status;
386                 }
387                 
388                 return nt_status;
389         }
390         default:
391                 spnego_free_data(&spnego);
392                 DEBUG(1, ("Invalid SPENGO request: %d\n", spnego.type));
393                 return NT_STATUS_INVALID_PARAMETER;
394         }
395 }
396
397 static void gensec_spnego_end(struct gensec_security *gensec_security)
398 {
399         struct spnego_state *spnego_state = gensec_security->private_data;
400
401         if (spnego_state->sub_sec_security) {
402                 gensec_end(&spnego_state->sub_sec_security);
403         }
404
405         talloc_destroy(spnego_state->mem_ctx);
406
407         gensec_security->private_data = NULL;
408 }
409
410 static const struct gensec_security_ops gensec_spnego_security_ops = {
411         .name           = "spnego",
412         .sasl_name      = "GSS-SPNEGO",
413         .auth_type      = DCERPC_AUTH_TYPE_SPNEGO,
414         .oid            = OID_SPNEGO,
415         .client_start   = gensec_spnego_client_start,
416         .update         = gensec_spnego_update,
417         .seal_packet    = gensec_spnego_seal_packet,
418         .sign_packet    = gensec_spnego_sign_packet,
419         .check_packet   = gensec_spnego_check_packet,
420         .unseal_packet  = gensec_spnego_unseal_packet,
421         .session_key    = gensec_spnego_session_key,
422         .end            = gensec_spnego_end
423 };
424
425 NTSTATUS gensec_spengo_init(void)
426 {
427         NTSTATUS ret;
428         ret = register_backend("gensec", &gensec_spnego_security_ops);
429         if (!NT_STATUS_IS_OK(ret)) {
430                 DEBUG(0,("Failed to register '%s' gensec backend!\n",
431                         gensec_spnego_security_ops.name));
432                 return ret;
433         }
434
435         return ret;
436 }