1/* This source code was modified by Martin Hedenfalk <mhe@stacken.kth.se> for 2 * use in Curl. His latest changes were done 2000-09-18. 3 * 4 * It has since been patched and modified a lot by Daniel Stenberg 5 * <daniel@haxx.se> to make it better applied to curl conditions, and to make 6 * it not use globals, pollute name space and more. This source code awaits a 7 * rewrite to work around the paragraph 2 in the BSD licenses as explained 8 * below. 9 * 10 * Copyright (c) 1998, 1999, 2017 Kungliga Tekniska H�gskolan 11 * (Royal Institute of Technology, Stockholm, Sweden). 12 * 13 * Copyright (C) 2001 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. 14 * 15 * All rights reserved. 16 * 17 * Redistribution and use in source and binary forms, with or without 18 * modification, are permitted provided that the following conditions 19 * are met: 20 * 21 * 1. Redistributions of source code must retain the above copyright 22 * notice, this list of conditions and the following disclaimer. 23 * 24 * 2. Redistributions in binary form must reproduce the above copyright 25 * notice, this list of conditions and the following disclaimer in the 26 * documentation and/or other materials provided with the distribution. 27 * 28 * 3. Neither the name of the Institute nor the names of its contributors 29 * may be used to endorse or promote products derived from this software 30 * without specific prior written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 33 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 34 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 35 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 36 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 37 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 38 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 39 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 40 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 41 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 42 * SUCH DAMAGE. */ 43 44#include "curl_setup.h" 45 46#ifndef CURL_DISABLE_FTP 47#ifdef HAVE_GSSAPI 48 49#ifdef HAVE_NETDB_H 50#include <netdb.h> 51#endif 52 53#include <limits.h> 54 55#include "urldata.h" 56#include "curl_base64.h" 57#include "curl_memory.h" 58#include "curl_sec.h" 59#include "ftp.h" 60#include "sendf.h" 61#include "strcase.h" 62#include "warnless.h" 63#include "strdup.h" 64/* The last #include file should be: */ 65#include "memdebug.h" 66 67static const struct { 68 enum protection_level level; 69 const char *name; 70} level_names[] = { 71 { PROT_CLEAR, "clear" }, 72 { PROT_SAFE, "safe" }, 73 { PROT_CONFIDENTIAL, "confidential" }, 74 { PROT_PRIVATE, "private" } 75}; 76 77static enum protection_level 78name_to_level(const char *name) 79{ 80 int i; 81 for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) 82 if(checkprefix(name, level_names[i].name)) 83 return level_names[i].level; 84 return PROT_NONE; 85} 86 87/* Convert a protocol |level| to its char representation. 88 We take an int to catch programming mistakes. */ 89static char level_to_char(int level) 90{ 91 switch(level) { 92 case PROT_CLEAR: 93 return 'C'; 94 case PROT_SAFE: 95 return 'S'; 96 case PROT_CONFIDENTIAL: 97 return 'E'; 98 case PROT_PRIVATE: 99 return 'P'; 100 case PROT_CMD: 101 /* Fall through */ 102 default: 103 /* Those 2 cases should not be reached! */ 104 break; 105 } 106 DEBUGASSERT(0); 107 /* Default to the most secure alternative. */ 108 return 'P'; 109} 110 111/* Send an FTP command defined by |message| and the optional arguments. The 112 function returns the ftp_code. If an error occurs, -1 is returned. */ 113static int ftp_send_command(struct connectdata *conn, const char *message, ...) 114{ 115 int ftp_code; 116 ssize_t nread = 0; 117 va_list args; 118 char print_buffer[50]; 119 120 va_start(args, message); 121 vsnprintf(print_buffer, sizeof(print_buffer), message, args); 122 va_end(args); 123 124 if(Curl_ftpsend(conn, print_buffer)) { 125 ftp_code = -1; 126 } 127 else { 128 if(Curl_GetFTPResponse(&nread, conn, &ftp_code)) 129 ftp_code = -1; 130 } 131 132 (void)nread; /* Unused */ 133 return ftp_code; 134} 135 136/* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode 137 saying whether an error occurred or CURLE_OK if |len| was read. */ 138static CURLcode 139socket_read(curl_socket_t fd, void *to, size_t len) 140{ 141 char *to_p = to; 142 CURLcode result; 143 ssize_t nread; 144 145 while(len > 0) { 146 result = Curl_read_plain(fd, to_p, len, &nread); 147 if(!result) { 148 len -= nread; 149 to_p += nread; 150 } 151 else { 152 /* FIXME: We are doing a busy wait */ 153 if(result == CURLE_AGAIN) 154 continue; 155 return result; 156 } 157 } 158 return CURLE_OK; 159} 160 161 162/* Write |len| bytes from the buffer |to| to the socket |fd|. Return a 163 CURLcode saying whether an error occurred or CURLE_OK if |len| was 164 written. */ 165static CURLcode 166socket_write(struct connectdata *conn, curl_socket_t fd, const void *to, 167 size_t len) 168{ 169 const char *to_p = to; 170 CURLcode result; 171 ssize_t written; 172 173 while(len > 0) { 174 result = Curl_write_plain(conn, fd, to_p, len, &written); 175 if(!result) { 176 len -= written; 177 to_p += written; 178 } 179 else { 180 /* FIXME: We are doing a busy wait */ 181 if(result == CURLE_AGAIN) 182 continue; 183 return result; 184 } 185 } 186 return CURLE_OK; 187} 188 189static CURLcode read_data(struct connectdata *conn, 190 curl_socket_t fd, 191 struct krb5buffer *buf) 192{ 193 int len; 194 void *tmp = NULL; 195 CURLcode result; 196 197 result = socket_read(fd, &len, sizeof(len)); 198 if(result) 199 return result; 200 201 if(len) { 202 /* only realloc if there was a length */ 203 len = ntohl(len); 204 tmp = Curl_saferealloc(buf->data, len); 205 } 206 if(tmp == NULL) 207 return CURLE_OUT_OF_MEMORY; 208 209 buf->data = tmp; 210 result = socket_read(fd, buf->data, len); 211 if(result) 212 return result; 213 buf->size = conn->mech->decode(conn->app_data, buf->data, len, 214 conn->data_prot, conn); 215 buf->index = 0; 216 return CURLE_OK; 217} 218 219static size_t 220buffer_read(struct krb5buffer *buf, void *data, size_t len) 221{ 222 if(buf->size - buf->index < len) 223 len = buf->size - buf->index; 224 memcpy(data, (char *)buf->data + buf->index, len); 225 buf->index += len; 226 return len; 227} 228 229/* Matches Curl_recv signature */ 230static ssize_t sec_recv(struct connectdata *conn, int sockindex, 231 char *buffer, size_t len, CURLcode *err) 232{ 233 size_t bytes_read; 234 size_t total_read = 0; 235 curl_socket_t fd = conn->sock[sockindex]; 236 237 *err = CURLE_OK; 238 239 /* Handle clear text response. */ 240 if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) 241 return read(fd, buffer, len); 242 243 if(conn->in_buffer.eof_flag) { 244 conn->in_buffer.eof_flag = 0; 245 return 0; 246 } 247 248 bytes_read = buffer_read(&conn->in_buffer, buffer, len); 249 len -= bytes_read; 250 total_read += bytes_read; 251 buffer += bytes_read; 252 253 while(len > 0) { 254 if(read_data(conn, fd, &conn->in_buffer)) 255 return -1; 256 if(conn->in_buffer.size == 0) { 257 if(bytes_read > 0) 258 conn->in_buffer.eof_flag = 1; 259 return bytes_read; 260 } 261 bytes_read = buffer_read(&conn->in_buffer, buffer, len); 262 len -= bytes_read; 263 total_read += bytes_read; 264 buffer += bytes_read; 265 } 266 /* FIXME: Check for overflow */ 267 return total_read; 268} 269 270/* Send |length| bytes from |from| to the |fd| socket taking care of encoding 271 and negociating with the server. |from| can be NULL. */ 272/* FIXME: We don't check for errors nor report any! */ 273static void do_sec_send(struct connectdata *conn, curl_socket_t fd, 274 const char *from, int length) 275{ 276 int bytes, htonl_bytes; /* 32-bit integers for htonl */ 277 char *buffer = NULL; 278 char *cmd_buffer; 279 size_t cmd_size = 0; 280 CURLcode error; 281 enum protection_level prot_level = conn->data_prot; 282 bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; 283 284 DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); 285 286 if(iscmd) { 287 if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5)) 288 prot_level = PROT_PRIVATE; 289 else 290 prot_level = conn->command_prot; 291 } 292 bytes = conn->mech->encode(conn->app_data, from, length, prot_level, 293 (void **)&buffer); 294 if(!buffer || bytes <= 0) 295 return; /* error */ 296 297 if(iscmd) { 298 error = Curl_base64_encode(conn->data, buffer, curlx_sitouz(bytes), 299 &cmd_buffer, &cmd_size); 300 if(error) { 301 free(buffer); 302 return; /* error */ 303 } 304 if(cmd_size > 0) { 305 static const char *enc = "ENC "; 306 static const char *mic = "MIC "; 307 if(prot_level == PROT_PRIVATE) 308 socket_write(conn, fd, enc, 4); 309 else 310 socket_write(conn, fd, mic, 4); 311 312 socket_write(conn, fd, cmd_buffer, cmd_size); 313 socket_write(conn, fd, "\r\n", 2); 314 infof(conn->data, "Send: %s%s\n", prot_level == PROT_PRIVATE?enc:mic, 315 cmd_buffer); 316 free(cmd_buffer); 317 } 318 } 319 else { 320 htonl_bytes = htonl(bytes); 321 socket_write(conn, fd, &htonl_bytes, sizeof(htonl_bytes)); 322 socket_write(conn, fd, buffer, curlx_sitouz(bytes)); 323 } 324 free(buffer); 325} 326 327static ssize_t sec_write(struct connectdata *conn, curl_socket_t fd, 328 const char *buffer, size_t length) 329{ 330 ssize_t tx = 0, len = conn->buffer_size; 331 332 len -= conn->mech->overhead(conn->app_data, conn->data_prot, 333 curlx_sztosi(len)); 334 if(len <= 0) 335 len = length; 336 while(length) { 337 if(length < (size_t)len) 338 len = length; 339 340 do_sec_send(conn, fd, buffer, curlx_sztosi(len)); 341 length -= len; 342 buffer += len; 343 tx += len; 344 } 345 return tx; 346} 347 348/* Matches Curl_send signature */ 349static ssize_t sec_send(struct connectdata *conn, int sockindex, 350 const void *buffer, size_t len, CURLcode *err) 351{ 352 curl_socket_t fd = conn->sock[sockindex]; 353 *err = CURLE_OK; 354 return sec_write(conn, fd, buffer, len); 355} 356 357int Curl_sec_read_msg(struct connectdata *conn, char *buffer, 358 enum protection_level level) 359{ 360 /* decoded_len should be size_t or ssize_t but conn->mech->decode returns an 361 int */ 362 int decoded_len; 363 char *buf; 364 int ret_code = 0; 365 size_t decoded_sz = 0; 366 CURLcode error; 367 368 if(!conn->mech) 369 /* not inititalized, return error */ 370 return -1; 371 372 DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); 373 374 error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); 375 if(error || decoded_sz == 0) 376 return -1; 377 378 if(decoded_sz > (size_t)INT_MAX) { 379 free(buf); 380 return -1; 381 } 382 decoded_len = curlx_uztosi(decoded_sz); 383 384 decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, 385 level, conn); 386 if(decoded_len <= 0) { 387 free(buf); 388 return -1; 389 } 390 391 if(conn->data->set.verbose) { 392 buf[decoded_len] = '\n'; 393 Curl_debug(conn->data, CURLINFO_HEADER_IN, buf, decoded_len + 1, conn); 394 } 395 396 buf[decoded_len] = '\0'; 397 if(decoded_len <= 3) 398 /* suspiciously short */ 399 return 0; 400 401 if(buf[3] != '-') 402 /* safe to ignore return code */ 403 (void)sscanf(buf, "%d", &ret_code); 404 405 if(buf[decoded_len - 1] == '\n') 406 buf[decoded_len - 1] = '\0'; 407 /* FIXME: Is |buffer| length always greater than |decoded_len|? */ 408 strcpy(buffer, buf); 409 free(buf); 410 return ret_code; 411} 412 413/* FIXME: The error code returned here is never checked. */ 414static int sec_set_protection_level(struct connectdata *conn) 415{ 416 int code; 417 char *pbsz; 418 static unsigned int buffer_size = 1 << 20; /* 1048576 */ 419 enum protection_level level = conn->request_data_prot; 420 421 DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); 422 423 if(!conn->sec_complete) { 424 infof(conn->data, "Trying to change the protection level after the" 425 "completion of the data exchange.\n"); 426 return -1; 427 } 428 429 /* Bail out if we try to set up the same level */ 430 if(conn->data_prot == level) 431 return 0; 432 433 if(level) { 434 code = ftp_send_command(conn, "PBSZ %u", buffer_size); 435 if(code < 0) 436 return -1; 437 438 if(code/100 != 2) { 439 failf(conn->data, "Failed to set the protection's buffer size."); 440 return -1; 441 } 442 conn->buffer_size = buffer_size; 443 444 pbsz = strstr(conn->data->state.buffer, "PBSZ="); 445 if(pbsz) { 446 /* ignore return code, use default value if it fails */ 447 (void)sscanf(pbsz, "PBSZ=%u", &buffer_size); 448 if(buffer_size < conn->buffer_size) 449 conn->buffer_size = buffer_size; 450 } 451 } 452 453 /* Now try to negiociate the protection level. */ 454 code = ftp_send_command(conn, "PROT %c", level_to_char(level)); 455 456 if(code < 0) 457 return -1; 458 459 if(code/100 != 2) { 460 failf(conn->data, "Failed to set the protection level."); 461 return -1; 462 } 463 464 conn->data_prot = level; 465 if(level == PROT_PRIVATE) 466 conn->command_prot = level; 467 468 return 0; 469} 470 471int 472Curl_sec_request_prot(struct connectdata *conn, const char *level) 473{ 474 enum protection_level l = name_to_level(level); 475 if(l == PROT_NONE) 476 return -1; 477 DEBUGASSERT(l > PROT_NONE && l < PROT_LAST); 478 conn->request_data_prot = l; 479 return 0; 480} 481 482static CURLcode choose_mech(struct connectdata *conn) 483{ 484 int ret; 485 struct Curl_easy *data = conn->data; 486 void *tmp_allocation; 487 const struct Curl_sec_client_mech *mech = &Curl_krb5_client_mech; 488 489 tmp_allocation = realloc(conn->app_data, mech->size); 490 if(tmp_allocation == NULL) { 491 failf(data, "Failed realloc of size %u", mech->size); 492 mech = NULL; 493 return CURLE_OUT_OF_MEMORY; 494 } 495 conn->app_data = tmp_allocation; 496 497 if(mech->init) { 498 ret = mech->init(conn->app_data); 499 if(ret) { 500 infof(data, "Failed initialization for %s. Skipping it.\n", 501 mech->name); 502 return CURLE_FAILED_INIT; 503 } 504 } 505 506 infof(data, "Trying mechanism %s...\n", mech->name); 507 ret = ftp_send_command(conn, "AUTH %s", mech->name); 508 if(ret < 0) 509 /* FIXME: This error is too generic but it is OK for now. */ 510 return CURLE_COULDNT_CONNECT; 511 512 if(ret/100 != 3) { 513 switch(ret) { 514 case 504: 515 infof(data, "Mechanism %s is not supported by the server (server " 516 "returned ftp code: 504).\n", mech->name); 517 break; 518 case 534: 519 infof(data, "Mechanism %s was rejected by the server (server returned " 520 "ftp code: 534).\n", mech->name); 521 break; 522 default: 523 if(ret/100 == 5) { 524 infof(data, "server does not support the security extensions\n"); 525 return CURLE_USE_SSL_FAILED; 526 } 527 break; 528 } 529 return CURLE_LOGIN_DENIED; 530 } 531 532 /* Authenticate */ 533 ret = mech->auth(conn->app_data, conn); 534 535 if(ret != AUTH_CONTINUE) { 536 if(ret != AUTH_OK) { 537 /* Mechanism has dumped the error to stderr, don't error here. */ 538 return -1; 539 } 540 DEBUGASSERT(ret == AUTH_OK); 541 542 conn->mech = mech; 543 conn->sec_complete = 1; 544 conn->recv[FIRSTSOCKET] = sec_recv; 545 conn->send[FIRSTSOCKET] = sec_send; 546 conn->recv[SECONDARYSOCKET] = sec_recv; 547 conn->send[SECONDARYSOCKET] = sec_send; 548 conn->command_prot = PROT_SAFE; 549 /* Set the requested protection level */ 550 /* BLOCKING */ 551 (void)sec_set_protection_level(conn); 552 } 553 554 return CURLE_OK; 555} 556 557CURLcode 558Curl_sec_login(struct connectdata *conn) 559{ 560 return choose_mech(conn); 561} 562 563 564void 565Curl_sec_end(struct connectdata *conn) 566{ 567 if(conn->mech != NULL && conn->mech->end) 568 conn->mech->end(conn->app_data); 569 free(conn->app_data); 570 conn->app_data = NULL; 571 if(conn->in_buffer.data) { 572 free(conn->in_buffer.data); 573 conn->in_buffer.data = NULL; 574 conn->in_buffer.size = 0; 575 conn->in_buffer.index = 0; 576 /* FIXME: Is this really needed? */ 577 conn->in_buffer.eof_flag = 0; 578 } 579 conn->sec_complete = 0; 580 conn->data_prot = PROT_CLEAR; 581 conn->mech = NULL; 582} 583 584#endif /* HAVE_GSSAPI */ 585 586#endif /* CURL_DISABLE_FTP */ 587