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, 2013 Kungliga Tekniska H�gskolan 11 * (Royal Institute of Technology, Stockholm, Sweden). 12 * 13 * Copyright (C) 2001 - 2013, 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#ifdef HAVE_LIMITS_H 54#include <limits.h> 55#endif 56 57#include "urldata.h" 58#include "curl_base64.h" 59#include "curl_memory.h" 60#include "curl_sec.h" 61#include "ftp.h" 62#include "sendf.h" 63#include "rawstr.h" 64#include "warnless.h" 65 66/* The last #include file should be: */ 67#include "memdebug.h" 68 69static const struct { 70 enum protection_level level; 71 const char *name; 72} level_names[] = { 73 { PROT_CLEAR, "clear" }, 74 { PROT_SAFE, "safe" }, 75 { PROT_CONFIDENTIAL, "confidential" }, 76 { PROT_PRIVATE, "private" } 77}; 78 79static enum protection_level 80name_to_level(const char *name) 81{ 82 int i; 83 for(i = 0; i < (int)sizeof(level_names)/(int)sizeof(level_names[0]); i++) 84 if(checkprefix(name, level_names[i].name)) 85 return level_names[i].level; 86 return PROT_NONE; 87} 88 89/* Convert a protocol |level| to its char representation. 90 We take an int to catch programming mistakes. */ 91static char level_to_char(int level) { 92 switch(level) { 93 case PROT_CLEAR: 94 return 'C'; 95 case PROT_SAFE: 96 return 'S'; 97 case PROT_CONFIDENTIAL: 98 return 'E'; 99 case PROT_PRIVATE: 100 return 'P'; 101 case PROT_CMD: 102 /* Fall through */ 103 default: 104 /* Those 2 cases should not be reached! */ 105 break; 106 } 107 DEBUGASSERT(0); 108 /* Default to the most secure alternative. */ 109 return 'P'; 110} 111 112static const struct Curl_sec_client_mech * const mechs[] = { 113#ifdef HAVE_GSSAPI 114 &Curl_krb5_client_mech, 115#endif 116 NULL 117}; 118 119/* Send an FTP command defined by |message| and the optional arguments. The 120 function returns the ftp_code. If an error occurs, -1 is returned. */ 121static int ftp_send_command(struct connectdata *conn, const char *message, ...) 122{ 123 int ftp_code; 124 ssize_t nread; 125 va_list args; 126 char print_buffer[50]; 127 128 va_start(args, message); 129 vsnprintf(print_buffer, sizeof(print_buffer), message, args); 130 va_end(args); 131 132 if(Curl_ftpsendf(conn, print_buffer) != CURLE_OK) { 133 ftp_code = -1; 134 } 135 else { 136 if(Curl_GetFTPResponse(&nread, conn, &ftp_code) != CURLE_OK) 137 ftp_code = -1; 138 } 139 140 (void)nread; /* Unused */ 141 return ftp_code; 142} 143 144/* Read |len| from the socket |fd| and store it in |to|. Return a CURLcode 145 saying whether an error occurred or CURLE_OK if |len| was read. */ 146static CURLcode 147socket_read(curl_socket_t fd, void *to, size_t len) 148{ 149 char *to_p = to; 150 CURLcode code; 151 ssize_t nread; 152 153 while(len > 0) { 154 code = Curl_read_plain(fd, to_p, len, &nread); 155 if(code == CURLE_OK) { 156 len -= nread; 157 to_p += nread; 158 } 159 else { 160 /* FIXME: We are doing a busy wait */ 161 if(code == CURLE_AGAIN) 162 continue; 163 return code; 164 } 165 } 166 return CURLE_OK; 167} 168 169 170/* Write |len| bytes from the buffer |to| to the socket |fd|. Return a 171 CURLcode saying whether an error occurred or CURLE_OK if |len| was 172 written. */ 173static CURLcode 174socket_write(struct connectdata *conn, curl_socket_t fd, const void *to, 175 size_t len) 176{ 177 const char *to_p = to; 178 CURLcode code; 179 ssize_t written; 180 181 while(len > 0) { 182 code = Curl_write_plain(conn, fd, to_p, len, &written); 183 if(code == CURLE_OK) { 184 len -= written; 185 to_p += written; 186 } 187 else { 188 /* FIXME: We are doing a busy wait */ 189 if(code == CURLE_AGAIN) 190 continue; 191 return code; 192 } 193 } 194 return CURLE_OK; 195} 196 197static CURLcode read_data(struct connectdata *conn, 198 curl_socket_t fd, 199 struct krb5buffer *buf) 200{ 201 int len; 202 void* tmp; 203 CURLcode ret; 204 205 ret = socket_read(fd, &len, sizeof(len)); 206 if(ret != CURLE_OK) 207 return ret; 208 209 len = ntohl(len); 210 tmp = realloc(buf->data, len); 211 if(tmp == NULL) 212 return CURLE_OUT_OF_MEMORY; 213 214 buf->data = tmp; 215 ret = socket_read(fd, buf->data, len); 216 if(ret != CURLE_OK) 217 return ret; 218 buf->size = conn->mech->decode(conn->app_data, buf->data, len, 219 conn->data_prot, conn); 220 buf->index = 0; 221 return CURLE_OK; 222} 223 224static size_t 225buffer_read(struct krb5buffer *buf, void *data, size_t len) 226{ 227 if(buf->size - buf->index < len) 228 len = buf->size - buf->index; 229 memcpy(data, (char*)buf->data + buf->index, len); 230 buf->index += len; 231 return len; 232} 233 234/* Matches Curl_recv signature */ 235static ssize_t sec_recv(struct connectdata *conn, int sockindex, 236 char *buffer, size_t len, CURLcode *err) 237{ 238 size_t bytes_read; 239 size_t total_read = 0; 240 curl_socket_t fd = conn->sock[sockindex]; 241 242 *err = CURLE_OK; 243 244 /* Handle clear text response. */ 245 if(conn->sec_complete == 0 || conn->data_prot == PROT_CLEAR) 246 return read(fd, buffer, len); 247 248 if(conn->in_buffer.eof_flag) { 249 conn->in_buffer.eof_flag = 0; 250 return 0; 251 } 252 253 bytes_read = buffer_read(&conn->in_buffer, buffer, len); 254 len -= bytes_read; 255 total_read += bytes_read; 256 buffer += bytes_read; 257 258 while(len > 0) { 259 if(read_data(conn, fd, &conn->in_buffer) != CURLE_OK) 260 return -1; 261 if(conn->in_buffer.size == 0) { 262 if(bytes_read > 0) 263 conn->in_buffer.eof_flag = 1; 264 return bytes_read; 265 } 266 bytes_read = buffer_read(&conn->in_buffer, buffer, len); 267 len -= bytes_read; 268 total_read += bytes_read; 269 buffer += bytes_read; 270 } 271 /* FIXME: Check for overflow */ 272 return total_read; 273} 274 275/* Send |length| bytes from |from| to the |fd| socket taking care of encoding 276 and negociating with the server. |from| can be NULL. */ 277/* FIXME: We don't check for errors nor report any! */ 278static void do_sec_send(struct connectdata *conn, curl_socket_t fd, 279 const char *from, int length) 280{ 281 int bytes, htonl_bytes; /* 32-bit integers for htonl */ 282 char *buffer = NULL; 283 char *cmd_buffer; 284 size_t cmd_size = 0; 285 CURLcode error; 286 enum protection_level prot_level = conn->data_prot; 287 bool iscmd = (prot_level == PROT_CMD)?TRUE:FALSE; 288 289 DEBUGASSERT(prot_level > PROT_NONE && prot_level < PROT_LAST); 290 291 if(iscmd) { 292 if(!strncmp(from, "PASS ", 5) || !strncmp(from, "ACCT ", 5)) 293 prot_level = PROT_PRIVATE; 294 else 295 prot_level = conn->command_prot; 296 } 297 bytes = conn->mech->encode(conn->app_data, from, length, prot_level, 298 (void**)&buffer, conn); 299 if(!buffer || bytes <= 0) 300 return; /* error */ 301 302 if(iscmd) { 303 error = Curl_base64_encode(conn->data, buffer, curlx_sitouz(bytes), 304 &cmd_buffer, &cmd_size); 305 if(error) { 306 free(buffer); 307 return; /* error */ 308 } 309 if(cmd_size > 0) { 310 static const char *enc = "ENC "; 311 static const char *mic = "MIC "; 312 if(prot_level == PROT_PRIVATE) 313 socket_write(conn, fd, enc, 4); 314 else 315 socket_write(conn, fd, mic, 4); 316 317 socket_write(conn, fd, cmd_buffer, cmd_size); 318 socket_write(conn, fd, "\r\n", 2); 319 infof(conn->data, "Send: %s%s\n", prot_level == PROT_PRIVATE?enc:mic, 320 cmd_buffer); 321 free(cmd_buffer); 322 } 323 } 324 else { 325 htonl_bytes = htonl(bytes); 326 socket_write(conn, fd, &htonl_bytes, sizeof(htonl_bytes)); 327 socket_write(conn, fd, buffer, curlx_sitouz(bytes)); 328 } 329 free(buffer); 330} 331 332static ssize_t sec_write(struct connectdata *conn, curl_socket_t fd, 333 const char *buffer, size_t length) 334{ 335 /* FIXME: Check for overflow */ 336 ssize_t tx = 0, len = conn->buffer_size; 337 338 len -= conn->mech->overhead(conn->app_data, conn->data_prot, 339 curlx_sztosi(len)); 340 if(len <= 0) 341 len = length; 342 while(length) { 343 if(len >= 0 || length < (size_t)len) { 344 /* FIXME: Check for overflow. */ 345 len = length; 346 } 347 do_sec_send(conn, fd, buffer, curlx_sztosi(len)); 348 length -= len; 349 buffer += len; 350 tx += len; 351 } 352 return tx; 353} 354 355/* Matches Curl_send signature */ 356static ssize_t sec_send(struct connectdata *conn, int sockindex, 357 const void *buffer, size_t len, CURLcode *err) 358{ 359 curl_socket_t fd = conn->sock[sockindex]; 360 *err = CURLE_OK; 361 return sec_write(conn, fd, buffer, len); 362} 363 364int Curl_sec_read_msg(struct connectdata *conn, char *buffer, 365 enum protection_level level) 366{ 367 /* decoded_len should be size_t or ssize_t but conn->mech->decode returns an 368 int */ 369 int decoded_len; 370 char *buf; 371 int ret_code; 372 size_t decoded_sz = 0; 373 CURLcode error; 374 375 DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); 376 377 error = Curl_base64_decode(buffer + 4, (unsigned char **)&buf, &decoded_sz); 378 if(error || decoded_sz == 0) 379 return -1; 380 381 if(decoded_sz > (size_t)INT_MAX) { 382 free(buf); 383 return -1; 384 } 385 decoded_len = curlx_uztosi(decoded_sz); 386 387 decoded_len = conn->mech->decode(conn->app_data, buf, decoded_len, 388 level, conn); 389 if(decoded_len <= 0) { 390 free(buf); 391 return -1; 392 } 393 394 if(conn->data->set.verbose) { 395 buf[decoded_len] = '\n'; 396 Curl_debug(conn->data, CURLINFO_HEADER_IN, buf, decoded_len + 1, conn); 397 } 398 399 buf[decoded_len] = '\0'; 400 DEBUGASSERT(decoded_len > 3); 401 if(buf[3] == '-') 402 ret_code = 0; 403 else { 404 /* Check for error? */ 405 sscanf(buf, "%d", &ret_code); 406 } 407 408 if(buf[decoded_len - 1] == '\n') 409 buf[decoded_len - 1] = '\0'; 410 /* FIXME: Is |buffer| length always greater than |decoded_len|? */ 411 strcpy(buffer, buf); 412 free(buf); 413 return ret_code; 414} 415 416/* FIXME: The error code returned here is never checked. */ 417static int sec_set_protection_level(struct connectdata *conn) 418{ 419 int code; 420 char* pbsz; 421 static unsigned int buffer_size = 1 << 20; /* 1048576 */ 422 enum protection_level level = conn->request_data_prot; 423 424 DEBUGASSERT(level > PROT_NONE && level < PROT_LAST); 425 426 if(!conn->sec_complete) { 427 infof(conn->data, "Trying to change the protection level after the" 428 "completion of the data exchange.\n"); 429 return -1; 430 } 431 432 /* Bail out if we try to set up the same level */ 433 if(conn->data_prot == level) 434 return 0; 435 436 if(level) { 437 code = ftp_send_command(conn, "PBSZ %u", buffer_size); 438 if(code < 0) 439 return -1; 440 441 if(code/100 != 2) { 442 failf(conn->data, "Failed to set the protection's buffer size."); 443 return -1; 444 } 445 conn->buffer_size = buffer_size; 446 447 pbsz = strstr(conn->data->state.buffer, "PBSZ="); 448 if(pbsz) { 449 /* FIXME: Checks for errors in sscanf? */ 450 sscanf(pbsz, "PBSZ=%u", &buffer_size); 451 if(buffer_size < conn->buffer_size) 452 conn->buffer_size = buffer_size; 453 } 454 } 455 456 /* Now try to negiociate the protection level. */ 457 code = ftp_send_command(conn, "PROT %c", level_to_char(level)); 458 459 if(code < 0) 460 return -1; 461 462 if(code/100 != 2) { 463 failf(conn->data, "Failed to set the protection level."); 464 return -1; 465 } 466 467 conn->data_prot = level; 468 if(level == PROT_PRIVATE) 469 conn->command_prot = level; 470 471 return 0; 472} 473 474int 475Curl_sec_request_prot(struct connectdata *conn, const char *level) 476{ 477 enum protection_level l = name_to_level(level); 478 if(l == PROT_NONE) 479 return -1; 480 DEBUGASSERT(l > PROT_NONE && l < PROT_LAST); 481 conn->request_data_prot = l; 482 return 0; 483} 484 485static CURLcode choose_mech(struct connectdata *conn) 486{ 487 int ret; 488 struct SessionHandle *data = conn->data; 489 const struct Curl_sec_client_mech * const *mech; 490 void *tmp_allocation; 491 const char *mech_name; 492 493 for(mech = mechs; (*mech); ++mech) { 494 mech_name = (*mech)->name; 495 /* We have no mechanism with a NULL name but keep this check */ 496 DEBUGASSERT(mech_name != NULL); 497 if(mech_name == NULL) { 498 infof(data, "Skipping mechanism with empty name (%p)\n", (void *)mech); 499 continue; 500 } 501 tmp_allocation = realloc(conn->app_data, (*mech)->size); 502 if(tmp_allocation == NULL) { 503 failf(data, "Failed realloc of size %u", (*mech)->size); 504 mech = NULL; 505 return CURLE_OUT_OF_MEMORY; 506 } 507 conn->app_data = tmp_allocation; 508 509 if((*mech)->init) { 510 ret = (*mech)->init(conn->app_data); 511 if(ret != 0) { 512 infof(data, "Failed initialization for %s. Skipping it.\n", mech_name); 513 continue; 514 } 515 } 516 517 infof(data, "Trying mechanism %s...\n", mech_name); 518 ret = ftp_send_command(conn, "AUTH %s", mech_name); 519 if(ret < 0) 520 /* FIXME: This error is too generic but it is OK for now. */ 521 return CURLE_COULDNT_CONNECT; 522 523 if(ret/100 != 3) { 524 switch(ret) { 525 case 504: 526 infof(data, "Mechanism %s is not supported by the server (server " 527 "returned ftp code: 504).\n", mech_name); 528 break; 529 case 534: 530 infof(data, "Mechanism %s was rejected by the server (server returned " 531 "ftp code: 534).\n", mech_name); 532 break; 533 default: 534 if(ret/100 == 5) { 535 infof(data, "server does not support the security extensions\n"); 536 return CURLE_USE_SSL_FAILED; 537 } 538 break; 539 } 540 continue; 541 } 542 543 /* Authenticate */ 544 ret = (*mech)->auth(conn->app_data, conn); 545 546 if(ret == AUTH_CONTINUE) 547 continue; 548 else if(ret != AUTH_OK) { 549 /* Mechanism has dumped the error to stderr, don't error here. */ 550 return -1; 551 } 552 DEBUGASSERT(ret == AUTH_OK); 553 554 conn->mech = *mech; 555 conn->sec_complete = 1; 556 conn->recv[FIRSTSOCKET] = sec_recv; 557 conn->send[FIRSTSOCKET] = sec_send; 558 conn->recv[SECONDARYSOCKET] = sec_recv; 559 conn->send[SECONDARYSOCKET] = sec_send; 560 conn->command_prot = PROT_SAFE; 561 /* Set the requested protection level */ 562 /* BLOCKING */ 563 (void)sec_set_protection_level(conn); 564 break; 565 } 566 567 return mech != NULL ? CURLE_OK : CURLE_FAILED_INIT; 568} 569 570CURLcode 571Curl_sec_login(struct connectdata *conn) 572{ 573 return choose_mech(conn); 574} 575 576 577void 578Curl_sec_end(struct connectdata *conn) 579{ 580 if(conn->mech != NULL && conn->mech->end) 581 conn->mech->end(conn->app_data); 582 if(conn->app_data) { 583 free(conn->app_data); 584 conn->app_data = NULL; 585 } 586 if(conn->in_buffer.data) { 587 free(conn->in_buffer.data); 588 conn->in_buffer.data = NULL; 589 conn->in_buffer.size = 0; 590 conn->in_buffer.index = 0; 591 /* FIXME: Is this really needed? */ 592 conn->in_buffer.eof_flag = 0; 593 } 594 conn->sec_complete = 0; 595 conn->data_prot = PROT_CLEAR; 596 conn->mech = NULL; 597} 598 599#endif /* HAVE_GSSAPI */ 600 601#endif /* CURL_DISABLE_FTP */ 602