1 /* $NetBSD: ssl.c,v 1.20 2024/09/25 16:53:58 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 1998-2004 Dag-Erling Coïdan Smørgrav 5 * Copyright (c) 2008, 2010 Joerg Sonnenberger <joerg@NetBSD.org> 6 * Copyright (c) 2015 Thomas Klausner <wiz@NetBSD.org> 7 * Copyright (c) 2023 Michael van Elst <mlelstv@NetBSD.org> 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer 15 * in this position and unchanged. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 3. The name of the author may not be used to endorse or promote products 20 * derived from this software without specific prior written permission 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 23 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 24 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 25 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 27 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 31 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 * 33 * $FreeBSD: common.c,v 1.53 2007/12/19 00:26:36 des Exp $ 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 __RCSID("$NetBSD: ssl.c,v 1.20 2024/09/25 16:53:58 christos Exp $"); 39 #endif 40 41 #include <err.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <stdarg.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 #include <time.h> 49 #include <unistd.h> 50 51 #include <sys/param.h> 52 #include <sys/uio.h> 53 54 #include <netinet/tcp.h> 55 #include <netinet/in.h> 56 57 #ifdef WITH_SSL 58 #include <openssl/crypto.h> 59 #include <openssl/x509.h> 60 #include <openssl/pem.h> 61 #include <openssl/ssl.h> 62 #include <openssl/err.h> 63 #endif 64 65 #include "ssl.h" 66 #include "ftp_var.h" 67 68 extern int quit_time, verbose, ftp_debug; 69 extern FILE *ttyout; 70 71 struct fetch_connect { 72 int sd; /* file/socket descriptor */ 73 char *buf; /* buffer */ 74 size_t bufsize; /* buffer size */ 75 size_t bufpos; /* position of buffer */ 76 size_t buflen; /* length of buffer contents */ 77 struct { /* data cached after an 78 interrupted read */ 79 char *buf; 80 size_t size; 81 size_t pos; 82 size_t len; 83 } cache; 84 int issock; 85 int iserr; 86 int iseof; 87 #ifdef WITH_SSL 88 SSL *ssl; /* SSL handle */ 89 #endif 90 }; 91 92 /* 93 * Write a vector to a connection w/ timeout 94 * Note: can modify the iovec. 95 */ 96 static ssize_t 97 fetch_writev(struct fetch_connect *conn, struct iovec *iov, int iovcnt) 98 { 99 struct timeval timeout, now, delta; 100 ssize_t len, total; 101 int fd = conn->sd; 102 int rv, timeout_secs; 103 struct pollfd pfd[1]; 104 105 pfd[0].fd = fd; 106 pfd[0].events = POLLOUT; 107 gettimeofday(&timeout, NULL); 108 timeout.tv_sec += quit_time; 109 110 total = 0; 111 while (iovcnt > 0) { 112 if (quit_time > 0) { /* enforce timeout */ 113 do { 114 (void)gettimeofday(&now, NULL); 115 timersub(&timeout, &now, &delta); 116 timeout_secs = (int)(delta.tv_sec * 1000 117 + delta.tv_usec / 1000); 118 if (timeout_secs < 0) 119 timeout_secs = 0; 120 rv = ftp_poll(pfd, 1, timeout_secs); 121 /* loop until poll !EINTR && !EAGAIN */ 122 } while (rv == -1 && (errno == EINTR || errno == EAGAIN)); 123 if (rv == -1) 124 return -1; 125 if (rv == 0) { 126 errno = ETIMEDOUT; 127 return -1; 128 } 129 } 130 errno = 0; 131 #ifdef WITH_SSL 132 if (conn->ssl != NULL) 133 len = SSL_write(conn->ssl, iov->iov_base, (int)iov->iov_len); 134 else 135 #endif 136 len = writev(fd, iov, iovcnt); 137 if (len == 0) { 138 /* we consider a short write a failure */ 139 /* XXX perhaps we shouldn't in the SSL case */ 140 errno = EPIPE; 141 return -1; 142 } 143 if (len < 0) { 144 if (errno == EINTR || errno == EAGAIN) 145 continue; 146 return -1; 147 } 148 total += len; 149 while (iovcnt > 0 && len >= (ssize_t)iov->iov_len) { 150 len -= iov->iov_len; 151 iov++; 152 iovcnt--; 153 } 154 if (iovcnt > 0) { 155 iov->iov_len -= len; 156 iov->iov_base = (char *)iov->iov_base + len; 157 } 158 } 159 return total; 160 } 161 162 static ssize_t 163 fetch_write(const void *str, size_t len, struct fetch_connect *conn) 164 { 165 struct iovec iov[1]; 166 167 iov[0].iov_base = (char *)__UNCONST(str); 168 iov[0].iov_len = len; 169 return fetch_writev(conn, iov, 1); 170 } 171 172 /* 173 * Send a formatted line; optionally echo to terminal 174 */ 175 int 176 fetch_printf(struct fetch_connect *conn, const char *fmt, ...) 177 { 178 va_list ap; 179 size_t len; 180 char *msg; 181 ssize_t r; 182 183 va_start(ap, fmt); 184 len = vasprintf(&msg, fmt, ap); 185 va_end(ap); 186 187 if (msg == NULL) { 188 errno = ENOMEM; 189 return -1; 190 } 191 192 r = fetch_write(msg, len, conn); 193 free(msg); 194 return (int)r; 195 } 196 197 int 198 fetch_fileno(struct fetch_connect *conn) 199 { 200 201 return conn->sd; 202 } 203 204 int 205 fetch_error(struct fetch_connect *conn) 206 { 207 208 return conn->iserr; 209 } 210 211 static void 212 fetch_clearerr(struct fetch_connect *conn) 213 { 214 215 conn->iserr = 0; 216 } 217 218 int 219 fetch_flush(struct fetch_connect *conn) 220 { 221 222 if (conn->issock) { 223 int fd = conn->sd; 224 int v; 225 #ifdef TCP_NOPUSH 226 v = 0; 227 setsockopt(fd, IPPROTO_TCP, TCP_NOPUSH, &v, sizeof(v)); 228 #endif 229 v = 1; 230 setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &v, sizeof(v)); 231 } 232 return 0; 233 } 234 235 /*ARGSUSED*/ 236 struct fetch_connect * 237 fetch_open(const char *fname, const char *fmode) 238 { 239 struct fetch_connect *conn; 240 int fd; 241 242 fd = open(fname, O_RDONLY); /* XXX: fmode */ 243 if (fd < 0) 244 return NULL; 245 246 if ((conn = calloc(1, sizeof(*conn))) == NULL) { 247 close(fd); 248 return NULL; 249 } 250 251 conn->sd = fd; 252 conn->issock = 0; 253 return conn; 254 } 255 256 /*ARGSUSED*/ 257 struct fetch_connect * 258 fetch_fdopen(int sd, const char *fmode) 259 { 260 struct fetch_connect *conn; 261 #if defined(SO_NOSIGPIPE) || defined(TCP_NOPUSH) 262 int opt = 1; 263 #endif 264 265 if ((conn = calloc(1, sizeof(*conn))) == NULL) 266 return NULL; 267 268 conn->sd = sd; 269 conn->issock = 1; 270 fcntl(sd, F_SETFD, FD_CLOEXEC); 271 #ifdef SO_NOSIGPIPE 272 setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt)); 273 #endif 274 #ifdef TCP_NOPUSH 275 setsockopt(sd, IPPROTO_TCP, TCP_NOPUSH, &opt, sizeof(opt)); 276 #endif 277 return conn; 278 } 279 280 int 281 fetch_close(struct fetch_connect *conn) 282 { 283 if (conn == NULL) 284 return 0; 285 286 fetch_flush(conn); 287 #ifdef WITH_SSL 288 SSL_free(conn->ssl); 289 #endif 290 close(conn->sd); 291 free(conn->cache.buf); 292 free(conn->buf); 293 free(conn); 294 return 0; 295 } 296 297 #define FETCH_WRITE_WAIT -3 298 #define FETCH_READ_WAIT -2 299 #define FETCH_READ_ERROR -1 300 301 #ifdef WITH_SSL 302 static ssize_t 303 fetch_ssl_read(SSL *ssl, void *buf, size_t len) 304 { 305 int rlen; 306 rlen = SSL_read(ssl, buf, (int)len); 307 if (rlen >= 0) 308 return rlen; 309 310 switch (SSL_get_error(ssl, rlen)) { 311 case SSL_ERROR_WANT_READ: 312 return FETCH_READ_WAIT; 313 case SSL_ERROR_WANT_WRITE: 314 return FETCH_WRITE_WAIT; 315 default: 316 ERR_print_errors_fp(ttyout); 317 return FETCH_READ_ERROR; 318 } 319 } 320 #endif /* WITH_SSL */ 321 322 static ssize_t 323 fetch_nonssl_read(int sd, void *buf, size_t len) 324 { 325 ssize_t rlen; 326 327 rlen = read(sd, buf, len); 328 if (rlen == -1) { 329 if (errno == EINTR || errno == EAGAIN) 330 return FETCH_READ_WAIT; 331 return FETCH_READ_ERROR; 332 } 333 return rlen; 334 } 335 336 /* 337 * Cache some data that was read from a socket but cannot be immediately 338 * returned because of an interrupted system call. 339 */ 340 static int 341 fetch_cache_data(struct fetch_connect *conn, char *src, size_t nbytes) 342 { 343 344 if (conn->cache.size < nbytes) { 345 char *tmp = realloc(conn->cache.buf, nbytes); 346 if (tmp == NULL) 347 return -1; 348 349 conn->cache.buf = tmp; 350 conn->cache.size = nbytes; 351 } 352 353 memcpy(conn->cache.buf, src, nbytes); 354 conn->cache.len = nbytes; 355 conn->cache.pos = 0; 356 return 0; 357 } 358 359 static int 360 fetch_wait(struct fetch_connect *conn, ssize_t rlen, struct timeval *timeout) 361 { 362 struct timeval now, delta; 363 int fd = conn->sd; 364 int rv, timeout_secs; 365 struct pollfd pfd[1]; 366 367 pfd[0].fd = fd; 368 if (rlen == FETCH_READ_WAIT) { 369 pfd[0].events = POLLIN; 370 } else if (rlen == FETCH_WRITE_WAIT) { 371 pfd[0].events = POLLOUT; 372 } else { 373 pfd[0].events = 0; 374 } 375 376 do { 377 if (quit_time > 0) { 378 gettimeofday(&now, NULL); 379 timersub(timeout, &now, &delta); 380 timeout_secs = (int)(delta.tv_sec * 1000 381 + delta.tv_usec / 1000); 382 if (timeout_secs < 0) 383 timeout_secs = 0; 384 } else { 385 timeout_secs = INFTIM; 386 } 387 errno = 0; 388 rv = ftp_poll(pfd, 1, timeout_secs); 389 /* loop until poll !EINTR && !EAGAIN */ 390 } while (rv == -1 && (errno == EINTR || errno == EAGAIN)); 391 if (rv == 0) { /* poll timeout */ 392 fprintf(ttyout, "\r\n%s: transfer aborted" 393 " because stalled for %lu sec.\r\n", 394 getprogname(), (unsigned long)quit_time); 395 errno = ETIMEDOUT; 396 conn->iserr = ETIMEDOUT; 397 return -1; 398 } 399 if (rv == -1) { /* poll error */ 400 conn->iserr = errno; 401 return -1; 402 } 403 return 0; 404 } 405 406 size_t 407 fetch_read(void *ptr, size_t size, size_t nmemb, struct fetch_connect *conn) 408 { 409 ssize_t rlen, total; 410 size_t len; 411 char *start, *buf; 412 struct timeval timeout; 413 414 if (quit_time > 0) { 415 gettimeofday(&timeout, NULL); 416 timeout.tv_sec += quit_time; 417 } 418 419 total = 0; 420 start = buf = ptr; 421 len = size * nmemb; 422 423 if (conn->cache.len > 0) { 424 /* 425 * The last invocation of fetch_read was interrupted by a 426 * signal after some data had been read from the socket. Copy 427 * the cached data into the supplied buffer before trying to 428 * read from the socket again. 429 */ 430 total = (conn->cache.len < len) ? conn->cache.len : len; 431 memcpy(buf, conn->cache.buf, total); 432 433 conn->cache.len -= total; 434 conn->cache.pos += total; 435 len -= total; 436 buf += total; 437 } 438 439 while (len > 0) { 440 /* 441 * The socket is non-blocking. Instead of the canonical 442 * poll() -> read(), we do the following: 443 * 444 * 1) call read() or SSL_read(). 445 * 2) if an error occurred, return -1. 446 * 3) if we received data but we still expect more, 447 * update our counters and loop. 448 * 4) if read() or SSL_read() signaled EOF, return. 449 * 5) if we did not receive any data but we're not at EOF, 450 * call poll(). 451 * 452 * In the SSL case, this is necessary because if we 453 * receive a close notification, we have to call 454 * SSL_read() one additional time after we've read 455 * everything we received. 456 * 457 * In the non-SSL case, it may improve performance (very 458 * slightly) when reading small amounts of data. 459 */ 460 #ifdef WITH_SSL 461 if (conn->ssl != NULL) 462 rlen = fetch_ssl_read(conn->ssl, buf, len); 463 else 464 #endif 465 rlen = fetch_nonssl_read(conn->sd, buf, len); 466 switch (rlen) { 467 case 0: 468 conn->iseof = 1; 469 return total; 470 case FETCH_READ_ERROR: 471 conn->iserr = errno; 472 if (errno == EINTR || errno == EAGAIN) 473 fetch_cache_data(conn, start, total); 474 return 0; 475 case FETCH_READ_WAIT: 476 case FETCH_WRITE_WAIT: 477 if (fetch_wait(conn, rlen, &timeout) == -1) 478 return 0; 479 break; 480 default: 481 len -= rlen; 482 buf += rlen; 483 total += rlen; 484 break; 485 } 486 } 487 return total; 488 } 489 490 #define MIN_BUF_SIZE 1024 491 492 /* 493 * Read a line of text from a connection w/ timeout 494 */ 495 char * 496 fetch_getln(char *str, int size, struct fetch_connect *conn) 497 { 498 size_t tmpsize; 499 size_t len; 500 char c; 501 502 if (conn->buf == NULL) { 503 if ((conn->buf = malloc(MIN_BUF_SIZE)) == NULL) { 504 errno = ENOMEM; 505 conn->iserr = 1; 506 return NULL; 507 } 508 conn->bufsize = MIN_BUF_SIZE; 509 } 510 511 if (conn->iserr || conn->iseof) 512 return NULL; 513 514 if (conn->buflen - conn->bufpos > 0) 515 goto done; 516 517 conn->buf[0] = '\0'; 518 conn->bufpos = 0; 519 conn->buflen = 0; 520 do { 521 len = fetch_read(&c, sizeof(c), 1, conn); 522 if (len == 0) { 523 if (conn->iserr) 524 return NULL; 525 if (conn->iseof) 526 break; 527 abort(); 528 } 529 conn->buf[conn->buflen++] = c; 530 if (conn->buflen == conn->bufsize) { 531 char *tmp = conn->buf; 532 tmpsize = conn->bufsize * 2 + 1; 533 if ((tmp = realloc(tmp, tmpsize)) == NULL) { 534 errno = ENOMEM; 535 conn->iserr = 1; 536 return NULL; 537 } 538 conn->buf = tmp; 539 conn->bufsize = tmpsize; 540 } 541 } while (c != '\n'); 542 543 if (conn->buflen == 0) 544 return NULL; 545 done: 546 tmpsize = MIN(size - 1, (int)(conn->buflen - conn->bufpos)); 547 memcpy(str, conn->buf + conn->bufpos, tmpsize); 548 str[tmpsize] = '\0'; 549 conn->bufpos += tmpsize; 550 return str; 551 } 552 553 int 554 fetch_getline(struct fetch_connect *conn, char *buf, size_t buflen, 555 const char **errormsg) 556 { 557 size_t len; 558 int rv; 559 560 if (fetch_getln(buf, (int)buflen, conn) == NULL) { 561 if (conn->iseof) { /* EOF */ 562 rv = -2; 563 if (errormsg) 564 *errormsg = "\nEOF received"; 565 } else { /* error */ 566 rv = -1; 567 if (errormsg) 568 *errormsg = "Error encountered"; 569 } 570 fetch_clearerr(conn); 571 return rv; 572 } 573 len = strlen(buf); 574 if (buf[len - 1] == '\n') { /* clear any trailing newline */ 575 buf[--len] = '\0'; 576 } else if (len == buflen - 1) { /* line too long */ 577 for (;;) { 578 char c; 579 size_t rlen = fetch_read(&c, sizeof(c), 1, conn); 580 if (rlen == 0 || c == '\n') 581 break; 582 } 583 if (errormsg) 584 *errormsg = "Input line is too long (specify -b > 16384)"; 585 fetch_clearerr(conn); 586 return -3; 587 } 588 if (errormsg) 589 *errormsg = NULL; 590 return (int)len; 591 } 592 593 #ifdef WITH_SSL 594 /* 595 * Start the SSL/TLS negotiation. 596 * Socket fcntl flags are temporarily updated to include O_NONBLOCK; 597 * these will not be reverted on connection failure. 598 * Returns pointer to allocated SSL structure on success, 599 * or NULL upon failure. 600 */ 601 void * 602 fetch_start_ssl(int sock, const char *servername) 603 { 604 SSL *ssl = NULL; 605 SSL_CTX *ctx = NULL; 606 X509_VERIFY_PARAM *param; 607 int ret, ssl_err, flags, rv, timeout_secs; 608 int verify = !ftp_truthy("sslnoverify", getoptionvalue("sslnoverify"), 0); 609 struct timeval timeout, now, delta; 610 struct pollfd pfd[1]; 611 612 /* Init the SSL library and context */ 613 if (!SSL_library_init()){ 614 warnx("SSL library init failed"); 615 goto cleanup_start_ssl; 616 } 617 618 SSL_load_error_strings(); 619 620 ctx = SSL_CTX_new(SSLv23_client_method()); 621 SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); 622 if (verify) { 623 SSL_CTX_set_default_verify_paths(ctx); 624 SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); 625 } 626 627 ssl = SSL_new(ctx); 628 if (ssl == NULL){ 629 warnx("SSL context creation failed"); 630 goto cleanup_start_ssl; 631 } 632 633 if (verify) { 634 param = SSL_get0_param(ssl); 635 if (!X509_VERIFY_PARAM_set1_host(param, servername, 636 strlen(servername))) { 637 warnx("SSL verification setup failed"); 638 goto cleanup_start_ssl; 639 } 640 641 /* Enable peer verification, (using the default callback) */ 642 SSL_set_verify(ssl, SSL_VERIFY_PEER, NULL); 643 } 644 #ifdef SSL_OP_IGNORE_UNEXPECTED_EOF 645 SSL_set_options(ssl, SSL_OP_IGNORE_UNEXPECTED_EOF); 646 #endif 647 648 /* save current socket flags */ 649 if ((flags = fcntl(sock, F_GETFL, 0)) == -1) { 650 warn("Can't %s socket flags for SSL connect to `%s'", 651 "save", servername); 652 goto cleanup_start_ssl; 653 } 654 /* set non-blocking connect */ 655 if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { 656 warn("Can't set socket non-blocking for SSL connect to `%s'", 657 servername); 658 goto cleanup_start_ssl; 659 } 660 661 /* NOTE: we now must restore socket flags on successful connection */ 662 663 (void)gettimeofday(&timeout, NULL); /* setup SSL_connect() timeout */ 664 timeout.tv_sec += (quit_time > 0) ? quit_time: 60; 665 /* without -q, default to 60s */ 666 667 SSL_set_fd(ssl, sock); 668 if (!SSL_set_tlsext_host_name(ssl, __UNCONST(servername))) { 669 warnx("SSL hostname setting failed"); 670 goto cleanup_start_ssl; 671 } 672 pfd[0].fd = sock; 673 pfd[0].events = 0; 674 while ((ret = SSL_connect(ssl)) <= 0) { 675 ssl_err = SSL_get_error(ssl, ret); 676 DPRINTF("%s: SSL_connect() ret=%d ssl_err=%d\n", 677 __func__, ret, ssl_err); 678 if (ret == 0) { /* unsuccessful handshake */ 679 ERR_print_errors_fp(ttyout); 680 goto cleanup_start_ssl; 681 } 682 if (ssl_err == SSL_ERROR_WANT_READ) { 683 pfd[0].events = POLLIN; 684 } else if (ssl_err == SSL_ERROR_WANT_WRITE) { 685 pfd[0].events = POLLOUT; 686 } else { 687 ERR_print_errors_fp(ttyout); 688 goto cleanup_start_ssl; 689 } 690 (void)gettimeofday(&now, NULL); 691 timersub(&timeout, &now, &delta); 692 timeout_secs = (int)(delta.tv_sec * 1000 693 + delta.tv_usec / 1000); 694 if (timeout_secs < 0) 695 timeout_secs = 0; 696 rv = ftp_poll(pfd, 1, timeout_secs); 697 if (rv == 0) { /* poll for SSL_connect() timed out */ 698 fprintf(ttyout, "Timeout establishing SSL connection to `%s'\n", 699 servername); 700 goto cleanup_start_ssl; 701 } else if (rv == -1 && errno != EINTR && errno != EAGAIN) { 702 warn("Error polling for SSL connect to `%s'", servername); 703 goto cleanup_start_ssl; 704 } 705 } 706 707 if (fcntl(sock, F_SETFL, flags) == -1) { 708 /* restore socket flags */ 709 warn("Can't %s socket flags for SSL connect to `%s'", 710 "restore", servername); 711 goto cleanup_start_ssl; 712 } 713 714 if (ftp_debug && verbose) { 715 X509 *cert; 716 X509_NAME *name; 717 char *str; 718 719 fprintf(ttyout, "SSL connection established using %s\n", 720 SSL_get_cipher(ssl)); 721 cert = SSL_get_peer_certificate(ssl); 722 name = X509_get_subject_name(cert); 723 str = X509_NAME_oneline(name, 0, 0); 724 fprintf(ttyout, "Certificate subject: %s\n", str); 725 free(str); 726 name = X509_get_issuer_name(cert); 727 str = X509_NAME_oneline(name, 0, 0); 728 fprintf(ttyout, "Certificate issuer: %s\n", str); 729 free(str); 730 } 731 732 return ssl; 733 734 cleanup_start_ssl: 735 if (ssl) 736 SSL_free(ssl); 737 if (ctx) 738 SSL_CTX_free(ctx); 739 return NULL; 740 } 741 #endif /* WITH_SSL */ 742 743 744 void 745 fetch_set_ssl(struct fetch_connect *conn, void *ssl) 746 { 747 #ifdef WITH_SSL 748 conn->ssl = ssl; 749 #endif 750 } 751