• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 - 2021 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_krb5.h"
49 #include "warnless.h"
50 #include "non-ascii.h"
51 #include "strcase.h"
52 #include "strdup.h"
53 
54 /* The last 3 #include files should be in this order */
55 #include "curl_printf.h"
56 #include "curl_memory.h"
57 #include "memdebug.h"
58 
ftpsend(struct Curl_easy * data,struct connectdata * conn,const char * cmd)59 static CURLcode ftpsend(struct Curl_easy *data, struct connectdata *conn,
60                         const char *cmd)
61 {
62   ssize_t bytes_written;
63 #define SBUF_SIZE 1024
64   char s[SBUF_SIZE];
65   size_t write_len;
66   char *sptr = s;
67   CURLcode result = CURLE_OK;
68 #ifdef HAVE_GSSAPI
69   enum protection_level data_sec = conn->data_prot;
70 #endif
71 
72   if(!cmd)
73     return CURLE_BAD_FUNCTION_ARGUMENT;
74 
75   write_len = strlen(cmd);
76   if(!write_len || write_len > (sizeof(s) -3))
77     return CURLE_BAD_FUNCTION_ARGUMENT;
78 
79   memcpy(&s, cmd, write_len);
80   strcpy(&s[write_len], "\r\n"); /* append a trailing CRLF */
81   write_len += 2;
82   bytes_written = 0;
83 
84   result = Curl_convert_to_network(data, s, write_len);
85   /* Curl_convert_to_network calls failf if unsuccessful */
86   if(result)
87     return result;
88 
89   for(;;) {
90 #ifdef HAVE_GSSAPI
91     conn->data_prot = PROT_CMD;
92 #endif
93     result = Curl_write(data, conn->sock[FIRSTSOCKET], sptr, write_len,
94                         &bytes_written);
95 #ifdef HAVE_GSSAPI
96     DEBUGASSERT(data_sec > PROT_NONE && data_sec < PROT_LAST);
97     conn->data_prot = data_sec;
98 #endif
99 
100     if(result)
101       break;
102 
103     Curl_debug(data, CURLINFO_HEADER_OUT, sptr, (size_t)bytes_written);
104 
105     if(bytes_written != (ssize_t)write_len) {
106       write_len -= bytes_written;
107       sptr += bytes_written;
108     }
109     else
110       break;
111   }
112 
113   return result;
114 }
115 
116 static int
krb5_init(void * app_data)117 krb5_init(void *app_data)
118 {
119   gss_ctx_id_t *context = app_data;
120   /* Make sure our context is initialized for krb5_end. */
121   *context = GSS_C_NO_CONTEXT;
122   return 0;
123 }
124 
125 static int
krb5_check_prot(void * app_data,int level)126 krb5_check_prot(void *app_data, int level)
127 {
128   (void)app_data; /* unused */
129   if(level == PROT_CONFIDENTIAL)
130     return -1;
131   return 0;
132 }
133 
134 static int
krb5_decode(void * app_data,void * buf,int len,int level UNUSED_PARAM,struct connectdata * conn UNUSED_PARAM)135 krb5_decode(void *app_data, void *buf, int len,
136             int level UNUSED_PARAM,
137             struct connectdata *conn UNUSED_PARAM)
138 {
139   gss_ctx_id_t *context = app_data;
140   OM_uint32 maj, min;
141   gss_buffer_desc enc, dec;
142 
143   (void)level;
144   (void)conn;
145 
146   enc.value = buf;
147   enc.length = len;
148   maj = gss_unwrap(&min, *context, &enc, &dec, NULL, NULL);
149   if(maj != GSS_S_COMPLETE)
150     return -1;
151 
152   memcpy(buf, dec.value, dec.length);
153   len = curlx_uztosi(dec.length);
154   gss_release_buffer(&min, &dec);
155 
156   return len;
157 }
158 
159 static int
krb5_encode(void * app_data,const void * from,int length,int level,void ** to)160 krb5_encode(void *app_data, const void *from, int length, int level, void **to)
161 {
162   gss_ctx_id_t *context = app_data;
163   gss_buffer_desc dec, enc;
164   OM_uint32 maj, min;
165   int state;
166   int len;
167 
168   /* NOTE that the cast is safe, neither of the krb5, gnu gss and heimdal
169    * libraries modify the input buffer in gss_wrap()
170    */
171   dec.value = (void *)from;
172   dec.length = length;
173   maj = gss_wrap(&min, *context,
174                  level == PROT_PRIVATE,
175                  GSS_C_QOP_DEFAULT,
176                  &dec, &state, &enc);
177 
178   if(maj != GSS_S_COMPLETE)
179     return -1;
180 
181   /* malloc a new buffer, in case gss_release_buffer doesn't work as
182      expected */
183   *to = malloc(enc.length);
184   if(!*to)
185     return -1;
186   memcpy(*to, enc.value, enc.length);
187   len = curlx_uztosi(enc.length);
188   gss_release_buffer(&min, &enc);
189   return len;
190 }
191 
192 static int
krb5_auth(void * app_data,struct Curl_easy * data,struct connectdata * conn)193 krb5_auth(void *app_data, struct Curl_easy *data, struct connectdata *conn)
194 {
195   int ret = AUTH_OK;
196   char *p;
197   const char *host = conn->host.name;
198   ssize_t nread;
199   curl_socklen_t l = sizeof(conn->local_addr);
200   CURLcode result;
201   const char *service = data->set.str[STRING_SERVICE_NAME] ?
202                         data->set.str[STRING_SERVICE_NAME] :
203                         "ftp";
204   const char *srv_host = "host";
205   gss_buffer_desc input_buffer, output_buffer, _gssresp, *gssresp;
206   OM_uint32 maj, min;
207   gss_name_t gssname;
208   gss_ctx_id_t *context = app_data;
209   struct gss_channel_bindings_struct chan;
210   size_t base64_sz = 0;
211   struct sockaddr_in **remote_addr =
212     (struct sockaddr_in **)&conn->ip_addr->ai_addr;
213   char *stringp;
214 
215   if(getsockname(conn->sock[FIRSTSOCKET],
216                  (struct sockaddr *)&conn->local_addr, &l) < 0)
217     perror("getsockname()");
218 
219   chan.initiator_addrtype = GSS_C_AF_INET;
220   chan.initiator_address.length = l - 4;
221   chan.initiator_address.value = &conn->local_addr.sin_addr.s_addr;
222   chan.acceptor_addrtype = GSS_C_AF_INET;
223   chan.acceptor_address.length = l - 4;
224   chan.acceptor_address.value = &(*remote_addr)->sin_addr.s_addr;
225   chan.application_data.length = 0;
226   chan.application_data.value = NULL;
227 
228   /* this loop will execute twice (once for service, once for host) */
229   for(;;) {
230     /* this really shouldn't be repeated here, but can't help it */
231     if(service == srv_host) {
232       result = ftpsend(data, conn, "AUTH GSSAPI");
233       if(result)
234         return -2;
235 
236       if(Curl_GetFTPResponse(data, &nread, NULL))
237         return -1;
238 
239       if(data->state.buffer[0] != '3')
240         return -1;
241     }
242 
243     stringp = aprintf("%s@%s", service, host);
244     if(!stringp)
245       return -2;
246 
247     input_buffer.value = stringp;
248     input_buffer.length = strlen(stringp);
249     maj = gss_import_name(&min, &input_buffer, GSS_C_NT_HOSTBASED_SERVICE,
250                           &gssname);
251     free(stringp);
252     if(maj != GSS_S_COMPLETE) {
253       gss_release_name(&min, &gssname);
254       if(service == srv_host) {
255         failf(data, "Error importing service name %s@%s", service, host);
256         return AUTH_ERROR;
257       }
258       service = srv_host;
259       continue;
260     }
261     /* We pass NULL as |output_name_type| to avoid a leak. */
262     gss_display_name(&min, gssname, &output_buffer, NULL);
263     infof(data, "Trying against %s", output_buffer.value);
264     gssresp = GSS_C_NO_BUFFER;
265     *context = GSS_C_NO_CONTEXT;
266 
267     do {
268       /* Release the buffer at each iteration to avoid leaking: the first time
269          we are releasing the memory from gss_display_name. The last item is
270          taken care by a final gss_release_buffer. */
271       gss_release_buffer(&min, &output_buffer);
272       ret = AUTH_OK;
273       maj = Curl_gss_init_sec_context(data,
274                                       &min,
275                                       context,
276                                       gssname,
277                                       &Curl_krb5_mech_oid,
278                                       &chan,
279                                       gssresp,
280                                       &output_buffer,
281                                       TRUE,
282                                       NULL);
283 
284       if(gssresp) {
285         free(_gssresp.value);
286         gssresp = NULL;
287       }
288 
289       if(GSS_ERROR(maj)) {
290         infof(data, "Error creating security context");
291         ret = AUTH_ERROR;
292         break;
293       }
294 
295       if(output_buffer.length) {
296         char *cmd;
297 
298         result = Curl_base64_encode(data, (char *)output_buffer.value,
299                                     output_buffer.length, &p, &base64_sz);
300         if(result) {
301           infof(data, "base64-encoding: %s", curl_easy_strerror(result));
302           ret = AUTH_ERROR;
303           break;
304         }
305 
306         cmd = aprintf("ADAT %s", p);
307         if(cmd)
308           result = ftpsend(data, conn, cmd);
309         else
310           result = CURLE_OUT_OF_MEMORY;
311 
312         free(p);
313         free(cmd);
314 
315         if(result) {
316           ret = -2;
317           break;
318         }
319 
320         if(Curl_GetFTPResponse(data, &nread, NULL)) {
321           ret = -1;
322           break;
323         }
324 
325         if(data->state.buffer[0] != '2' && data->state.buffer[0] != '3') {
326           infof(data, "Server didn't accept auth data");
327           ret = AUTH_ERROR;
328           break;
329         }
330 
331         _gssresp.value = NULL; /* make sure it is initialized */
332         p = data->state.buffer + 4;
333         p = strstr(p, "ADAT=");
334         if(p) {
335           result = Curl_base64_decode(p + 5,
336                                       (unsigned char **)&_gssresp.value,
337                                       &_gssresp.length);
338           if(result) {
339             failf(data, "base64-decoding: %s", curl_easy_strerror(result));
340             ret = AUTH_CONTINUE;
341             break;
342           }
343         }
344 
345         gssresp = &_gssresp;
346       }
347     } while(maj == GSS_S_CONTINUE_NEEDED);
348 
349     gss_release_name(&min, &gssname);
350     gss_release_buffer(&min, &output_buffer);
351 
352     if(gssresp)
353       free(_gssresp.value);
354 
355     if(ret == AUTH_OK || service == srv_host)
356       return ret;
357 
358     service = srv_host;
359   }
360   return ret;
361 }
362 
krb5_end(void * app_data)363 static void krb5_end(void *app_data)
364 {
365     OM_uint32 min;
366     gss_ctx_id_t *context = app_data;
367     if(*context != GSS_C_NO_CONTEXT) {
368       OM_uint32 maj = gss_delete_sec_context(&min, context, GSS_C_NO_BUFFER);
369       (void)maj;
370       DEBUGASSERT(maj == GSS_S_COMPLETE);
371     }
372 }
373 
374 static struct Curl_sec_client_mech Curl_krb5_client_mech = {
375   "GSSAPI",
376   sizeof(gss_ctx_id_t),
377   krb5_init,
378   krb5_auth,
379   krb5_end,
380   krb5_check_prot,
381 
382   krb5_encode,
383   krb5_decode
384 };
385 
386 static const struct {
387   enum protection_level level;
388   const char *name;
389 } level_names[] = {
390   { PROT_CLEAR, "clear" },
391   { PROT_SAFE, "safe" },
392   { PROT_CONFIDENTIAL, "confidential" },
393   { PROT_PRIVATE, "private" }
394 };
395 
396 static enum protection_level
name_to_level(const char * name)397 name_to_level(const char *name)
398 {
399   int i;
400   for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++)
401     if(curl_strequal(name, level_names[i].name))
402       return level_names[i].level;
403   return PROT_NONE;
404 }
405 
406 /* Convert a protocol |level| to its char representation.
407    We take an int to catch programming mistakes. */
level_to_char(int level)408 static char level_to_char(int level)
409 {
410   switch(level) {
411   case PROT_CLEAR:
412     return 'C';
413   case PROT_SAFE:
414     return 'S';
415   case PROT_CONFIDENTIAL:
416     return 'E';
417   case PROT_PRIVATE:
418     return 'P';
419   case PROT_CMD:
420     /* Fall through */
421   default:
422     /* Those 2 cases should not be reached! */
423     break;
424   }
425   DEBUGASSERT(0);
426   /* Default to the most secure alternative. */
427   return 'P';
428 }
429 
430 /* Send an FTP command defined by |message| and the optional arguments. The
431    function returns the ftp_code. If an error occurs, -1 is returned. */
ftp_send_command(struct Curl_easy * data,const char * message,...)432 static int ftp_send_command(struct Curl_easy *data, const char *message, ...)
433 {
434   int ftp_code;
435   ssize_t nread = 0;
436   va_list args;
437   char print_buffer[50];
438 
439   va_start(args, message);
440   mvsnprintf(print_buffer, sizeof(print_buffer), message, args);
441   va_end(args);
442 
443   if(ftpsend(data, data->conn, print_buffer)) {
444     ftp_code = -1;
445   }
446   else {
447     if(Curl_GetFTPResponse(data, &nread, &ftp_code))
448       ftp_code = -1;
449   }
450 
451   (void)nread; /* Unused */
452   return ftp_code;
453 }
454 
455 /* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode
456    saying whether an error occurred or CURLE_OK if |len| was read. */
457 static CURLcode
socket_read(curl_socket_t fd,void * to,size_t len)458 socket_read(curl_socket_t fd, void *to, size_t len)
459 {
460   char *to_p = to;
461   CURLcode result;
462   ssize_t nread = 0;
463 
464   while(len > 0) {
465     result = Curl_read_plain(fd, to_p, len, &nread);
466     if(!result) {
467       len -= nread;
468       to_p += nread;
469     }
470     else {
471       if(result == CURLE_AGAIN)
472         continue;
473       return result;
474     }
475   }
476   return CURLE_OK;
477 }
478 
479 
480 /* Write |len| bytes from the buffer |to| to the socket |fd|. Return a
481    CURLcode saying whether an error occurred or CURLE_OK if |len| was
482    written. */
483 static CURLcode
socket_write(struct Curl_easy * data,curl_socket_t fd,const void * to,size_t len)484 socket_write(struct Curl_easy *data, curl_socket_t fd, const void *to,
485              size_t len)
486 {
487   const char *to_p = to;
488   CURLcode result;
489   ssize_t written;
490 
491   while(len > 0) {
492     result = Curl_write_plain(data, fd, to_p, len, &written);
493     if(!result) {
494       len -= written;
495       to_p += written;
496     }
497     else {
498       if(result == CURLE_AGAIN)
499         continue;
500       return result;
501     }
502   }
503   return CURLE_OK;
504 }
505 
read_data(struct connectdata * conn,curl_socket_t fd,struct krb5buffer * buf)506 static CURLcode read_data(struct connectdata *conn,
507                           curl_socket_t fd,
508                           struct krb5buffer *buf)
509 {
510   int len;
511   CURLcode result;
512   int nread;
513 
514   result = socket_read(fd, &len, sizeof(len));
515   if(result)
516     return result;
517 
518   if(len) {
519     /* only realloc if there was a length */
520     len = ntohl(len);
521     if(len > CURL_MAX_INPUT_LENGTH)
522       len = 0;
523     else
524       buf->data = Curl_saferealloc(buf->data, len);
525   }
526   if(!len || !buf->data)
527     return CURLE_OUT_OF_MEMORY;
528 
529   result = socket_read(fd, buf->data, len);
530   if(result)
531     return result;
532   nread = conn->mech->decode(conn->app_data, buf->data, len,
533                              conn->data_prot, conn);
534   if(nread < 0)
535     return CURLE_RECV_ERROR;
536   buf->size = (size_t)nread;
537   buf->index = 0;
538   return CURLE_OK;
539 }
540 
541 static size_t
buffer_read(struct krb5buffer * buf,void * data,size_t len)542 buffer_read(struct krb5buffer *buf, void *data, size_t len)
543 {
544   if(buf->size - buf->index < len)
545     len = buf->size - buf->index;
546   memcpy(data, (char *)buf->data + buf->index, len);
547   buf->index += len;
548   return len;
549 }
550 
551 /* Matches Curl_recv signature */
sec_recv(struct Curl_easy * data,int sockindex,char * buffer,size_t len,CURLcode * err)552 static ssize_t sec_recv(struct Curl_easy *data, int sockindex,
553                         char *buffer, size_t len, CURLcode *err)
554 {
555   size_t bytes_read;
556   size_t total_read = 0;
557   struct connectdata *conn = data->conn;
558   curl_socket_t fd = conn->sock[sockindex];
559 
560   *err = CURLE_OK;
561 
562   /* Handle clear text response. */
563   if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR)
564     return sread(fd, buffer, len);
565 
566   if(conn->in_buffer.eof_flag) {
567     conn->in_buffer.eof_flag = 0;
568     return 0;
569   }
570 
571   bytes_read = buffer_read(&conn->in_buffer, buffer, len);
572   len -= bytes_read;
573   total_read += bytes_read;
574   buffer += bytes_read;
575 
576   while(len > 0) {
577     if(read_data(conn, fd, &conn->in_buffer))
578       return -1;
579     if(conn->in_buffer.size == 0) {
580       if(bytes_read > 0)
581         conn->in_buffer.eof_flag = 1;
582       return bytes_read;
583     }
584     bytes_read = buffer_read(&conn->in_buffer, buffer, len);
585     len -= bytes_read;
586     total_read += bytes_read;
587     buffer += bytes_read;
588   }
589   return total_read;
590 }
591 
592 /* Send |length| bytes from |from| to the |fd| socket taking care of encoding
593    and negotiating with the server. |from| can be NULL. */
do_sec_send(struct Curl_easy * data,struct connectdata * conn,curl_socket_t fd,const char * from,int length)594 static void do_sec_send(struct Curl_easy *data, struct connectdata *conn,
595                         curl_socket_t fd, const char *from, int length)
596 {
597   int bytes, htonl_bytes; /* 32-bit integers for htonl */
598   char *buffer = NULL;
599   char *cmd_buffer;
600   size_t cmd_size = 0;
601   CURLcode error;
602   enum protection_level prot_level = conn->data_prot;
603   bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE;
604 
605   DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST);
606 
607   if(iscmd) {
608     if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5))
609       prot_level = PROT_PRIVATE;
610     else
611       prot_level = conn->command_prot;
612   }
613   bytes = conn->mech->encode(conn->app_data, from, length, prot_level,
614                              (void **)&buffer);
615   if(!buffer || bytes <= 0)
616     return; /* error */
617 
618   if(iscmd) {
619     error = Curl_base64_encode(data, buffer, curlx_sitouz(bytes),
620                                &cmd_buffer, &cmd_size);
621     if(error) {
622       free(buffer);
623       return; /* error */
624     }
625     if(cmd_size > 0) {
626       static const char *enc = "ENC ";
627       static const char *mic = "MIC ";
628       if(prot_level == PROT_PRIVATE)
629         socket_write(data, fd, enc, 4);
630       else
631         socket_write(data, fd, mic, 4);
632 
633       socket_write(data, fd, cmd_buffer, cmd_size);
634       socket_write(data, fd, "\r\n", 2);
635       infof(data, "Send: %s%s", prot_level == PROT_PRIVATE?enc:mic,
636             cmd_buffer);
637       free(cmd_buffer);
638     }
639   }
640   else {
641     htonl_bytes = htonl(bytes);
642     socket_write(data, fd, &htonl_bytes, sizeof(htonl_bytes));
643     socket_write(data, fd, buffer, curlx_sitouz(bytes));
644   }
645   free(buffer);
646 }
647 
sec_write(struct Curl_easy * data,struct connectdata * conn,curl_socket_t fd,const char * buffer,size_t length)648 static ssize_t sec_write(struct Curl_easy *data, struct connectdata *conn,
649                          curl_socket_t fd, const char *buffer, size_t length)
650 {
651   ssize_t tx = 0, len = conn->buffer_size;
652 
653   if(len <= 0)
654     len = length;
655   while(length) {
656     if(length < (size_t)len)
657       len = length;
658 
659     do_sec_send(data, conn, fd, buffer, curlx_sztosi(len));
660     length -= len;
661     buffer += len;
662     tx += len;
663   }
664   return tx;
665 }
666 
667 /* Matches Curl_send signature */
sec_send(struct Curl_easy * data,int sockindex,const void * buffer,size_t len,CURLcode * err)668 static ssize_t sec_send(struct Curl_easy *data, int sockindex,
669                         const void *buffer, size_t len, CURLcode *err)
670 {
671   struct connectdata *conn = data->conn;
672   curl_socket_t fd = conn->sock[sockindex];
673   *err = CURLE_OK;
674   return sec_write(data, conn, fd, buffer, len);
675 }
676 
Curl_sec_read_msg(struct Curl_easy * data,struct connectdata * conn,char * buffer,enum protection_level level)677 int Curl_sec_read_msg(struct Curl_easy *data, struct connectdata *conn,
678                       char *buffer, enum protection_level level)
679 {
680   /* decoded_len should be size_t or ssize_t but conn->mech->decode returns an
681      int */
682   int decoded_len;
683   char *buf;
684   int ret_code = 0;
685   size_t decoded_sz = 0;
686   CURLcode error;
687 
688   (void) data;
689 
690   if(!conn->mech)
691     /* not inititalized, return error */
692     return -1;
693 
694   DEBUGASSERT(level > PROT_NONE && level < PROT_LAST);
695 
696   error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz);
697   if(error || decoded_sz == 0)
698     return -1;
699 
700   if(decoded_sz > (size_t)INT_MAX) {
701     free(buf);
702     return -1;
703   }
704   decoded_len = curlx_uztosi(decoded_sz);
705 
706   decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len,
707                                    level, conn);
708   if(decoded_len <= 0) {
709     free(buf);
710     return -1;
711   }
712 
713   {
714     buf[decoded_len] = '\n';
715     Curl_debug(data, CURLINFO_HEADER_IN, buf, decoded_len + 1);
716   }
717 
718   buf[decoded_len] = '\0';
719   if(decoded_len <= 3)
720     /* suspiciously short */
721     return 0;
722 
723   if(buf[3] != '-')
724     /* safe to ignore return code */
725     (void)sscanf(buf, "%d", &ret_code);
726 
727   if(buf[decoded_len - 1] == '\n')
728     buf[decoded_len - 1] = '\0';
729   strcpy(buffer, buf);
730   free(buf);
731   return ret_code;
732 }
733 
sec_set_protection_level(struct Curl_easy * data)734 static int sec_set_protection_level(struct Curl_easy *data)
735 {
736   int code;
737   struct connectdata *conn = data->conn;
738   enum protection_level level = conn->request_data_prot;
739 
740   DEBUGASSERT(level > PROT_NONE && level < PROT_LAST);
741 
742   if(!conn->sec_complete) {
743     infof(data, "Trying to change the protection level after the"
744                 " completion of the data exchange.");
745     return -1;
746   }
747 
748   /* Bail out if we try to set up the same level */
749   if(conn->data_prot == level)
750     return 0;
751 
752   if(level) {
753     char *pbsz;
754     unsigned int buffer_size = 1 << 20; /* 1048576 */
755 
756     code = ftp_send_command(data, "PBSZ %u", buffer_size);
757     if(code < 0)
758       return -1;
759 
760     if(code/100 != 2) {
761       failf(data, "Failed to set the protection's buffer size.");
762       return -1;
763     }
764     conn->buffer_size = buffer_size;
765 
766     pbsz = strstr(data->state.buffer, "PBSZ=");
767     if(pbsz) {
768       /* ignore return code, use default value if it fails */
769       (void)sscanf(pbsz, "PBSZ=%u", &buffer_size);
770       if(buffer_size < conn->buffer_size)
771         conn->buffer_size = buffer_size;
772     }
773   }
774 
775   /* Now try to negiociate the protection level. */
776   code = ftp_send_command(data, "PROT %c", level_to_char(level));
777 
778   if(code < 0)
779     return -1;
780 
781   if(code/100 != 2) {
782     failf(data, "Failed to set the protection level.");
783     return -1;
784   }
785 
786   conn->data_prot = level;
787   if(level == PROT_PRIVATE)
788     conn->command_prot = level;
789 
790   return 0;
791 }
792 
793 int
Curl_sec_request_prot(struct connectdata * conn,const char * level)794 Curl_sec_request_prot(struct connectdata *conn, const char *level)
795 {
796   enum protection_level l = name_to_level(level);
797   if(l == PROT_NONE)
798     return -1;
799   DEBUGASSERT(l > PROT_NONE && l < PROT_LAST);
800   conn->request_data_prot = l;
801   return 0;
802 }
803 
choose_mech(struct Curl_easy * data,struct connectdata * conn)804 static CURLcode choose_mech(struct Curl_easy *data, struct connectdata *conn)
805 {
806   int ret;
807   void *tmp_allocation;
808   const struct Curl_sec_client_mech *mech = &Curl_krb5_client_mech;
809 
810   tmp_allocation = realloc(conn->app_data, mech->size);
811   if(!tmp_allocation) {
812     failf(data, "Failed realloc of size %zu", mech->size);
813     mech = NULL;
814     return CURLE_OUT_OF_MEMORY;
815   }
816   conn->app_data = tmp_allocation;
817 
818   if(mech->init) {
819     ret = mech->init(conn->app_data);
820     if(ret) {
821       infof(data, "Failed initialization for %s. Skipping it.",
822             mech->name);
823       return CURLE_FAILED_INIT;
824     }
825   }
826 
827   infof(data, "Trying mechanism %s...", mech->name);
828   ret = ftp_send_command(data, "AUTH %s", mech->name);
829   if(ret < 0)
830     return CURLE_COULDNT_CONNECT;
831 
832   if(ret/100 != 3) {
833     switch(ret) {
834     case 504:
835       infof(data, "Mechanism %s is not supported by the server (server "
836             "returned ftp code: 504).", mech->name);
837       break;
838     case 534:
839       infof(data, "Mechanism %s was rejected by the server (server returned "
840             "ftp code: 534).", mech->name);
841       break;
842     default:
843       if(ret/100 == 5) {
844         infof(data, "server does not support the security extensions");
845         return CURLE_USE_SSL_FAILED;
846       }
847       break;
848     }
849     return CURLE_LOGIN_DENIED;
850   }
851 
852   /* Authenticate */
853   ret = mech->auth(conn->app_data, data, conn);
854 
855   if(ret != AUTH_CONTINUE) {
856     if(ret != AUTH_OK) {
857       /* Mechanism has dumped the error to stderr, don't error here. */
858       return CURLE_USE_SSL_FAILED;
859     }
860     DEBUGASSERT(ret == AUTH_OK);
861 
862     conn->mech = mech;
863     conn->sec_complete = 1;
864     conn->recv[FIRSTSOCKET] = sec_recv;
865     conn->send[FIRSTSOCKET] = sec_send;
866     conn->recv[SECONDARYSOCKET] = sec_recv;
867     conn->send[SECONDARYSOCKET] = sec_send;
868     conn->command_prot = PROT_SAFE;
869     /* Set the requested protection level */
870     /* BLOCKING */
871     (void)sec_set_protection_level(data);
872   }
873 
874   return CURLE_OK;
875 }
876 
877 CURLcode
Curl_sec_login(struct Curl_easy * data,struct connectdata * conn)878 Curl_sec_login(struct Curl_easy *data, struct connectdata *conn)
879 {
880   return choose_mech(data, conn);
881 }
882 
883 
884 void
Curl_sec_end(struct connectdata * conn)885 Curl_sec_end(struct connectdata *conn)
886 {
887   if(conn->mech != NULL && conn->mech->end)
888     conn->mech->end(conn->app_data);
889   free(conn->app_data);
890   conn->app_data = NULL;
891   if(conn->in_buffer.data) {
892     free(conn->in_buffer.data);
893     conn->in_buffer.data = NULL;
894     conn->in_buffer.size = 0;
895     conn->in_buffer.index = 0;
896     conn->in_buffer.eof_flag = 0;
897   }
898   conn->sec_complete = 0;
899   conn->data_prot = PROT_CLEAR;
900   conn->mech = NULL;
901 }
902 
903 #endif /* HAVE_GSSAPI && !CURL_DISABLE_FTP */
904