1 |
/* |
2 |
* Copyright (c) 1998 - 2005 Kungliga Tekniska Högskolan |
3 |
* (Royal Institute of Technology, Stockholm, Sweden). |
4 |
* All rights reserved. |
5 |
* |
6 |
* Redistribution and use in source and binary forms, with or without |
7 |
* modification, are permitted provided that the following conditions |
8 |
* are met: |
9 |
* |
10 |
* 1. Redistributions of source code must retain the above copyright |
11 |
* notice, this list of conditions and the following disclaimer. |
12 |
* |
13 |
* 2. Redistributions in binary form must reproduce the above copyright |
14 |
* notice, this list of conditions and the following disclaimer in the |
15 |
* documentation and/or other materials provided with the distribution. |
16 |
* |
17 |
* 3. Neither the name of the Institute nor the names of its contributors |
18 |
* may be used to endorse or promote products derived from this software |
19 |
* without specific prior written permission. |
20 |
* |
21 |
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
22 |
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
23 |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
24 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
25 |
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
26 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
27 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
28 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
29 |
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
30 |
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
31 |
* SUCH DAMAGE. |
32 |
*/ |
33 |
|
34 |
#ifdef FTP_SERVER |
35 |
#include "ftpd_locl.h" |
36 |
#else |
37 |
#include "ftp_locl.h" |
38 |
#endif |
39 |
#include <gssapi/gssapi.h> |
40 |
#include <gssapi/gssapi_krb5.h> |
41 |
#include <krb5_err.h> |
42 |
|
43 |
RCSID("$Id$"); |
44 |
|
45 |
int ftp_do_gss_bindings = 0; |
46 |
int ftp_do_gss_delegate = 1; |
47 |
|
48 |
struct gssapi_data { |
49 |
gss_ctx_id_t context_hdl; |
50 |
gss_name_t client_name; |
51 |
gss_cred_id_t delegated_cred_handle; |
52 |
void *mech_data; |
53 |
}; |
54 |
|
55 |
static int |
56 |
gss_init(void *app_data) |
57 |
{ |
58 |
struct gssapi_data *d = app_data; |
59 |
d->context_hdl = GSS_C_NO_CONTEXT; |
60 |
d->delegated_cred_handle = GSS_C_NO_CREDENTIAL; |
61 |
#if defined(FTP_SERVER) |
62 |
return 0; |
63 |
#else |
64 |
/* XXX Check the gss mechanism; with gss_indicate_mechs() ? */ |
65 |
#ifdef KRB5 |
66 |
return !use_kerberos; |
67 |
#else |
68 |
return 0; |
69 |
#endif /* KRB5 */ |
70 |
#endif /* FTP_SERVER */ |
71 |
} |
72 |
|
73 |
static int |
74 |
gss_check_prot(void *app_data, int level) |
75 |
{ |
76 |
if(level == prot_confidential) |
77 |
return -1; |
78 |
return 0; |
79 |
} |
80 |
|
81 |
static int |
82 |
gss_decode(void *app_data, void *buf, int len, int level) |
83 |
{ |
84 |
OM_uint32 maj_stat, min_stat; |
85 |
gss_buffer_desc input, output; |
86 |
gss_qop_t qop_state; |
87 |
int conf_state; |
88 |
struct gssapi_data *d = app_data; |
89 |
size_t ret_len; |
90 |
|
91 |
input.length = len; |
92 |
input.value = buf; |
93 |
maj_stat = gss_unwrap (&min_stat, |
94 |
d->context_hdl, |
95 |
&input, |
96 |
&output, |
97 |
&conf_state, |
98 |
&qop_state); |
99 |
if(GSS_ERROR(maj_stat)) |
100 |
return -1; |
101 |
memmove(buf, output.value, output.length); |
102 |
ret_len = output.length; |
103 |
gss_release_buffer(&min_stat, &output); |
104 |
return ret_len; |
105 |
} |
106 |
|
107 |
static int |
108 |
gss_overhead(void *app_data, int level, int len) |
109 |
{ |
110 |
return 100; /* dunno? */ |
111 |
} |
112 |
|
113 |
|
114 |
static int |
115 |
gss_encode(void *app_data, void *from, int length, int level, void **to) |
116 |
{ |
117 |
OM_uint32 maj_stat, min_stat; |
118 |
gss_buffer_desc input, output; |
119 |
int conf_state; |
120 |
struct gssapi_data *d = app_data; |
121 |
|
122 |
input.length = length; |
123 |
input.value = from; |
124 |
maj_stat = gss_wrap (&min_stat, |
125 |
d->context_hdl, |
126 |
level == prot_private, |
127 |
GSS_C_QOP_DEFAULT, |
128 |
&input, |
129 |
&conf_state, |
130 |
&output); |
131 |
*to = output.value; |
132 |
return output.length; |
133 |
} |
134 |
|
135 |
static void |
136 |
sockaddr_to_gss_address (struct sockaddr *sa, |
137 |
OM_uint32 *addr_type, |
138 |
gss_buffer_desc *gss_addr) |
139 |
{ |
140 |
switch (sa->sa_family) { |
141 |
#ifdef HAVE_IPV6 |
142 |
case AF_INET6 : { |
143 |
struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; |
144 |
|
145 |
gss_addr->length = 16; |
146 |
gss_addr->value = &sin6->sin6_addr; |
147 |
*addr_type = GSS_C_AF_INET6; |
148 |
break; |
149 |
} |
150 |
#endif |
151 |
case AF_INET : { |
152 |
struct sockaddr_in *sin4 = (struct sockaddr_in *)sa; |
153 |
|
154 |
gss_addr->length = 4; |
155 |
gss_addr->value = &sin4->sin_addr; |
156 |
*addr_type = GSS_C_AF_INET; |
157 |
break; |
158 |
} |
159 |
default : |
160 |
errx (1, "unknown address family %d", sa->sa_family); |
161 |
|
162 |
} |
163 |
} |
164 |
|
165 |
/* end common stuff */ |
166 |
|
167 |
#ifdef FTP_SERVER |
168 |
|
169 |
static int |
170 |
gss_adat(void *app_data, void *buf, size_t len) |
171 |
{ |
172 |
char *p = NULL; |
173 |
gss_buffer_desc input_token, output_token; |
174 |
OM_uint32 maj_stat, min_stat; |
175 |
gss_name_t client_name; |
176 |
struct gssapi_data *d = app_data; |
177 |
gss_channel_bindings_t bindings; |
178 |
|
179 |
if (ftp_do_gss_bindings) { |
180 |
bindings = malloc(sizeof(*bindings)); |
181 |
if (bindings == NULL) |
182 |
errx(1, "out of memory"); |
183 |
|
184 |
sockaddr_to_gss_address (his_addr, |
185 |
&bindings->initiator_addrtype, |
186 |
&bindings->initiator_address); |
187 |
sockaddr_to_gss_address (ctrl_addr, |
188 |
&bindings->acceptor_addrtype, |
189 |
&bindings->acceptor_address); |
190 |
|
191 |
bindings->application_data.length = 0; |
192 |
bindings->application_data.value = NULL; |
193 |
} else |
194 |
bindings = GSS_C_NO_CHANNEL_BINDINGS; |
195 |
|
196 |
input_token.value = buf; |
197 |
input_token.length = len; |
198 |
|
199 |
maj_stat = gss_accept_sec_context (&min_stat, |
200 |
&d->context_hdl, |
201 |
GSS_C_NO_CREDENTIAL, |
202 |
&input_token, |
203 |
bindings, |
204 |
&client_name, |
205 |
NULL, |
206 |
&output_token, |
207 |
NULL, |
208 |
NULL, |
209 |
&d->delegated_cred_handle); |
210 |
|
211 |
if (bindings != GSS_C_NO_CHANNEL_BINDINGS) |
212 |
free(bindings); |
213 |
|
214 |
if(output_token.length) { |
215 |
if(base64_encode(output_token.value, output_token.length, &p) < 0) { |
216 |
reply(535, "Out of memory base64-encoding."); |
217 |
return -1; |
218 |
} |
219 |
gss_release_buffer(&min_stat, &output_token); |
220 |
} |
221 |
if(maj_stat == GSS_S_COMPLETE){ |
222 |
d->client_name = client_name; |
223 |
client_name = GSS_C_NO_NAME; |
224 |
if(p) |
225 |
reply(235, "ADAT=%s", p); |
226 |
else |
227 |
reply(235, "ADAT Complete"); |
228 |
sec_complete = 1; |
229 |
|
230 |
} else if(maj_stat == GSS_S_CONTINUE_NEEDED) { |
231 |
if(p) |
232 |
reply(335, "ADAT=%s", p); |
233 |
else |
234 |
reply(335, "OK, need more data"); |
235 |
} else { |
236 |
OM_uint32 new_stat; |
237 |
OM_uint32 msg_ctx = 0; |
238 |
gss_buffer_desc status_string; |
239 |
gss_display_status(&new_stat, |
240 |
min_stat, |
241 |
GSS_C_MECH_CODE, |
242 |
GSS_C_NO_OID, |
243 |
&msg_ctx, |
244 |
&status_string); |
245 |
syslog(LOG_ERR, "gss_accept_sec_context: %.*s", |
246 |
(int)status_string.length, |
247 |
(char*)status_string.value); |
248 |
gss_release_buffer(&new_stat, &status_string); |
249 |
reply(431, "Security resource unavailable"); |
250 |
} |
251 |
|
252 |
if (client_name) |
253 |
gss_release_name(&min_stat, &client_name); |
254 |
free(p); |
255 |
return 0; |
256 |
} |
257 |
|
258 |
int gssapi_userok(void*, char*); |
259 |
int gssapi_session(void*, char*); |
260 |
|
261 |
struct sec_server_mech gss_server_mech = { |
262 |
"GSSAPI", |
263 |
sizeof(struct gssapi_data), |
264 |
gss_init, /* init */ |
265 |
NULL, /* end */ |
266 |
gss_check_prot, |
267 |
gss_overhead, |
268 |
gss_encode, |
269 |
gss_decode, |
270 |
/* */ |
271 |
NULL, |
272 |
gss_adat, |
273 |
NULL, /* pbsz */ |
274 |
NULL, /* ccc */ |
275 |
gssapi_userok, |
276 |
gssapi_session |
277 |
}; |
278 |
|
279 |
#else /* FTP_SERVER */ |
280 |
|
281 |
extern struct sockaddr *hisctladdr, *myctladdr; |
282 |
|
283 |
static int |
284 |
import_name(const char *kname, const char *host, gss_name_t *target_name) |
285 |
{ |
286 |
OM_uint32 maj_stat, min_stat; |
287 |
gss_buffer_desc name; |
288 |
char *str; |
289 |
|
290 |
name.length = asprintf(&str, "%s@%s", kname, host); |
291 |
if (str == NULL) { |
292 |
printf("Out of memory\n"); |
293 |
return AUTH_ERROR; |
294 |
} |
295 |
name.value = str; |
296 |
|
297 |
maj_stat = gss_import_name(&min_stat, |
298 |
&name, |
299 |
GSS_C_NT_HOSTBASED_SERVICE, |
300 |
target_name); |
301 |
if (GSS_ERROR(maj_stat)) { |
302 |
OM_uint32 new_stat; |
303 |
OM_uint32 msg_ctx = 0; |
304 |
gss_buffer_desc status_string; |
305 |
|
306 |
gss_display_status(&new_stat, |
307 |
min_stat, |
308 |
GSS_C_MECH_CODE, |
309 |
GSS_C_NO_OID, |
310 |
&msg_ctx, |
311 |
&status_string); |
312 |
printf("Error importing name %.*s: %.*s\n", |
313 |
(int)name.length, |
314 |
(char *)name.value, |
315 |
(int)status_string.length, |
316 |
(char *)status_string.value); |
317 |
free(name.value); |
318 |
gss_release_buffer(&new_stat, &status_string); |
319 |
return AUTH_ERROR; |
320 |
} |
321 |
free(name.value); |
322 |
return 0; |
323 |
} |
324 |
|
325 |
static int |
326 |
gss_auth(void *app_data, char *host) |
327 |
{ |
328 |
|
329 |
OM_uint32 maj_stat, min_stat; |
330 |
gss_name_t target_name; |
331 |
gss_buffer_desc input, output_token; |
332 |
int context_established = 0; |
333 |
char *p; |
334 |
int n; |
335 |
gss_channel_bindings_t bindings; |
336 |
struct gssapi_data *d = app_data; |
337 |
OM_uint32 mech_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG; |
338 |
|
339 |
const char *knames[] = { "ftp", "host", NULL }, **kname = knames; |
340 |
|
341 |
|
342 |
if(import_name(*kname++, host, &target_name)) |
343 |
return AUTH_ERROR; |
344 |
|
345 |
input.length = 0; |
346 |
input.value = NULL; |
347 |
|
348 |
if (ftp_do_gss_bindings) { |
349 |
bindings = malloc(sizeof(*bindings)); |
350 |
if (bindings == NULL) |
351 |
errx(1, "out of memory"); |
352 |
|
353 |
sockaddr_to_gss_address (myctladdr, |
354 |
&bindings->initiator_addrtype, |
355 |
&bindings->initiator_address); |
356 |
sockaddr_to_gss_address (hisctladdr, |
357 |
&bindings->acceptor_addrtype, |
358 |
&bindings->acceptor_address); |
359 |
|
360 |
bindings->application_data.length = 0; |
361 |
bindings->application_data.value = NULL; |
362 |
} else |
363 |
bindings = GSS_C_NO_CHANNEL_BINDINGS; |
364 |
|
365 |
if (ftp_do_gss_delegate) |
366 |
mech_flags |= GSS_C_DELEG_FLAG; |
367 |
|
368 |
while(!context_established) { |
369 |
maj_stat = gss_init_sec_context(&min_stat, |
370 |
GSS_C_NO_CREDENTIAL, |
371 |
&d->context_hdl, |
372 |
target_name, |
373 |
GSS_C_NO_OID, |
374 |
mech_flags, |
375 |
0, |
376 |
bindings, |
377 |
&input, |
378 |
NULL, |
379 |
&output_token, |
380 |
NULL, |
381 |
NULL); |
382 |
if (GSS_ERROR(maj_stat)) { |
383 |
OM_uint32 new_stat; |
384 |
OM_uint32 msg_ctx = 0; |
385 |
gss_buffer_desc status_string; |
386 |
|
387 |
d->context_hdl = GSS_C_NO_CONTEXT; |
388 |
|
389 |
gss_release_name(&min_stat, &target_name); |
390 |
|
391 |
if(*kname != NULL) { |
392 |
|
393 |
if(import_name(*kname++, host, &target_name)) { |
394 |
if (bindings != GSS_C_NO_CHANNEL_BINDINGS) |
395 |
free(bindings); |
396 |
return AUTH_ERROR; |
397 |
} |
398 |
continue; |
399 |
} |
400 |
|
401 |
if (bindings != GSS_C_NO_CHANNEL_BINDINGS) |
402 |
free(bindings); |
403 |
|
404 |
gss_display_status(&new_stat, |
405 |
min_stat, |
406 |
GSS_C_MECH_CODE, |
407 |
GSS_C_NO_OID, |
408 |
&msg_ctx, |
409 |
&status_string); |
410 |
printf("Error initializing security context: %.*s\n", |
411 |
(int)status_string.length, |
412 |
(char*)status_string.value); |
413 |
gss_release_buffer(&new_stat, &status_string); |
414 |
return AUTH_CONTINUE; |
415 |
} |
416 |
|
417 |
if (input.value) { |
418 |
free(input.value); |
419 |
input.value = NULL; |
420 |
input.length = 0; |
421 |
} |
422 |
if (output_token.length != 0) { |
423 |
base64_encode(output_token.value, output_token.length, &p); |
424 |
gss_release_buffer(&min_stat, &output_token); |
425 |
n = command("ADAT %s", p); |
426 |
free(p); |
427 |
} |
428 |
if (GSS_ERROR(maj_stat)) { |
429 |
if (d->context_hdl != GSS_C_NO_CONTEXT) |
430 |
gss_delete_sec_context (&min_stat, |
431 |
&d->context_hdl, |
432 |
GSS_C_NO_BUFFER); |
433 |
break; |
434 |
} |
435 |
if (maj_stat & GSS_S_CONTINUE_NEEDED) { |
436 |
p = strstr(reply_string, "ADAT="); |
437 |
if(p == NULL){ |
438 |
printf("Error: expected ADAT in reply. got: %s\n", |
439 |
reply_string); |
440 |
if (bindings != GSS_C_NO_CHANNEL_BINDINGS) |
441 |
free(bindings); |
442 |
return AUTH_ERROR; |
443 |
} else { |
444 |
p+=5; |
445 |
input.value = malloc(strlen(p)); |
446 |
input.length = base64_decode(p, input.value); |
447 |
} |
448 |
} else { |
449 |
if(code != 235) { |
450 |
printf("Unrecognized response code: %d\n", code); |
451 |
if (bindings != GSS_C_NO_CHANNEL_BINDINGS) |
452 |
free(bindings); |
453 |
return AUTH_ERROR; |
454 |
} |
455 |
context_established = 1; |
456 |
} |
457 |
} |
458 |
|
459 |
gss_release_name(&min_stat, &target_name); |
460 |
|
461 |
if (bindings != GSS_C_NO_CHANNEL_BINDINGS) |
462 |
free(bindings); |
463 |
if (input.value) |
464 |
free(input.value); |
465 |
|
466 |
{ |
467 |
gss_name_t targ_name; |
468 |
|
469 |
maj_stat = gss_inquire_context(&min_stat, |
470 |
d->context_hdl, |
471 |
NULL, |
472 |
&targ_name, |
473 |
NULL, |
474 |
NULL, |
475 |
NULL, |
476 |
NULL, |
477 |
NULL); |
478 |
if (GSS_ERROR(maj_stat) == 0) { |
479 |
gss_buffer_desc name; |
480 |
maj_stat = gss_display_name (&min_stat, |
481 |
targ_name, |
482 |
&name, |
483 |
NULL); |
484 |
if (GSS_ERROR(maj_stat) == 0) { |
485 |
printf("Authenticated to <%.*s>\n", |
486 |
(int)name.length, |
487 |
(char *)name.value); |
488 |
gss_release_buffer(&min_stat, &name); |
489 |
} |
490 |
gss_release_name(&min_stat, &targ_name); |
491 |
} else |
492 |
printf("Failed to get gss name of peer.\n"); |
493 |
} |
494 |
|
495 |
|
496 |
return AUTH_OK; |
497 |
} |
498 |
|
499 |
struct sec_client_mech gss_client_mech = { |
500 |
"GSSAPI", |
501 |
sizeof(struct gssapi_data), |
502 |
gss_init, |
503 |
gss_auth, |
504 |
NULL, /* end */ |
505 |
gss_check_prot, |
506 |
gss_overhead, |
507 |
gss_encode, |
508 |
gss_decode, |
509 |
}; |
510 |
|
511 |
#endif /* FTP_SERVER */ |