session_free(LIBSSH2_SESSION *session)
{
    int rc;
    LIBSSH2_PACKET *pkg;
    LIBSSH2_CHANNEL *ch;
    LIBSSH2_LISTENER *l;
    int packets_left = 0;

    if(session->free_state == libssh2_NB_state_idle) {
        _libssh2_debug(session, LIBSSH2_TRACE_TRANS, "Freeing session resource",
                       session->remote.banner);

        session->free_state = libssh2_NB_state_created;
    }

    if(session->free_state == libssh2_NB_state_created) {
        while((ch = _libssh2_list_first(&session->channels))) {

            rc = _libssh2_channel_free(ch);
            if(rc == LIBSSH2_ERROR_EAGAIN)
                return rc;
        }

        session->free_state = libssh2_NB_state_sent;
    }

    if(session->free_state == libssh2_NB_state_sent) {
        while((l = _libssh2_list_first(&session->listeners))) {
            rc = _libssh2_channel_forward_cancel(l);
            if(rc == LIBSSH2_ERROR_EAGAIN)
                return rc;
        }

        session->free_state = libssh2_NB_state_sent1;
    }

    if(session->state & LIBSSH2_STATE_NEWKEYS) {
        /* hostkey */
        if(session->hostkey && session->hostkey->dtor) {
            session->hostkey->dtor(session, &session->server_hostkey_abstract);
        }

        /* Client to Server */
        /* crypt */
        if(session->local.crypt && session->local.crypt->dtor) {
            session->local.crypt->dtor(session,
                                       &session->local.crypt_abstract);
        }
        /* comp */
        if(session->local.comp && session->local.comp->dtor) {
            session->local.comp->dtor(session, 1,
                                      &session->local.comp_abstract);
        }
        /* mac */
        if(session->local.mac && session->local.mac->dtor) {
            session->local.mac->dtor(session, &session->local.mac_abstract);
        }

        /* Server to Client */
        /* crypt */
        if(session->remote.crypt && session->remote.crypt->dtor) {
            session->remote.crypt->dtor(session,
                                        &session->remote.crypt_abstract);
        }
        /* comp */
        if(session->remote.comp && session->remote.comp->dtor) {
            session->remote.comp->dtor(session, 0,
                                       &session->remote.comp_abstract);
        }
        /* mac */
        if(session->remote.mac && session->remote.mac->dtor) {
            session->remote.mac->dtor(session, &session->remote.mac_abstract);
        }

        /* session_id */
        if(session->session_id) {
            LIBSSH2_FREE(session, session->session_id);
        }
    }

    /* Free banner(s) */
    if(session->remote.banner) {
        LIBSSH2_FREE(session, session->remote.banner);
    }
    if(session->local.banner) {
        LIBSSH2_FREE(session, session->local.banner);
    }

    /* Free preference(s) */
    if(session->kex_prefs) {
        LIBSSH2_FREE(session, session->kex_prefs);
    }
    if(session->hostkey_prefs) {
        LIBSSH2_FREE(session, session->hostkey_prefs);
    }

    if(session->local.kexinit) {
        LIBSSH2_FREE(session, session->local.kexinit);
    }
    if(session->local.crypt_prefs) {
        LIBSSH2_FREE(session, session->local.crypt_prefs);
    }
    if(session->local.mac_prefs) {
        LIBSSH2_FREE(session, session->local.mac_prefs);
    }
    if(session->local.comp_prefs) {
        LIBSSH2_FREE(session, session->local.comp_prefs);
    }
    if(session->local.lang_prefs) {
        LIBSSH2_FREE(session, session->local.lang_prefs);
    }

    if(session->remote.kexinit) {
        LIBSSH2_FREE(session, session->remote.kexinit);
    }
    if(session->remote.crypt_prefs) {
        LIBSSH2_FREE(session, session->remote.crypt_prefs);
    }
    if(session->remote.mac_prefs) {
        LIBSSH2_FREE(session, session->remote.mac_prefs);
    }
    if(session->remote.comp_prefs) {
        LIBSSH2_FREE(session, session->remote.comp_prefs);
    }
    if(session->remote.lang_prefs) {
        LIBSSH2_FREE(session, session->remote.lang_prefs);
    }

    /*
     * Make sure all memory used in the state variables are free
     */
    if(session->kexinit_data) {
        LIBSSH2_FREE(session, session->kexinit_data);
    }
    if(session->startup_data) {
        LIBSSH2_FREE(session, session->startup_data);
    }
    if(session->userauth_list_data) {
        LIBSSH2_FREE(session, session->userauth_list_data);
    }
    if(session->userauth_pswd_data) {
        LIBSSH2_FREE(session, session->userauth_pswd_data);
    }
    if(session->userauth_pswd_newpw) {
        LIBSSH2_FREE(session, session->userauth_pswd_newpw);
    }
    if(session->userauth_host_packet) {
        LIBSSH2_FREE(session, session->userauth_host_packet);
    }
    if(session->userauth_host_method) {
        LIBSSH2_FREE(session, session->userauth_host_method);
    }
    if(session->userauth_host_data) {
        LIBSSH2_FREE(session, session->userauth_host_data);
    }
    if(session->userauth_pblc_data) {
        LIBSSH2_FREE(session, session->userauth_pblc_data);
    }
    if(session->userauth_pblc_packet) {
        LIBSSH2_FREE(session, session->userauth_pblc_packet);
    }
    if(session->userauth_pblc_method) {
        LIBSSH2_FREE(session, session->userauth_pblc_method);
    }
    if(session->userauth_kybd_data) {
        LIBSSH2_FREE(session, session->userauth_kybd_data);
    }
    if(session->userauth_kybd_packet) {
        LIBSSH2_FREE(session, session->userauth_kybd_packet);
    }
    if(session->userauth_kybd_auth_instruction) {
        LIBSSH2_FREE(session, session->userauth_kybd_auth_instruction);
    }
    if(session->open_packet) {
        LIBSSH2_FREE(session, session->open_packet);
    }
    if(session->open_data) {
        LIBSSH2_FREE(session, session->open_data);
    }
    if(session->direct_message) {
        LIBSSH2_FREE(session, session->direct_message);
    }
    if(session->fwdLstn_packet) {
        LIBSSH2_FREE(session, session->fwdLstn_packet);
    }
    if(session->pkeyInit_data) {
        LIBSSH2_FREE(session, session->pkeyInit_data);
    }
    if(session->scpRecv_command) {
        LIBSSH2_FREE(session, session->scpRecv_command);
    }
    if(session->scpSend_command) {
        LIBSSH2_FREE(session, session->scpSend_command);
    }
    if(session->sftpInit_sftp) {
        LIBSSH2_FREE(session, session->sftpInit_sftp);
    }

    /* Free payload buffer */
    if(session->packet.total_num) {
        LIBSSH2_FREE(session, session->packet.payload);
    }

    /* Cleanup all remaining packets */
    while((pkg = _libssh2_list_first(&session->packets))) {
        packets_left++;
        _libssh2_debug(session, LIBSSH2_TRACE_TRANS,
            "packet left with id %d", pkg->data[0]);
        /* unlink the node */
        _libssh2_list_remove(&pkg->node);

        /* free */
        LIBSSH2_FREE(session, pkg->data);
        LIBSSH2_FREE(session, pkg);
    }
    _libssh2_debug(session, LIBSSH2_TRACE_TRANS,
         "Extra packets left %d", packets_left);

    if(session->socket_prev_blockstate) {
        /* if the socket was previously blocking, put it back so */
        rc = session_nonblock(session->socket_fd, 0);
        if(rc) {
            _libssh2_debug(session, LIBSSH2_TRACE_TRANS,
             "unable to reset socket's blocking state");
        }
    }

    if(session->server_hostkey) {
        LIBSSH2_FREE(session, session->server_hostkey);
    }

    /* error string */
    if(session->err_msg && ((session->err_flags & LIBSSH2_ERR_FLAG_DUP) != 0)) {
        LIBSSH2_FREE(session, (char *)session->err_msg);
    }

    LIBSSH2_FREE(session, session);

    return 0;
}