1 | /* GSSAPI/krb5 support for FTP - loosely based on old krb4.c |
2 | * |
3 | * Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Högskolan |
4 | * (Royal Institute of Technology, Stockholm, Sweden). |
5 | * Copyright (c) 2004 - 2019 Daniel Stenberg |
6 | * All rights reserved. |
7 | * |
8 | * Redistribution and use in source and binary forms, with or without |
9 | * modification, are permitted provided that the following conditions |
10 | * are met: |
11 | * |
12 | * 1. Redistributions of source code must retain the above copyright |
13 | * notice, this list of conditions and the following disclaimer. |
14 | * |
15 | * 2. Redistributions in binary form must reproduce the above copyright |
16 | * notice, this list of conditions and the following disclaimer in the |
17 | * documentation and/or other materials provided with the distribution. |
18 | * |
19 | * 3. Neither the name of the Institute nor the names of its contributors |
20 | * may be used to endorse or promote products derived from this software |
21 | * without specific prior written permission. |
22 | * |
23 | * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND |
24 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
25 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
26 | * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE |
27 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
28 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
29 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
30 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
31 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
32 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
33 | * SUCH DAMAGE. */ |
34 | |
35 | #include "curl_setup.h" |
36 | |
37 | #if defined(HAVE_GSSAPI) && !defined(CURL_DISABLE_FTP) |
38 | |
39 | #ifdef HAVE_NETDB_H |
40 | #include <netdb.h> |
41 | #endif |
42 | |
43 | #include "urldata.h" |
44 | #include "curl_base64.h" |
45 | #include "ftp.h" |
46 | #include "curl_gssapi.h" |
47 | #include "sendf.h" |
48 | #include "curl_sec.h" |
49 | #include "warnless.h" |
50 | |
51 | /* The last 3 #include files should be in this order */ |
52 | #include "curl_printf.h" |
53 | #include "curl_memory.h" |
54 | #include "memdebug.h" |
55 | |
56 | static int |
57 | krb5_init(void *app_data) |
58 | { |
59 | gss_ctx_id_t *context = app_data; |
60 | /* Make sure our context is initialized for krb5_end. */ |
61 | *context = GSS_C_NO_CONTEXT; |
62 | return 0; |
63 | } |
64 | |
65 | static int |
66 | krb5_check_prot(void *app_data, int level) |
67 | { |
68 | (void)app_data; /* unused */ |
69 | if(level == PROT_CONFIDENTIAL) |
70 | return -1; |
71 | return 0; |
72 | } |
73 | |
74 | static int |
75 | krb5_decode(void *app_data, void *buf, int len, |
76 | int level UNUSED_PARAM, |
77 | struct connectdata *conn UNUSED_PARAM) |
78 | { |
79 | gss_ctx_id_t *context = app_data; |
80 | OM_uint32 maj, min; |
81 | gss_buffer_desc enc, dec; |
82 | |
83 | (void)level; |
84 | (void)conn; |
85 | |
86 | enc.value = buf; |
87 | enc.length = len; |
88 | maj = gss_unwrap(&min, *context, &enc, &dec, NULL, NULL); |
89 | if(maj != GSS_S_COMPLETE) { |
90 | if(len >= 4) |
91 | strcpy(buf, "599 " ); |
92 | return -1; |
93 | } |
94 | |
95 | memcpy(buf, dec.value, dec.length); |
96 | len = curlx_uztosi(dec.length); |
97 | gss_release_buffer(&min, &dec); |
98 | |
99 | return len; |
100 | } |
101 | |
102 | static int |
103 | krb5_overhead(void *app_data, int level, int len) |
104 | { |
105 | /* no arguments are used */ |
106 | (void)app_data; |
107 | (void)level; |
108 | (void)len; |
109 | return 0; |
110 | } |
111 | |
112 | static int |
113 | krb5_encode(void *app_data, const void *from, int length, int level, void **to) |
114 | { |
115 | gss_ctx_id_t *context = app_data; |
116 | gss_buffer_desc dec, enc; |
117 | OM_uint32 maj, min; |
118 | int state; |
119 | int len; |
120 | |
121 | /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal |
122 | * libraries modify the input buffer in gss_wrap() |
123 | */ |
124 | dec.value = (void *)from; |
125 | dec.length = length; |
126 | maj = gss_wrap(&min, *context, |
127 | level == PROT_PRIVATE, |
128 | GSS_C_QOP_DEFAULT, |
129 | &dec, &state, &enc); |
130 | |
131 | if(maj != GSS_S_COMPLETE) |
132 | return -1; |
133 | |
134 | /* malloc a new buffer, in case gss_release_buffer doesn't work as |
135 | expected */ |
136 | *to = malloc(enc.length); |
137 | if(!*to) |
138 | return -1; |
139 | memcpy(*to, enc.value, enc.length); |
140 | len = curlx_uztosi(enc.length); |
141 | gss_release_buffer(&min, &enc); |
142 | return len; |
143 | } |
144 | |
145 | static int |
146 | krb5_auth(void *app_data, struct connectdata *conn) |
147 | { |
148 | int ret = AUTH_OK; |
149 | char *p; |
150 | const char *host = conn->host.name; |
151 | ssize_t nread; |
152 | curl_socklen_t l = sizeof(conn->local_addr); |
153 | struct Curl_easy *data = conn->data; |
154 | CURLcode result; |
155 | const char *service = data->set.str[STRING_SERVICE_NAME] ? |
156 | data->set.str[STRING_SERVICE_NAME] : |
157 | "ftp" ; |
158 | const char *srv_host = "host" ; |
159 | gss_buffer_desc input_buffer, output_buffer, _gssresp, *gssresp; |
160 | OM_uint32 maj, min; |
161 | gss_name_t gssname; |
162 | gss_ctx_id_t *context = app_data; |
163 | struct gss_channel_bindings_struct chan; |
164 | size_t base64_sz = 0; |
165 | struct sockaddr_in **remote_addr = |
166 | (struct sockaddr_in **)&conn->ip_addr->ai_addr; |
167 | char *stringp; |
168 | |
169 | if(getsockname(conn->sock[FIRSTSOCKET], |
170 | (struct sockaddr *)&conn->local_addr, &l) < 0) |
171 | perror("getsockname()" ); |
172 | |
173 | chan.initiator_addrtype = GSS_C_AF_INET; |
174 | chan.initiator_address.length = l - 4; |
175 | chan.initiator_address.value = &conn->local_addr.sin_addr.s_addr; |
176 | chan.acceptor_addrtype = GSS_C_AF_INET; |
177 | chan.acceptor_address.length = l - 4; |
178 | chan.acceptor_address.value = &(*remote_addr)->sin_addr.s_addr; |
179 | chan.application_data.length = 0; |
180 | chan.application_data.value = NULL; |
181 | |
182 | /* this loop will execute twice (once for service, once for host) */ |
183 | for(;;) { |
184 | /* this really shouldn't be repeated here, but can't help it */ |
185 | if(service == srv_host) { |
186 | result = Curl_ftpsend(conn, "AUTH GSSAPI" ); |
187 | if(result) |
188 | return -2; |
189 | |
190 | if(Curl_GetFTPResponse(&nread, conn, NULL)) |
191 | return -1; |
192 | |
193 | if(data->state.buffer[0] != '3') |
194 | return -1; |
195 | } |
196 | |
197 | stringp = aprintf("%s@%s" , service, host); |
198 | if(!stringp) |
199 | return -2; |
200 | |
201 | input_buffer.value = stringp; |
202 | input_buffer.length = strlen(stringp); |
203 | maj = gss_import_name(&min, &input_buffer, GSS_C_NT_HOSTBASED_SERVICE, |
204 | &gssname); |
205 | free(stringp); |
206 | if(maj != GSS_S_COMPLETE) { |
207 | gss_release_name(&min, &gssname); |
208 | if(service == srv_host) { |
209 | failf(data, "Error importing service name %s@%s" , service, host); |
210 | return AUTH_ERROR; |
211 | } |
212 | service = srv_host; |
213 | continue; |
214 | } |
215 | /* We pass NULL as |output_name_type| to avoid a leak. */ |
216 | gss_display_name(&min, gssname, &output_buffer, NULL); |
217 | Curl_infof(data, "Trying against %s\n" , output_buffer.value); |
218 | gssresp = GSS_C_NO_BUFFER; |
219 | *context = GSS_C_NO_CONTEXT; |
220 | |
221 | do { |
222 | /* Release the buffer at each iteration to avoid leaking: the first time |
223 | we are releasing the memory from gss_display_name. The last item is |
224 | taken care by a final gss_release_buffer. */ |
225 | gss_release_buffer(&min, &output_buffer); |
226 | ret = AUTH_OK; |
227 | maj = Curl_gss_init_sec_context(data, |
228 | &min, |
229 | context, |
230 | gssname, |
231 | &Curl_krb5_mech_oid, |
232 | &chan, |
233 | gssresp, |
234 | &output_buffer, |
235 | TRUE, |
236 | NULL); |
237 | |
238 | if(gssresp) { |
239 | free(_gssresp.value); |
240 | gssresp = NULL; |
241 | } |
242 | |
243 | if(GSS_ERROR(maj)) { |
244 | Curl_infof(data, "Error creating security context\n" ); |
245 | ret = AUTH_ERROR; |
246 | break; |
247 | } |
248 | |
249 | if(output_buffer.length != 0) { |
250 | char *cmd; |
251 | |
252 | result = Curl_base64_encode(data, (char *)output_buffer.value, |
253 | output_buffer.length, &p, &base64_sz); |
254 | if(result) { |
255 | Curl_infof(data, "base64-encoding: %s\n" , |
256 | curl_easy_strerror(result)); |
257 | ret = AUTH_ERROR; |
258 | break; |
259 | } |
260 | |
261 | cmd = aprintf("ADAT %s" , p); |
262 | if(cmd) |
263 | result = Curl_ftpsend(conn, cmd); |
264 | else |
265 | result = CURLE_OUT_OF_MEMORY; |
266 | |
267 | free(p); |
268 | free(cmd); |
269 | |
270 | if(result) { |
271 | ret = -2; |
272 | break; |
273 | } |
274 | |
275 | if(Curl_GetFTPResponse(&nread, conn, NULL)) { |
276 | ret = -1; |
277 | break; |
278 | } |
279 | |
280 | if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') { |
281 | Curl_infof(data, "Server didn't accept auth data\n" ); |
282 | ret = AUTH_ERROR; |
283 | break; |
284 | } |
285 | |
286 | _gssresp.value = NULL; /* make sure it is initialized */ |
287 | p = data->state.buffer + 4; |
288 | p = strstr(p, "ADAT=" ); |
289 | if(p) { |
290 | result = Curl_base64_decode(p + 5, |
291 | (unsigned char **)&_gssresp.value, |
292 | &_gssresp.length); |
293 | if(result) { |
294 | failf(data, "base64-decoding: %s" , curl_easy_strerror(result)); |
295 | ret = AUTH_CONTINUE; |
296 | break; |
297 | } |
298 | } |
299 | |
300 | gssresp = &_gssresp; |
301 | } |
302 | } while(maj == GSS_S_CONTINUE_NEEDED); |
303 | |
304 | gss_release_name(&min, &gssname); |
305 | gss_release_buffer(&min, &output_buffer); |
306 | |
307 | if(gssresp) |
308 | free(_gssresp.value); |
309 | |
310 | if(ret == AUTH_OK || service == srv_host) |
311 | return ret; |
312 | |
313 | service = srv_host; |
314 | } |
315 | return ret; |
316 | } |
317 | |
318 | static void krb5_end(void *app_data) |
319 | { |
320 | OM_uint32 min; |
321 | gss_ctx_id_t *context = app_data; |
322 | if(*context != GSS_C_NO_CONTEXT) { |
323 | OM_uint32 maj = gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER); |
324 | (void)maj; |
325 | DEBUGASSERT(maj == GSS_S_COMPLETE); |
326 | } |
327 | } |
328 | |
329 | struct Curl_sec_client_mech Curl_krb5_client_mech = { |
330 | "GSSAPI" , |
331 | sizeof(gss_ctx_id_t), |
332 | krb5_init, |
333 | krb5_auth, |
334 | krb5_end, |
335 | krb5_check_prot, |
336 | krb5_overhead, |
337 | krb5_encode, |
338 | krb5_decode |
339 | }; |
340 | |
341 | #endif /* HAVE_GSSAPI && !CURL_DISABLE_FTP */ |
342 | |