1178825Sdfr/*
2233294Sstas * Copyright (c) 1998 - 2005 Kungliga Tekniska H��gskolan
3233294Sstas * (Royal Institute of Technology, Stockholm, Sweden).
4233294Sstas * All rights reserved.
5178825Sdfr *
6233294Sstas * Redistribution and use in source and binary forms, with or without
7233294Sstas * modification, are permitted provided that the following conditions
8233294Sstas * are met:
9178825Sdfr *
10233294Sstas * 1. Redistributions of source code must retain the above copyright
11233294Sstas *    notice, this list of conditions and the following disclaimer.
12178825Sdfr *
13233294Sstas * 2. Redistributions in binary form must reproduce the above copyright
14233294Sstas *    notice, this list of conditions and the following disclaimer in the
15233294Sstas *    documentation and/or other materials provided with the distribution.
16178825Sdfr *
17233294Sstas * 3. Neither the name of the Institute nor the names of its contributors
18233294Sstas *    may be used to endorse or promote products derived from this software
19233294Sstas *    without specific prior written permission.
20178825Sdfr *
21233294Sstas * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
22233294Sstas * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23233294Sstas * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24233294Sstas * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
25233294Sstas * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26233294Sstas * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27233294Sstas * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28233294Sstas * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29233294Sstas * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30233294Sstas * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31233294Sstas * SUCH DAMAGE.
32178825Sdfr */
33178825Sdfr
34178825Sdfr#ifdef FTP_SERVER
35178825Sdfr#include "ftpd_locl.h"
36178825Sdfr#else
37178825Sdfr#include "ftp_locl.h"
38178825Sdfr#endif
39233294Sstas#include <gssapi/gssapi.h>
40233294Sstas#include <gssapi/gssapi_krb5.h>
41178825Sdfr#include <krb5_err.h>
42178825Sdfr
43233294SstasRCSID("$Id$");
44178825Sdfr
45178825Sdfrint ftp_do_gss_bindings = 0;
46178825Sdfrint ftp_do_gss_delegate = 1;
47178825Sdfr
48233294Sstasstruct gssapi_data {
49178825Sdfr    gss_ctx_id_t context_hdl;
50233294Sstas    gss_name_t client_name;
51178825Sdfr    gss_cred_id_t delegated_cred_handle;
52178825Sdfr    void *mech_data;
53178825Sdfr};
54178825Sdfr
55178825Sdfrstatic int
56178825Sdfrgss_init(void *app_data)
57178825Sdfr{
58233294Sstas    struct gssapi_data *d = app_data;
59178825Sdfr    d->context_hdl = GSS_C_NO_CONTEXT;
60178825Sdfr    d->delegated_cred_handle = GSS_C_NO_CREDENTIAL;
61178825Sdfr#if defined(FTP_SERVER)
62178825Sdfr    return 0;
63178825Sdfr#else
64178825Sdfr    /* XXX Check the gss mechanism; with  gss_indicate_mechs() ? */
65178825Sdfr#ifdef KRB5
66178825Sdfr    return !use_kerberos;
67178825Sdfr#else
68178825Sdfr    return 0;
69178825Sdfr#endif /* KRB5 */
70178825Sdfr#endif /* FTP_SERVER */
71178825Sdfr}
72178825Sdfr
73178825Sdfrstatic int
74178825Sdfrgss_check_prot(void *app_data, int level)
75178825Sdfr{
76178825Sdfr    if(level == prot_confidential)
77178825Sdfr	return -1;
78178825Sdfr    return 0;
79178825Sdfr}
80178825Sdfr
81178825Sdfrstatic int
82178825Sdfrgss_decode(void *app_data, void *buf, int len, int level)
83178825Sdfr{
84178825Sdfr    OM_uint32 maj_stat, min_stat;
85178825Sdfr    gss_buffer_desc input, output;
86178825Sdfr    gss_qop_t qop_state;
87178825Sdfr    int conf_state;
88233294Sstas    struct gssapi_data *d = app_data;
89178825Sdfr    size_t ret_len;
90178825Sdfr
91178825Sdfr    input.length = len;
92178825Sdfr    input.value = buf;
93178825Sdfr    maj_stat = gss_unwrap (&min_stat,
94178825Sdfr			   d->context_hdl,
95178825Sdfr			   &input,
96178825Sdfr			   &output,
97178825Sdfr			   &conf_state,
98178825Sdfr			   &qop_state);
99178825Sdfr    if(GSS_ERROR(maj_stat))
100178825Sdfr	return -1;
101178825Sdfr    memmove(buf, output.value, output.length);
102178825Sdfr    ret_len = output.length;
103178825Sdfr    gss_release_buffer(&min_stat, &output);
104178825Sdfr    return ret_len;
105178825Sdfr}
106178825Sdfr
107178825Sdfrstatic int
108178825Sdfrgss_overhead(void *app_data, int level, int len)
109178825Sdfr{
110178825Sdfr    return 100; /* dunno? */
111178825Sdfr}
112178825Sdfr
113178825Sdfr
114178825Sdfrstatic int
115178825Sdfrgss_encode(void *app_data, void *from, int length, int level, void **to)
116178825Sdfr{
117178825Sdfr    OM_uint32 maj_stat, min_stat;
118178825Sdfr    gss_buffer_desc input, output;
119178825Sdfr    int conf_state;
120233294Sstas    struct gssapi_data *d = app_data;
121178825Sdfr
122178825Sdfr    input.length = length;
123178825Sdfr    input.value = from;
124178825Sdfr    maj_stat = gss_wrap (&min_stat,
125178825Sdfr			 d->context_hdl,
126178825Sdfr			 level == prot_private,
127178825Sdfr			 GSS_C_QOP_DEFAULT,
128178825Sdfr			 &input,
129178825Sdfr			 &conf_state,
130178825Sdfr			 &output);
131178825Sdfr    *to = output.value;
132178825Sdfr    return output.length;
133178825Sdfr}
134178825Sdfr
135178825Sdfrstatic void
136178825Sdfrsockaddr_to_gss_address (struct sockaddr *sa,
137178825Sdfr			 OM_uint32 *addr_type,
138178825Sdfr			 gss_buffer_desc *gss_addr)
139178825Sdfr{
140178825Sdfr    switch (sa->sa_family) {
141178825Sdfr#ifdef HAVE_IPV6
142178825Sdfr    case AF_INET6 : {
143178825Sdfr	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
144178825Sdfr
145178825Sdfr	gss_addr->length = 16;
146178825Sdfr	gss_addr->value  = &sin6->sin6_addr;
147178825Sdfr	*addr_type       = GSS_C_AF_INET6;
148178825Sdfr	break;
149178825Sdfr    }
150178825Sdfr#endif
151178825Sdfr    case AF_INET : {
152178825Sdfr	struct sockaddr_in *sin4 = (struct sockaddr_in *)sa;
153178825Sdfr
154178825Sdfr	gss_addr->length = 4;
155178825Sdfr	gss_addr->value  = &sin4->sin_addr;
156178825Sdfr	*addr_type       = GSS_C_AF_INET;
157178825Sdfr	break;
158178825Sdfr    }
159178825Sdfr    default :
160178825Sdfr	errx (1, "unknown address family %d", sa->sa_family);
161233294Sstas
162178825Sdfr    }
163178825Sdfr}
164178825Sdfr
165178825Sdfr/* end common stuff */
166178825Sdfr
167178825Sdfr#ifdef FTP_SERVER
168178825Sdfr
169178825Sdfrstatic int
170178825Sdfrgss_adat(void *app_data, void *buf, size_t len)
171178825Sdfr{
172178825Sdfr    char *p = NULL;
173178825Sdfr    gss_buffer_desc input_token, output_token;
174178825Sdfr    OM_uint32 maj_stat, min_stat;
175178825Sdfr    gss_name_t client_name;
176233294Sstas    struct gssapi_data *d = app_data;
177178825Sdfr    gss_channel_bindings_t bindings;
178178825Sdfr
179178825Sdfr    if (ftp_do_gss_bindings) {
180178825Sdfr	bindings = malloc(sizeof(*bindings));
181178825Sdfr	if (bindings == NULL)
182178825Sdfr	    errx(1, "out of memory");
183178825Sdfr
184178825Sdfr	sockaddr_to_gss_address (his_addr,
185178825Sdfr				 &bindings->initiator_addrtype,
186178825Sdfr				 &bindings->initiator_address);
187178825Sdfr	sockaddr_to_gss_address (ctrl_addr,
188178825Sdfr				 &bindings->acceptor_addrtype,
189178825Sdfr				 &bindings->acceptor_address);
190233294Sstas
191178825Sdfr	bindings->application_data.length = 0;
192178825Sdfr	bindings->application_data.value = NULL;
193178825Sdfr    } else
194178825Sdfr	bindings = GSS_C_NO_CHANNEL_BINDINGS;
195178825Sdfr
196178825Sdfr    input_token.value = buf;
197178825Sdfr    input_token.length = len;
198178825Sdfr
199178825Sdfr    maj_stat = gss_accept_sec_context (&min_stat,
200178825Sdfr				       &d->context_hdl,
201178825Sdfr				       GSS_C_NO_CREDENTIAL,
202178825Sdfr				       &input_token,
203178825Sdfr				       bindings,
204178825Sdfr				       &client_name,
205178825Sdfr				       NULL,
206178825Sdfr				       &output_token,
207178825Sdfr				       NULL,
208178825Sdfr				       NULL,
209178825Sdfr				       &d->delegated_cred_handle);
210178825Sdfr
211178825Sdfr    if (bindings != GSS_C_NO_CHANNEL_BINDINGS)
212178825Sdfr	free(bindings);
213178825Sdfr
214178825Sdfr    if(output_token.length) {
215178825Sdfr	if(base64_encode(output_token.value, output_token.length, &p) < 0) {
216178825Sdfr	    reply(535, "Out of memory base64-encoding.");
217178825Sdfr	    return -1;
218178825Sdfr	}
219178825Sdfr	gss_release_buffer(&min_stat, &output_token);
220178825Sdfr    }
221178825Sdfr    if(maj_stat == GSS_S_COMPLETE){
222233294Sstas	d->client_name = client_name;
223233294Sstas	client_name = GSS_C_NO_NAME;
224178825Sdfr	if(p)
225178825Sdfr	    reply(235, "ADAT=%s", p);
226178825Sdfr	else
227178825Sdfr	    reply(235, "ADAT Complete");
228178825Sdfr	sec_complete = 1;
229178825Sdfr
230178825Sdfr    } else if(maj_stat == GSS_S_CONTINUE_NEEDED) {
231178825Sdfr	if(p)
232178825Sdfr	    reply(335, "ADAT=%s", p);
233178825Sdfr	else
234178825Sdfr	    reply(335, "OK, need more data");
235178825Sdfr    } else {
236178825Sdfr	OM_uint32 new_stat;
237178825Sdfr	OM_uint32 msg_ctx = 0;
238178825Sdfr	gss_buffer_desc status_string;
239178825Sdfr	gss_display_status(&new_stat,
240178825Sdfr			   min_stat,
241178825Sdfr			   GSS_C_MECH_CODE,
242178825Sdfr			   GSS_C_NO_OID,
243178825Sdfr			   &msg_ctx,
244178825Sdfr			   &status_string);
245233294Sstas	syslog(LOG_ERR, "gss_accept_sec_context: %.*s",
246233294Sstas	       (int)status_string.length,
247178825Sdfr	       (char*)status_string.value);
248178825Sdfr	gss_release_buffer(&new_stat, &status_string);
249178825Sdfr	reply(431, "Security resource unavailable");
250178825Sdfr    }
251233294Sstas
252178825Sdfr    if (client_name)
253178825Sdfr	gss_release_name(&min_stat, &client_name);
254178825Sdfr    free(p);
255178825Sdfr    return 0;
256178825Sdfr}
257178825Sdfr
258233294Sstasint gssapi_userok(void*, char*);
259233294Sstasint gssapi_session(void*, char*);
260178825Sdfr
261178825Sdfrstruct sec_server_mech gss_server_mech = {
262178825Sdfr    "GSSAPI",
263233294Sstas    sizeof(struct gssapi_data),
264178825Sdfr    gss_init, /* init */
265178825Sdfr    NULL, /* end */
266178825Sdfr    gss_check_prot,
267178825Sdfr    gss_overhead,
268178825Sdfr    gss_encode,
269178825Sdfr    gss_decode,
270178825Sdfr    /* */
271178825Sdfr    NULL,
272178825Sdfr    gss_adat,
273178825Sdfr    NULL, /* pbsz */
274178825Sdfr    NULL, /* ccc */
275233294Sstas    gssapi_userok,
276233294Sstas    gssapi_session
277178825Sdfr};
278178825Sdfr
279178825Sdfr#else /* FTP_SERVER */
280178825Sdfr
281178825Sdfrextern struct sockaddr *hisctladdr, *myctladdr;
282178825Sdfr
283178825Sdfrstatic int
284178825Sdfrimport_name(const char *kname, const char *host, gss_name_t *target_name)
285178825Sdfr{
286178825Sdfr    OM_uint32 maj_stat, min_stat;
287178825Sdfr    gss_buffer_desc name;
288178825Sdfr    char *str;
289178825Sdfr
290178825Sdfr    name.length = asprintf(&str, "%s@%s", kname, host);
291178825Sdfr    if (str == NULL) {
292178825Sdfr	printf("Out of memory\n");
293178825Sdfr	return AUTH_ERROR;
294178825Sdfr    }
295178825Sdfr    name.value = str;
296178825Sdfr
297178825Sdfr    maj_stat = gss_import_name(&min_stat,
298178825Sdfr			       &name,
299178825Sdfr			       GSS_C_NT_HOSTBASED_SERVICE,
300178825Sdfr			       target_name);
301178825Sdfr    if (GSS_ERROR(maj_stat)) {
302178825Sdfr	OM_uint32 new_stat;
303178825Sdfr	OM_uint32 msg_ctx = 0;
304178825Sdfr	gss_buffer_desc status_string;
305233294Sstas
306178825Sdfr	gss_display_status(&new_stat,
307178825Sdfr			   min_stat,
308178825Sdfr			   GSS_C_MECH_CODE,
309178825Sdfr			   GSS_C_NO_OID,
310178825Sdfr			   &msg_ctx,
311178825Sdfr			   &status_string);
312233294Sstas	printf("Error importing name %.*s: %.*s\n",
313233294Sstas	       (int)name.length,
314178825Sdfr	       (char *)name.value,
315233294Sstas	       (int)status_string.length,
316178825Sdfr	       (char *)status_string.value);
317178825Sdfr	free(name.value);
318178825Sdfr	gss_release_buffer(&new_stat, &status_string);
319178825Sdfr	return AUTH_ERROR;
320178825Sdfr    }
321178825Sdfr    free(name.value);
322178825Sdfr    return 0;
323178825Sdfr}
324178825Sdfr
325178825Sdfrstatic int
326178825Sdfrgss_auth(void *app_data, char *host)
327178825Sdfr{
328233294Sstas
329178825Sdfr    OM_uint32 maj_stat, min_stat;
330178825Sdfr    gss_name_t target_name;
331178825Sdfr    gss_buffer_desc input, output_token;
332178825Sdfr    int context_established = 0;
333178825Sdfr    char *p;
334178825Sdfr    int n;
335178825Sdfr    gss_channel_bindings_t bindings;
336233294Sstas    struct gssapi_data *d = app_data;
337178825Sdfr    OM_uint32 mech_flags = GSS_C_MUTUAL_FLAG | GSS_C_SEQUENCE_FLAG;
338178825Sdfr
339178825Sdfr    const char *knames[] = { "ftp", "host", NULL }, **kname = knames;
340233294Sstas
341233294Sstas
342178825Sdfr    if(import_name(*kname++, host, &target_name))
343178825Sdfr	return AUTH_ERROR;
344178825Sdfr
345178825Sdfr    input.length = 0;
346178825Sdfr    input.value = NULL;
347178825Sdfr
348178825Sdfr    if (ftp_do_gss_bindings) {
349178825Sdfr	bindings = malloc(sizeof(*bindings));
350178825Sdfr	if (bindings == NULL)
351178825Sdfr	    errx(1, "out of memory");
352233294Sstas
353178825Sdfr	sockaddr_to_gss_address (myctladdr,
354178825Sdfr				 &bindings->initiator_addrtype,
355178825Sdfr				 &bindings->initiator_address);
356178825Sdfr	sockaddr_to_gss_address (hisctladdr,
357178825Sdfr				 &bindings->acceptor_addrtype,
358178825Sdfr				 &bindings->acceptor_address);
359233294Sstas
360178825Sdfr	bindings->application_data.length = 0;
361178825Sdfr	bindings->application_data.value = NULL;
362178825Sdfr    } else
363178825Sdfr	bindings = GSS_C_NO_CHANNEL_BINDINGS;
364178825Sdfr
365178825Sdfr    if (ftp_do_gss_delegate)
366178825Sdfr	mech_flags |= GSS_C_DELEG_FLAG;
367178825Sdfr
368178825Sdfr    while(!context_established) {
369178825Sdfr	maj_stat = gss_init_sec_context(&min_stat,
370178825Sdfr					GSS_C_NO_CREDENTIAL,
371178825Sdfr					&d->context_hdl,
372178825Sdfr					target_name,
373178825Sdfr					GSS_C_NO_OID,
374178825Sdfr                                        mech_flags,
375178825Sdfr					0,
376178825Sdfr					bindings,
377178825Sdfr					&input,
378178825Sdfr					NULL,
379178825Sdfr					&output_token,
380178825Sdfr					NULL,
381178825Sdfr					NULL);
382178825Sdfr	if (GSS_ERROR(maj_stat)) {
383178825Sdfr	    OM_uint32 new_stat;
384178825Sdfr	    OM_uint32 msg_ctx = 0;
385178825Sdfr	    gss_buffer_desc status_string;
386178825Sdfr
387178825Sdfr	    d->context_hdl = GSS_C_NO_CONTEXT;
388178825Sdfr
389178825Sdfr	    gss_release_name(&min_stat, &target_name);
390178825Sdfr
391178825Sdfr	    if(*kname != NULL) {
392178825Sdfr
393178825Sdfr		if(import_name(*kname++, host, &target_name)) {
394178825Sdfr		    if (bindings != GSS_C_NO_CHANNEL_BINDINGS)
395178825Sdfr			free(bindings);
396178825Sdfr		    return AUTH_ERROR;
397178825Sdfr		}
398178825Sdfr		continue;
399178825Sdfr	    }
400233294Sstas
401178825Sdfr	    if (bindings != GSS_C_NO_CHANNEL_BINDINGS)
402178825Sdfr		free(bindings);
403178825Sdfr
404178825Sdfr	    gss_display_status(&new_stat,
405178825Sdfr			       min_stat,
406178825Sdfr			       GSS_C_MECH_CODE,
407178825Sdfr			       GSS_C_NO_OID,
408178825Sdfr			       &msg_ctx,
409178825Sdfr			       &status_string);
410233294Sstas	    printf("Error initializing security context: %.*s\n",
411233294Sstas		   (int)status_string.length,
412178825Sdfr		   (char*)status_string.value);
413178825Sdfr	    gss_release_buffer(&new_stat, &status_string);
414178825Sdfr	    return AUTH_CONTINUE;
415178825Sdfr	}
416178825Sdfr
417178825Sdfr	if (input.value) {
418178825Sdfr	    free(input.value);
419178825Sdfr	    input.value = NULL;
420178825Sdfr	    input.length = 0;
421178825Sdfr	}
422178825Sdfr	if (output_token.length != 0) {
423178825Sdfr	    base64_encode(output_token.value, output_token.length, &p);
424178825Sdfr	    gss_release_buffer(&min_stat, &output_token);
425178825Sdfr	    n = command("ADAT %s", p);
426178825Sdfr	    free(p);
427178825Sdfr	}
428178825Sdfr	if (GSS_ERROR(maj_stat)) {
429178825Sdfr	    if (d->context_hdl != GSS_C_NO_CONTEXT)
430178825Sdfr		gss_delete_sec_context (&min_stat,
431178825Sdfr					&d->context_hdl,
432178825Sdfr					GSS_C_NO_BUFFER);
433178825Sdfr	    break;
434178825Sdfr	}
435178825Sdfr	if (maj_stat & GSS_S_CONTINUE_NEEDED) {
436178825Sdfr	    p = strstr(reply_string, "ADAT=");
437178825Sdfr	    if(p == NULL){
438178825Sdfr		printf("Error: expected ADAT in reply. got: %s\n",
439178825Sdfr		       reply_string);
440178825Sdfr		if (bindings != GSS_C_NO_CHANNEL_BINDINGS)
441178825Sdfr		    free(bindings);
442178825Sdfr		return AUTH_ERROR;
443178825Sdfr	    } else {
444178825Sdfr		p+=5;
445178825Sdfr		input.value = malloc(strlen(p));
446178825Sdfr		input.length = base64_decode(p, input.value);
447178825Sdfr	    }
448178825Sdfr	} else {
449178825Sdfr	    if(code != 235) {
450178825Sdfr		printf("Unrecognized response code: %d\n", code);
451178825Sdfr		if (bindings != GSS_C_NO_CHANNEL_BINDINGS)
452178825Sdfr		    free(bindings);
453178825Sdfr		return AUTH_ERROR;
454178825Sdfr	    }
455178825Sdfr	    context_established = 1;
456178825Sdfr	}
457178825Sdfr    }
458178825Sdfr
459178825Sdfr    gss_release_name(&min_stat, &target_name);
460178825Sdfr
461178825Sdfr    if (bindings != GSS_C_NO_CHANNEL_BINDINGS)
462178825Sdfr	free(bindings);
463178825Sdfr    if (input.value)
464178825Sdfr	free(input.value);
465178825Sdfr
466178825Sdfr    {
467178825Sdfr	gss_name_t targ_name;
468178825Sdfr
469178825Sdfr	maj_stat = gss_inquire_context(&min_stat,
470178825Sdfr				       d->context_hdl,
471178825Sdfr				       NULL,
472178825Sdfr				       &targ_name,
473178825Sdfr				       NULL,
474178825Sdfr				       NULL,
475178825Sdfr				       NULL,
476178825Sdfr				       NULL,
477178825Sdfr				       NULL);
478178825Sdfr	if (GSS_ERROR(maj_stat) == 0) {
479178825Sdfr	    gss_buffer_desc name;
480178825Sdfr	    maj_stat = gss_display_name (&min_stat,
481178825Sdfr					 targ_name,
482178825Sdfr					 &name,
483178825Sdfr					 NULL);
484178825Sdfr	    if (GSS_ERROR(maj_stat) == 0) {
485233294Sstas		printf("Authenticated to <%.*s>\n",
486233294Sstas			(int)name.length,
487233294Sstas			(char *)name.value);
488178825Sdfr		gss_release_buffer(&min_stat, &name);
489178825Sdfr	    }
490178825Sdfr	    gss_release_name(&min_stat, &targ_name);
491178825Sdfr	} else
492178825Sdfr	    printf("Failed to get gss name of peer.\n");
493233294Sstas    }
494178825Sdfr
495178825Sdfr
496178825Sdfr    return AUTH_OK;
497178825Sdfr}
498178825Sdfr
499178825Sdfrstruct sec_client_mech gss_client_mech = {
500178825Sdfr    "GSSAPI",
501233294Sstas    sizeof(struct gssapi_data),
502178825Sdfr    gss_init,
503178825Sdfr    gss_auth,
504178825Sdfr    NULL, /* end */
505178825Sdfr    gss_check_prot,
506178825Sdfr    gss_overhead,
507178825Sdfr    gss_encode,
508178825Sdfr    gss_decode,
509178825Sdfr};
510178825Sdfr
511178825Sdfr#endif /* FTP_SERVER */
512