Skip to content
Snippets Groups Projects
ssh.c 70.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • /***************************************************************************
    *                                  _   _ ____  _
    *  Project                     ___| | | |  _ \| |
    *                             / __| | | | |_) | |
    *                            | (__| |_| |  _ <| |___
    *                             \___|\___/|_| \_\_____|
    *
    
    * Copyright (C) 1998 - 2007, Daniel Stenberg, <daniel@haxx.se>, et al.
    
    *
    * This software is licensed as described in the file COPYING, which
    * you should have received as part of this distribution. The terms
    * are also available at http://curl.haxx.se/docs/copyright.html.
    *
    * You may opt to use, copy, modify, merge, publish, distribute and/or sell
    * copies of the Software, and permit persons to whom the Software is
    * furnished to do so, under the terms of the COPYING file.
    *
    * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
    * KIND, either express or implied.
    *
    * $Id$
    ***************************************************************************/
    
    
    
    #include "setup.h"
    
    #ifdef USE_LIBSSH2
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <ctype.h>
    #include <limits.h>
    
    #include <libssh2.h>
    #include <libssh2_sftp.h>
    
    #ifdef HAVE_UNISTD_H
    #include <unistd.h>
    #endif
    
    #ifdef HAVE_FCNTL_H
    #include <fcntl.h>
    #endif
    
    
    #ifdef HAVE_TIME_H
    #include <time.h>
    #endif
    
    
    #ifdef HAVE_SYS_SOCKET_H
    #include <sys/socket.h>
    #endif
    #ifdef HAVE_NETINET_IN_H
    #include <netinet/in.h>
    #endif
    #ifdef HAVE_ARPA_INET_H
    #include <arpa/inet.h>
    #endif
    #ifdef HAVE_UTSNAME_H
    #include <sys/utsname.h>
    #endif
    #ifdef HAVE_NETDB_H
    #include <netdb.h>
    #endif
    #ifdef  VMS
    #include <in.h>
    #include <inet.h>
    #endif
    
    
    #if (defined(NETWARE) && defined(__NOVELL_LIBC__))
    #undef in_addr_t
    #define in_addr_t unsigned long
    #endif
    
    #include <curl/curl.h>
    #include "urldata.h"
    #include "sendf.h"
    #include "easyif.h" /* for Curl_convert_... prototypes */
    
    #include "if2ip.h"
    #include "hostip.h"
    #include "progress.h"
    #include "transfer.h"
    #include "escape.h"
    #include "http.h" /* for HTTP proxy tunnel stuff */
    #include "ssh.h"
    #include "url.h"
    #include "speedcheck.h"
    #include "getinfo.h"
    
    #include "strtoofft.h"
    #include "strequal.h"
    #include "sslgen.h"
    #include "connect.h"
    #include "strerror.h"
    #include "memory.h"
    #include "inet_ntop.h"
    #include "select.h"
    #include "parsedate.h" /* for the week day and month names */
    #include "sockaddr.h" /* required for Curl_sockaddr_storage */
    #include "multiif.h"
    
    #if defined(HAVE_INET_NTOA_R) && !defined(HAVE_INET_NTOA_R_DECL)
    #include "inet_ntoa_r.h"
    #endif
    
    #define _MPRINTF_REPLACE /* use our functions only */
    #include <curl/mprintf.h>
    
    #if defined(WIN32) || defined(MSDOS) || defined(__EMX__)
    #define DIRSEP '\\'
    #else
    #define DIRSEP '/'
    #endif
    
    #define _MPRINTF_REPLACE /* use our functions only */
    #include <curl/mprintf.h>
    
    /* The last #include file should be: */
    #ifdef CURLDEBUG
    #include "memdebug.h"
    #endif
    
    
    #ifndef LIBSSH2_SFTP_S_IRUSR
    /* Here's a work-around for those of you who happend to run a libssh2 version
       that is 0.14 or older. We should remove this kludge as soon as we can
       require a more recent libssh2 release. */
    
    #ifndef S_IRGRP
    #define S_IRGRP  0
    #endif
    
    #ifndef S_IROTH
    #define S_IROTH 0
    #endif
    
    
    #define LIBSSH2_SFTP_S_IRUSR S_IRUSR
    #define LIBSSH2_SFTP_S_IWUSR S_IWUSR
    #define LIBSSH2_SFTP_S_IRGRP S_IRGRP
    #define LIBSSH2_SFTP_S_IROTH S_IROTH
    #define LIBSSH2_SFTP_S_IRUSR S_IRUSR
    #define LIBSSH2_SFTP_S_IWUSR S_IWUSR
    #define LIBSSH2_SFTP_S_IRGRP S_IRGRP
    #define LIBSSH2_SFTP_S_IROTH S_IROTH
    #define LIBSSH2_SFTP_S_IFMT S_IFMT
    #define LIBSSH2_SFTP_S_IFDIR S_IFDIR
    #define LIBSSH2_SFTP_S_IFLNK S_IFLNK
    #define LIBSSH2_SFTP_S_IFSOCK S_IFSOCK
    #define LIBSSH2_SFTP_S_IFCHR S_IFCHR
    #define LIBSSH2_SFTP_S_IFBLK S_IFBLK
    #define LIBSSH2_SFTP_S_IXUSR S_IXUSR
    #define LIBSSH2_SFTP_S_IWGRP S_IWGRP
    #define LIBSSH2_SFTP_S_IXGRP S_IXGRP
    #define LIBSSH2_SFTP_S_IWOTH S_IWOTH
    #define LIBSSH2_SFTP_S_IXOTH S_IXOTH
    #endif
    
    
    static const char *sftp_libssh2_strerror(unsigned long err);
    
    static CURLcode sftp_sendquote(struct connectdata *conn,
                                   struct curl_slist *quote);
    
    
    static LIBSSH2_ALLOC_FUNC(libssh2_malloc);
    static LIBSSH2_REALLOC_FUNC(libssh2_realloc);
    static LIBSSH2_FREE_FUNC(libssh2_free);
    
    static void
    kbd_callback(const char *name, int name_len, const char *instruction,
                 int instruction_len, int num_prompts,
                 const LIBSSH2_USERAUTH_KBDINT_PROMPT *prompts,
                 LIBSSH2_USERAUTH_KBDINT_RESPONSE *responses,
                 void **abstract)
    {
    
      struct SSHPROTO *ssh = (struct SSHPROTO *)*abstract;
    
    
    #ifdef CURL_LIBSSH2_DEBUG
      fprintf(stderr, "name=%s\n", name);
      fprintf(stderr, "name_len=%d\n", name_len);
      fprintf(stderr, "instruction=%s\n", instruction);
      fprintf(stderr, "instruction_len=%d\n", instruction_len);
      fprintf(stderr, "num_prompts=%d\n", num_prompts);
    
    #else
      (void)name;
      (void)name_len;
      (void)instruction;
      (void)instruction_len;
    
    #endif  /* CURL_LIBSSH2_DEBUG */
      if (num_prompts == 1) {
    
        responses[0].text = strdup(ssh->passwd);
        responses[0].length = strlen(ssh->passwd);
    
      }
      (void)prompts;
      (void)abstract;
    } /* kbd_callback */
    
    
    static CURLcode sftp_libssh2_error_to_CURLE(unsigned long err)
    
      if (err == LIBSSH2_FX_OK)
    
        return CURLE_OK;
    
      /* TODO: map some of the libssh2 errors to the more appropriate CURLcode
         error code, and possibly add a few new SSH-related one. We must however
         not return or even depend on libssh2 errors in the public libcurl API */
    
    
      if (err == LIBSSH2_FX_NO_SUCH_FILE)
         return CURLE_REMOTE_FILE_NOT_FOUND;
    
      return CURLE_SSH;
    }
    
    static CURLcode libssh2_session_error_to_CURLE(int err)
    {
      (void)err;
    
      return CURLE_SSH;
    }
    
    static LIBSSH2_ALLOC_FUNC(libssh2_malloc)
    {
      return malloc(count);
      (void)abstract;
    }
    
    static LIBSSH2_REALLOC_FUNC(libssh2_realloc)
    {
      return realloc(ptr, count);
      (void)abstract;
    }
    
    static LIBSSH2_FREE_FUNC(libssh2_free)
    {
      free(ptr);
      (void)abstract;
    }
    
    
    237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
    /*
     * SSH State machine related code 
     */
    /* This is the ONLY way to change SSH state! */
    static void state(struct connectdata *conn, ftpstate state)
    {
    #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
      /* for debug purposes */
      const char *names[]={
        "STOP",
        "SSH_S_STARTUP",
        "SSH_AUTHLIST",
        "SSH_AUTH_PKEY_INIT",
        "SSH_AUTH_PKEY",
        "SSH_AUTH_PASS_INIT",
        "SSH_AUTH_PASS",
        "SSH_AUTH_HOST_INIT",
        "SSH_AUTH_HOST",
        "SSH_AUTH_KEY_INIT",
        "SSH_AUTH_KEY",
        "SSH_AUTH_DONE",
        "SSH_SFTP_INIT",
        "SSH_SFTP_REALPATH",
        "SSH_GET_WORKINGPATH",
        "QUIT"
      };
    #endif
      struct ssh_conn *sshc = &conn->proto.sshc;
      
    #if defined(CURLDEBUG) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
      if (sshc->state != state) {
        infof(conn->data, "FTP %p state change from %s to %s\n",
              sshc, names[sshc->state], names[state]);
      }
    #endif
      
      sshc->state = state;
    }
    
    static CURLcode ssh_statemach_act(struct connectdata *conn)
    {
      CURLcode result = CURLE_OK;
      struct SessionHandle *data=conn->data;
      struct ssh_conn *sshc = &conn->proto.sshc;
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
      struct SSHPROTO *ssh;
    #ifdef CURL_LIBSSH2_DEBUG
      const char *fingerprint;
    #endif /* CURL_LIBSSH2_DEBUG */
      int rc;
      
      ssh = data->reqdata.proto.ssh;
      
      switch(sshc->state) {
        case SSH_S_STARTUP:
          rc = libssh2_session_startup(ssh->ssh_session, sock);
          if (rc == LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          else if (rc) {
            failf(data, "Failure establishing ssh session");
            libssh2_session_free(ssh->ssh_session);
            ssh->ssh_session = NULL;
            state(conn, SSH_STOP);
            result = CURLE_FAILED_INIT;
            break;
          }
            
    #ifdef CURL_LIBSSH2_DEBUG
          /*
           * Before we authenticate we should check the hostkey's fingerprint
           * against our known hosts. How that is handled (reading from file,
           * whatever) is up to us. As for know not much is implemented, besides
           * showing how to get the fingerprint.
           */
          fingerprint = libssh2_hostkey_hash(ssh->ssh_session,
                                             LIBSSH2_HOSTKEY_HASH_MD5);
          
          /* The fingerprint points to static storage (!), don't free() it. */
          infof(data, "Fingerprint: ");
          for (i = 0; i < 16; i++) {
            infof(data, "%02X ", (unsigned char) fingerprint[i]);
          }
          infof(data, "\n");
    #endif /* CURL_LIBSSH2_DEBUG */
          
          state(conn, SSH_AUTHLIST);
          break;
          
        case SSH_AUTHLIST:
          /* TBD - methods to check the host keys need to be done */
          
          /*
           * Figure out authentication methods
           * NB: As soon as we have provided a username to an openssh server we
           * must never change it later. Thus, always specify the correct username
           * here, even though the libssh2 docs kind of indicate that it should be
           * possible to get a 'generic' list (not user-specific) of authentication
           * methods, presumably with a blank username. That won't work in my
           * experience.
           * So always specify it here.
           */
          sshc->authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user,
                                           strlen(ssh->user));
          
          if (!sshc->authlist) {
            if (libssh2_session_last_errno(ssh->ssh_session) ==
                            LIBSSH2_ERROR_EAGAIN) {
              break;
            } else {
              libssh2_session_free(ssh->ssh_session);
              ssh->ssh_session = NULL;
              state(conn, SSH_STOP);
              result = CURLE_OUT_OF_MEMORY;
              break;
            }
          }
          infof(data, "SSH authentication methods available: %s\n", sshc->authlist);
    
          state(conn, SSH_AUTH_PKEY_INIT);
          break;
          
        case SSH_AUTH_PKEY_INIT:
          /*
           * Check the supported auth types in the order I feel is most secure with
           * the requested type of authentication
           */
          sshc->authed = FALSE;
          
          if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
              (strstr(sshc->authlist, "publickey") != NULL)) {
            char *home;
            
            sshc->rsa_pub[0] = sshc->rsa[0] = '\0';
            
            /* To ponder about: should really the lib be messing about with the
               HOME environment variable etc? */
            home = curl_getenv("HOME");
            
            if (data->set.ssh_public_key)
              snprintf(sshc->rsa_pub, sizeof(sshc->rsa_pub), "%s",
                       data->set.ssh_public_key);
            else if (home)
              snprintf(sshc->rsa_pub, sizeof(sshc->rsa_pub), "%s/.ssh/id_dsa.pub",
                       home);
            
            if (data->set.ssh_private_key)
              snprintf(sshc->rsa, sizeof(sshc->rsa), "%s",
                       data->set.ssh_private_key);
            else if (home)
              snprintf(sshc->rsa, sizeof(sshc->rsa), "%s/.ssh/id_dsa", home);
            
            sshc->passphrase = data->set.key_passwd;
            if (!sshc->passphrase)
              sshc->passphrase = "";
            
            curl_free(home);
            
            infof(conn->data, "Using ssh public key file %s\n", sshc->rsa_pub);
            infof(conn->data, "Using ssh private key file %s\n", sshc->rsa);
            
            if (sshc->rsa_pub[0]) {
              state(conn, SSH_AUTH_PKEY);
            } else {
              state(conn, SSH_AUTH_PASS_INIT);
            }
          } else {
            state(conn, SSH_AUTH_PASS_INIT);
          }
          break;
          
        case SSH_AUTH_PKEY:
          /* The function below checks if the files exists, no need to stat() here.
           */
          rc = libssh2_userauth_publickey_fromfile(ssh->ssh_session, ssh->user,
                                                   sshc->rsa_pub, sshc->rsa,
                                                   sshc->passphrase);
          if (rc == LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          else if (rc == 0) {
            sshc->authed = TRUE;
            infof(conn->data, "Initialized SSH public key authentication\n");
            state(conn, SSH_AUTH_DONE);
          } else {
            state(conn, SSH_AUTH_PASS_INIT);
          }
          break;
    
        case SSH_AUTH_PASS_INIT:
          if ((data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
              (strstr(sshc->authlist, "password") != NULL)) {
            state(conn, SSH_AUTH_PASS);
          } else {
            state(conn, SSH_AUTH_HOST_INIT);
          }
          break;
          
        case SSH_AUTH_PASS:
          rc = libssh2_userauth_password(ssh->ssh_session, ssh->user,
                                         ssh->passwd);
          if (rc == LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          else if (rc == 0) {
            sshc->authed = TRUE;
            infof(conn->data, "Initialized password authentication\n");
            state(conn, SSH_AUTH_DONE);
          } else {
            state(conn, SSH_AUTH_HOST_INIT);
          }
          break;
          
        case SSH_AUTH_HOST_INIT:
          if ((data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
              (strstr(sshc->authlist, "hostbased") != NULL)) {
            state(conn, SSH_AUTH_HOST);
          }
          break;
          
        case SSH_AUTH_HOST:
          state(conn, SSH_AUTH_KEY_INIT);
          break;
          
        case SSH_AUTH_KEY_INIT:
          if ((data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
              && (strstr(sshc->authlist, "keyboard-interactive") != NULL)) {
            state(conn, SSH_AUTH_KEY);
          } else {
            state(conn, SSH_AUTH_DONE);
          }        
          break;
          
        case SSH_AUTH_KEY:
          /* Authentication failed. Continue with keyboard-interactive now. */
          rc = libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session,
                                                        ssh->user,
                                                        strlen(ssh->user),
                                                        &kbd_callback);
          if (rc == LIBSSH2_ERROR_EAGAIN) {
            break;
          }
          else if (rc == 0) {
            sshc->authed = TRUE;
            infof(conn->data, "Initialized keyboard interactive authentication\n");
          }
          state(conn, SSH_AUTH_DONE);
          break;
          
        case SSH_AUTH_DONE:
          if (!sshc->authed) {
            failf(data, "Authentication failure");
            libssh2_session_free(ssh->ssh_session);
            ssh->ssh_session = NULL;
            state(conn, SSH_STOP);
            result = CURLE_LOGIN_DENIED;
            break;
          }
          
          /*
           * At this point we have an authenticated ssh session.
           */
          infof(conn->data, "Authentication complete\n");
          
          conn->sockfd = sock;
          conn->writesockfd = CURL_SOCKET_BAD;
    
          if (conn->protocol == PROT_SFTP) {
            state(conn, SSH_SFTP_INIT);
            break;
          }
          state(conn, SSH_GET_WORKINGPATH);
          break;
          
        case SSH_SFTP_INIT:
          /*
           * Start the libssh2 sftp session
           */
          ssh->sftp_session = libssh2_sftp_init(ssh->ssh_session);
          if (!ssh->sftp_session) {
            if (libssh2_session_last_errno(ssh->ssh_session) ==
                LIBSSH2_ERROR_EAGAIN) {
              break;
            } else {
              failf(data, "Failure initialising sftp session\n");
              libssh2_session_free(ssh->ssh_session);
              ssh->ssh_session = NULL;
              state(conn, SSH_STOP);
              result = CURLE_FAILED_INIT;
              break;
            }
          }
          state(conn, SSH_SFTP_REALPATH);
          break;
            
        case SSH_SFTP_REALPATH:
          {
            char tempHome[PATH_MAX];
            
            /*
             * Get the "home" directory
             */
            rc = libssh2_sftp_realpath(ssh->sftp_session, ".",
                                       tempHome, PATH_MAX-1);
            if (rc == LIBSSH2_ERROR_EAGAIN) {
              break;
            }
            else if (rc > 0) {
              /* It seems that this string is not always NULL terminated */
              tempHome[rc] = '\0';
              ssh->homedir = (char *)strdup(tempHome);
              if (!ssh->homedir) {
                libssh2_sftp_shutdown(ssh->sftp_session);
                ssh->sftp_session = NULL;
                libssh2_session_free(ssh->ssh_session);
                ssh->ssh_session = NULL;
                state(conn, SSH_STOP);
                result = CURLE_OUT_OF_MEMORY;
                break;
              }
            } else {
              /* Return the error type */
              result = libssh2_sftp_last_error(ssh->sftp_session);
              DEBUGF(infof(data, "error = %d\n", result));
              state(conn, SSH_STOP);
              break;
            }
            state(conn, SSH_GET_WORKINGPATH);
          }
          break;
          
        case SSH_GET_WORKINGPATH:
          {
            char *real_path;
            char *working_path;
            int working_path_len;
            
            working_path = curl_easy_unescape(data, data->reqdata.path, 0,
                                              &working_path_len);
            if (!working_path) {
              state(conn, SSH_STOP);
              result = CURLE_OUT_OF_MEMORY;
              break;
            }
            
            /* Check for /~/ , indicating relative to the user's home directory */
            if (conn->protocol == PROT_SCP) {
              real_path = (char *)malloc(working_path_len+1);
              if (real_path == NULL) {
                libssh2_session_free(ssh->ssh_session);
                ssh->ssh_session = NULL;
                Curl_safefree(working_path);
                state(conn, SSH_STOP);
                result = CURLE_OUT_OF_MEMORY;
                break;
              }
              if (working_path[1] == '~')
                /* It is referenced to the home directory, so strip the
                   leading '/' */
                memcpy(real_path, working_path+1, 1 + working_path_len-1);
              else
                memcpy(real_path, working_path, 1 + working_path_len);
            }
            else if (conn->protocol == PROT_SFTP) {
              if (working_path[1] == '~') {
                real_path = (char *)malloc(strlen(ssh->homedir) +
                                           working_path_len + 1);
                if (real_path == NULL) {
                  libssh2_sftp_shutdown(ssh->sftp_session);
                  ssh->sftp_session = NULL;
                  libssh2_session_free(ssh->ssh_session);
                  ssh->ssh_session = NULL;
                  Curl_safefree(ssh->homedir);
                  ssh->homedir = NULL;
                  Curl_safefree(working_path);
                  state(conn, SSH_STOP);
                  result = CURLE_OUT_OF_MEMORY;
                  break;
                }
                /* It is referenced to the home directory, so strip the
                   leading '/' */
                memcpy(real_path, ssh->homedir, strlen(ssh->homedir));
                real_path[strlen(ssh->homedir)] = '/';
                real_path[strlen(ssh->homedir)+1] = '\0';
                if (working_path_len > 3) {
                  memcpy(real_path+strlen(ssh->homedir)+1, working_path + 3,
                         1 + working_path_len -3);
                }
              }
              else {
                real_path = (char *)malloc(working_path_len+1);
                if (real_path == NULL) {
                  libssh2_sftp_shutdown(ssh->sftp_session);
                  ssh->sftp_session = NULL;
                  libssh2_session_free(ssh->ssh_session);
                  ssh->ssh_session = NULL;
                  Curl_safefree(ssh->homedir);
                  ssh->homedir = NULL;
                  Curl_safefree(working_path);
                  state(conn, SSH_STOP);
                  result = CURLE_OUT_OF_MEMORY;
                  break;
                }
                memcpy(real_path, working_path, 1+working_path_len);
              }
            }
            else {
              libssh2_session_free(ssh->ssh_session);
              ssh->ssh_session = NULL;
              Curl_safefree(working_path);
              state(conn, SSH_STOP);
              result = CURLE_FAILED_INIT;
              break;
            }
            
            Curl_safefree(working_path);
            ssh->path = real_path;
            
            /* Connect is all done */
            state(conn, SSH_STOP);
          }
          break;
          
        case SSH_QUIT:
          /* fallthrough, just stop! */
        default:
          /* internal error */
          state(conn, SSH_STOP);
          break;
      }
    
      return result;
    }
    
    /* called repeatedly until done from multi.c */
    CURLcode Curl_ssh_multi_statemach(struct connectdata *conn,
                                      bool *done)
    {
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
      int rc = 1;
      struct SessionHandle *data=conn->data;
      struct ssh_conn *sshc = &conn->proto.sshc;
      CURLcode result = CURLE_OK;
    #if 0
      long timeout_ms = ssh_state_timeout(conn);
    #endif
      
      *done = FALSE; /* default to not done yet */
      
    #if 0
      if (timeout_ms <= 0) {
        failf(data, "SSH response timeout");
        return CURLE_OPERATION_TIMEDOUT;
      }
    
      rc = Curl_socket_ready(sshc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                             sshc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                             0);
    #endif
    
      if (rc == -1) {
        failf(data, "select/poll error");
        return CURLE_OUT_OF_MEMORY;
      }
      else if (rc != 0) {
        result = ssh_statemach_act(conn);
        *done = (bool)(sshc->state == SSH_STOP);
      }
      /* if rc == 0, then select() timed out */
    
      return result;
    }
    
    static CURLcode ssh_easy_statemach(struct connectdata *conn)
    {
      curl_socket_t sock = conn->sock[FIRSTSOCKET];
      int rc = 1;
      struct SessionHandle *data=conn->data;
      struct ssh_conn *sshc = &conn->proto.sshc;
      CURLcode result = CURLE_OK;
      
      while(sshc->state != SSH_STOP) {
    #if 0
        long timeout_ms = ssh_state_timeout(conn);
        
        if (timeout_ms <=0 ) {
          failf(data, "SSH response timeout");
          return CURLE_OPERATION_TIMEDOUT; /* already too little time */
        }
    
        rc = Curl_socket_ready(sshc->sendleft?CURL_SOCKET_BAD:sock, /* reading */
                               sshc->sendleft?sock:CURL_SOCKET_BAD, /* writing */
                               (int)timeout_ms);
    #endif
    
        if (rc == -1) {
          failf(data, "select/poll error");
          return CURLE_OUT_OF_MEMORY;
        }
        else if (rc == 0) {
          result = CURLE_OPERATION_TIMEDOUT;
          break;
        }
        else {
          result = ssh_statemach_act(conn);
          if (result)
            break;
        }
      }
    
    return result;
    }
    
    /*
     * SSH setup and connection
     */
    
    static CURLcode ssh_init(struct connectdata *conn)
    
    {
      struct SessionHandle *data = conn->data;
    
      struct SSHPROTO *ssh;
      if (data->reqdata.proto.ssh)
    
      ssh = (struct SSHPROTO *)calloc(sizeof(struct SSHPROTO), 1);
      if (!ssh)
    
        return CURLE_OUT_OF_MEMORY;
    
    
      data->reqdata.proto.ssh = ssh;
    
      /* get some initial data into the ssh struct */
      ssh->bytecountp = &data->reqdata.keep.bytecount;
    
    
      /* no need to duplicate them, this connectdata struct won't change */
    
      ssh->user = conn->user;
      ssh->passwd = conn->passwd;
    
      ssh->errorstr = NULL;
    
      ssh->ssh_session = NULL;
      ssh->ssh_channel = NULL;
      ssh->sftp_session = NULL;
      ssh->sftp_handle = NULL;
    
     * Curl_ssh_connect() gets called from Curl_protocol_connect() to allow us to
    
     * do protocol-specific actions at connect-time.
     */
    
    CURLcode Curl_ssh_connect(struct connectdata *conn, bool *done)
    
      const char *fingerprint;
      const char *authlist;
    
      curl_socket_t sock;
      char *real_path;
      char *working_path;
    
      bool authed = FALSE;
      CURLcode result;
      struct SessionHandle *data = conn->data;
    
    
      ssh = data->reqdata.proto.ssh;
    
    
    #ifdef CURL_LIBSSH2_DEBUG
    
      if (ssh->user) {
        infof(data, "User: %s\n", ssh->user);
    
      if (ssh->passwd) {
        infof(data, "Password: %s\n", ssh->passwd);
    
      }
    #endif /* CURL_LIBSSH2_DEBUG */
      sock = conn->sock[FIRSTSOCKET];
    
      ssh->ssh_session = libssh2_session_init_ex(libssh2_malloc, libssh2_free,
                                                libssh2_realloc, ssh);
      if (ssh->ssh_session == NULL) {
    
        failf(data, "Failure initialising ssh session");
    
        return CURLE_FAILED_INIT;
      }
    
    #ifdef CURL_LIBSSH2_DEBUG
    
      libssh2_trace(ssh->ssh_session, LIBSSH2_TRACE_CONN|LIBSSH2_TRACE_TRANS|
                    LIBSSH2_TRACE_KEX|LIBSSH2_TRACE_AUTH|LIBSSH2_TRACE_SCP|
                    LIBSSH2_TRACE_SFTP|LIBSSH2_TRACE_ERROR|
                    LIBSSH2_TRACE_PUBLICKEY);
    
      infof(data, "SSH socket: %d\n", sock);
    
    #endif /* CURL_LIBSSH2_DEBUG */
    
    
    #if (LIBSSH2_APINO >= 200706012030)
    
      /* Set libssh2 to non-blocking, since cURL is all non-blocking */
      libssh2_session_set_blocking(ssh->ssh_session, 0);
      
      state(conn, SSH_S_STARTUP);
      
      if (data->state.used_interface == Curl_if_multi)
        result = Curl_ssh_multi_statemach(conn, done);
      else {
        result = ssh_easy_statemach(conn);
        if (!result)
          *done = TRUE;
      }
    
      return result;
      (void)authed; /* not used */
      (void)working_path; /* not used */
      (void)working_path_len; /* not used */
      (void)real_path; /* not used */
      (void)tempHome; /* not used */
      (void)authlist; /* not used */
      (void)fingerprint; /* not used */
      (void)i; /* not used */
      
    
    #else /* !(LIBSSH2_APINO >= 200706012030) */
    
      
      if (libssh2_session_startup(ssh->ssh_session, sock)) {
    
        failf(data, "Failure establishing ssh session");
    
        libssh2_session_free(ssh->ssh_session);
        ssh->ssh_session = NULL;
    
        return CURLE_FAILED_INIT;
      }
    
      /*
       * Before we authenticate we should check the hostkey's fingerprint against
       * our known hosts. How that is handled (reading from file, whatever) is
       * up to us. As for know not much is implemented, besides showing how to
       * get the fingerprint.
       */
    
      fingerprint = libssh2_hostkey_hash(ssh->ssh_session,
    
                                         LIBSSH2_HOSTKEY_HASH_MD5);
    
    #ifdef CURL_LIBSSH2_DEBUG
      /* The fingerprint points to static storage (!), don't free() it. */
    
      infof(data, "Fingerprint: ");
    
      for (i = 0; i < 16; i++) {
        infof(data, "%02X ", (unsigned char) fingerprint[i]);
      }
      infof(data, "\n");
    #endif /* CURL_LIBSSH2_DEBUG */
    
      /* TBD - methods to check the host keys need to be done */
    
      /*
       * Figure out authentication methods
       * NB: As soon as we have provided a username to an openssh server we must
       * never change it later. Thus, always specify the correct username here,
       * even though the libssh2 docs kind of indicate that it should be possible
       * to get a 'generic' list (not user-specific) of authentication methods,
       * presumably with a blank username. That won't work in my experience.
       * So always specify it here.
       */
    
      authlist = libssh2_userauth_list(ssh->ssh_session, ssh->user,
                                       strlen(ssh->user));
    
    Dan Fandrich's avatar
    Dan Fandrich committed
      if (!authlist) {
        libssh2_session_free(ssh->ssh_session);
        ssh->ssh_session = NULL;
        return CURLE_OUT_OF_MEMORY;
      }
    
      infof(data, "SSH authentication methods available: %s\n", authlist);
    
    
      /*
       * Check the supported auth types in the order I feel is most secure with the
       * requested type of authentication
       */
      if ((data->set.ssh_auth_types & CURLSSH_AUTH_PUBLICKEY) &&
          (strstr(authlist, "publickey") != NULL)) {
    
        char *home;
    
        const char *passphrase;
        char rsa_pub[PATH_MAX];
        char rsa[PATH_MAX];
    
        rsa_pub[0] = rsa[0] = '\0';
    
    
        /* To ponder about: should really the lib be messing about with the HOME
           environment variable etc? */
        home = curl_getenv("HOME");
    
        if (data->set.ssh_public_key)
          snprintf(rsa_pub, sizeof(rsa_pub), "%s", data->set.ssh_public_key);
    
          snprintf(rsa_pub, sizeof(rsa_pub), "%s/.ssh/id_dsa.pub", home);
    
    
        if (data->set.ssh_private_key)
    
          snprintf(rsa, sizeof(rsa), "%s", data->set.ssh_private_key);
    
          snprintf(rsa, sizeof(rsa), "%s/.ssh/id_dsa", home);
    
    
        passphrase = data->set.key_passwd;
        if (!passphrase)
          passphrase = "";
    
    
        infof(conn->data, "Using ssh public key file %s\n", rsa_pub);
        infof(conn->data, "Using ssh private key file %s\n", rsa);
    
    
        if (rsa_pub[0]) {
          /* The function below checks if the files exists, no need to stat() here.
    
          if (libssh2_userauth_publickey_fromfile(ssh->ssh_session, ssh->user,
                                                  rsa_pub, rsa, passphrase) == 0) {
    
            infof(conn->data, "Initialized SSH public key authentication\n");
    
          }
        }
      }
      if (!authed &&
          (data->set.ssh_auth_types & CURLSSH_AUTH_PASSWORD) &&
          (strstr(authlist, "password") != NULL)) {
    
        if (!libssh2_userauth_password(ssh->ssh_session, ssh->user, ssh->passwd)) {
    
          infof(conn->data, "Initialized password authentication\n");
        }
    
      }
      if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_HOST) &&
          (strstr(authlist, "hostbased") != NULL)) {
      }
      if (!authed && (data->set.ssh_auth_types & CURLSSH_AUTH_KEYBOARD)
          && (strstr(authlist, "keyboard-interactive") != NULL)) {
        /* Authentication failed. Continue with keyboard-interactive now. */
    
        if (!libssh2_userauth_keyboard_interactive_ex(ssh->ssh_session, ssh->user,
                                                      strlen(ssh->user),
                                                      &kbd_callback)) {
    
          infof(conn->data, "Initialized keyboard interactive authentication\n");
    
    Dan Fandrich's avatar
    Dan Fandrich committed
      Curl_safefree((void *)authlist);
      authlist = NULL;
    
        failf(data, "Authentication failure");
    
        libssh2_session_free(ssh->ssh_session);
        ssh->ssh_session = NULL;
    
        return CURLE_LOGIN_DENIED;
    
      }
    
      /*
       * At this point we have an authenticated ssh session.
       */
    
      infof(conn->data, "Authentication complete\n");
    
    
      conn->sockfd = sock;
      conn->writesockfd = CURL_SOCKET_BAD;
    
    
      if (conn->protocol == PROT_SFTP) {
        /*
         * Start the libssh2 sftp session
         */
        ssh->sftp_session = libssh2_sftp_init(ssh->ssh_session);
        if (ssh->sftp_session == NULL) {
          failf(data, "Failure initialising sftp session\n");
          libssh2_session_free(ssh->ssh_session);
          ssh->ssh_session = NULL;
          return CURLE_FAILED_INIT;
        }
    
        /*