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
43RCSID("$Id$");
44
45int ftp_do_gss_bindings = 0;
46int ftp_do_gss_delegate = 1;
47
48struct 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
55static int
56gss_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
73static int
74gss_check_prot(void *app_data, int level)
75{
76    if(level == prot_confidential)
77	return -1;
78    return 0;
79}
80
81static int
82gss_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
107static int
108gss_overhead(void *app_data, int level, int len)
109{
110    return 100; /* dunno? */
111}
112
113
114static int
115gss_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
135static void
136sockaddr_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
169static int
170gss_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
258int gssapi_userok(void*, char*);
259int gssapi_session(void*, char*);
260
261struct 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
281extern struct sockaddr *hisctladdr, *myctladdr;
282
283static int
284import_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
325static int
326gss_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
499struct 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 */
512