Hi!
Daiki Ueno <[email protected]> writes:
> If it looks OK, I will try to implement it this weekend.
The task took longer than I expected ;-)
The attached is the initial implementation.
Notes:
- The API changed from the proposal after the manner of the knownhost API.
Now the following 7 new functions added. See the
example/simple/ssh2_agent.c for the usage.
LIBSSH2_AGENT *libssh2_agent_init(LIBSSH2_SESSION *session);
int libssh2_agent_connect(LIBSSH2_AGENT *agent);
int libssh2_agent_list_identities(LIBSSH2_AGENT *agent);
int libssh2_agent_get_identity(LIBSSH2_AGENT *agent,
struct libssh2_agent_publickey **store,
struct libssh2_agent_publickey *prev);
int libssh2_agent_userauth(LIBSSH2_AGENT *agent,
const char *username,
struct libssh2_agent_publickey *identity);
int libssh2_agent_disconnect(LIBSSH2_AGENT *agent);
int libssh2_agent_free(LIBSSH2_AGENT *agent);
- libssh2_agent_{add,del}_identity() could also be added. It will be
useful for testing, but it will require extension to the internal
hostkey interface to encode DSA/RSA parameters in the agent protocol.
- Pageant support is not yet implemented but it should be easy if I take
some code from the Putty source (putty/windows/winpgnt.c). Is that
OK? The Putty License is the MIT License.
I would appreciate any comments.
Regards,
--
Daiki Ueno
>From 0d68e06f170781e2f54d1eb1de073e36a1dcf699 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <[email protected]>
Date: Wed, 16 Dec 2009 08:46:06 +0900
Subject: [PATCH 1/3] Add callback-based API for publickey auth.
---
include/libssh2.h | 13 ++++
src/userauth.c | 179 ++++++++++++++++++++++++++++++++++++++--------------
2 files changed, 144 insertions(+), 48 deletions(-)
diff --git a/include/libssh2.h b/include/libssh2.h
index 807f6a2..f771eca 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -178,6 +178,11 @@ typedef struct _LIBSSH2_USERAUTH_KBDINT_RESPONSE
unsigned int length;
} LIBSSH2_USERAUTH_KBDINT_RESPONSE;
+/* 'publickey' authentication callback */
+#define LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC(name) \
+ int name(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len, \
+ const unsigned char *data, size_t data_len, void **abstract)
+
/* 'keyboard-interactive' authentication callback */
#define LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC(name_) \
void name_(const char* name, int name_len, const char* instruction, \
@@ -437,6 +442,14 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session,
(privatekey), (passphrase))
LIBSSH2_API int
+libssh2_userauth_publickey(LIBSSH2_SESSION *session,
+ const char *username,
+ const unsigned char *pubkeydata,
+ size_t pubkeydata_len,
+ LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+ void **abstract);
+
+LIBSSH2_API int
libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session,
const char *username,
unsigned int username_len,
diff --git a/src/userauth.c b/src/userauth.c
index 3ee338e..6e28460 100644
--- a/src/userauth.c
+++ b/src/userauth.c
@@ -613,6 +613,45 @@ file_read_privatekey(LIBSSH2_SESSION * session,
return 0;
}
+struct privkey_file {
+ const char *filename;
+ const char *passphrase;
+};
+
+static int
+sign_fromfile(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len,
+ const unsigned char *data, size_t data_len, void **abstract)
+{
+ struct privkey_file *privkey_file = (struct privkey_file *) (*abstract);
+ const LIBSSH2_HOSTKEY_METHOD *privkeyobj;
+ void *hostkey_abstract;
+ struct iovec datavec;
+
+ if (file_read_privatekey(session, &privkeyobj, &hostkey_abstract,
+ session->userauth_pblc_method,
+ session->userauth_pblc_method_len,
+ privkey_file->filename,
+ privkey_file->passphrase)) {
+ return -1;
+ }
+
+ datavec.iov_base = (unsigned char *)data;
+ datavec.iov_len = data_len;
+
+ if (privkeyobj->signv(session, sig, sig_len, 1, &datavec,
+ &hostkey_abstract)) {
+ if (privkeyobj->dtor) {
+ privkeyobj->dtor(session, abstract);
+ }
+ return -1;
+ }
+
+ if (privkeyobj->dtor) {
+ privkeyobj->dtor(session, &hostkey_abstract);
+ }
+ return 0;
+}
+
/* userauth_hostbased_fromfile
@@ -885,19 +924,15 @@ libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session,
-/*
- * userauth_publickey_fromfile
- * Authenticate using a keypair found in the named files
- */
static int
-userauth_publickey_fromfile(LIBSSH2_SESSION *session,
- const char *username,
- unsigned int username_len,
- const char *publickey,
- const char *privatekey,
- const char *passphrase)
+userauth_publickey(LIBSSH2_SESSION *session,
+ const char *username,
+ unsigned int username_len,
+ const unsigned char *pubkeydata,
+ unsigned long pubkeydata_len,
+ LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+ void *abstract)
{
- unsigned long pubkeydata_len = 0;
unsigned char reply_codes[4] =
{ SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE,
SSH_MSG_USERAUTH_PK_OK, 0
@@ -905,17 +940,20 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
int rc;
if (session->userauth_pblc_state == libssh2_NB_state_idle) {
- unsigned char *pubkeydata;
-
/* Zero the whole thing out */
memset(&session->userauth_pblc_packet_requirev_state, 0,
sizeof(session->userauth_pblc_packet_requirev_state));
- if (file_read_publickey(session, &session->userauth_pblc_method,
- &session->userauth_pblc_method_len,
- &pubkeydata, &pubkeydata_len,publickey)) {
+ session->userauth_pblc_method_len = _libssh2_ntohu32(pubkeydata);
+ session->userauth_pblc_method =
+ LIBSSH2_ALLOC(session, session->userauth_pblc_method_len);
+ if (!session->userauth_pblc_method) {
+ libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for public key data", 0);
return -1;
}
+ memcpy(session->userauth_pblc_method, pubkeydata + 4,
+ session->userauth_pblc_method_len);
/*
* 45 = packet_type(1) + username_len(4) + servicename_len(4) +
@@ -940,7 +978,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
if (!session->userauth_pblc_packet) {
LIBSSH2_FREE(session, session->userauth_pblc_method);
session->userauth_pblc_method = NULL;
- LIBSSH2_FREE(session, pubkeydata);
return -1;
}
@@ -975,7 +1012,6 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
session->userauth_pblc_s += 4;
memcpy(session->userauth_pblc_s, pubkeydata, pubkeydata_len);
session->userauth_pblc_s += pubkeydata_len;
- LIBSSH2_FREE(session, pubkeydata);
_libssh2_debug(session, LIBSSH2_TRACE_AUTH,
"Attempting publickey authentication");
@@ -1003,10 +1039,7 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
}
if (session->userauth_pblc_state == libssh2_NB_state_sent) {
- const LIBSSH2_HOSTKEY_METHOD *privkeyobj;
- void *abstract;
- unsigned char buf[5];
- struct iovec datavec[4];
+ unsigned char *buf, *s;
unsigned char *sig;
unsigned long sig_len;
@@ -1063,43 +1096,37 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
LIBSSH2_FREE(session, session->userauth_pblc_data);
session->userauth_pblc_data = NULL;
- if (file_read_privatekey(session, &privkeyobj, &abstract,
- session->userauth_pblc_method,
- session->userauth_pblc_method_len,
- privatekey, passphrase)) {
- LIBSSH2_FREE(session, session->userauth_pblc_method);
- session->userauth_pblc_method = NULL;
- LIBSSH2_FREE(session, session->userauth_pblc_packet);
- session->userauth_pblc_packet = NULL;
- session->userauth_pblc_state = libssh2_NB_state_idle;
- return -1;
- }
-
*session->userauth_pblc_b = 0x01;
- _libssh2_htonu32(buf, session->session_id_len);
- datavec[0].iov_base = buf;
- datavec[0].iov_len = 4;
- datavec[1].iov_base = session->session_id;
- datavec[1].iov_len = session->session_id_len;
- datavec[2].iov_base = session->userauth_pblc_packet;
- datavec[2].iov_len = session->userauth_pblc_packet_len;
+ s = buf = LIBSSH2_ALLOC(session, 4 + session->session_id_len
+ + session->userauth_pblc_packet_len);
+ if (!buf) {
+ libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for userauth-publickey "
+ "signed data", 0);
+ return -1;
+ }
- if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) {
+ _libssh2_htonu32(s, session->session_id_len);
+ s += 4;
+ memcpy (s, session->session_id, session->session_id_len);
+ s += session->session_id_len;
+ memcpy (s, session->userauth_pblc_packet,
+ session->userauth_pblc_packet_len);
+ s += session->userauth_pblc_packet_len;
+
+ if (sign_callback(session, &sig, &sig_len, buf, s - buf, abstract)) {
+ LIBSSH2_FREE(session, buf);
LIBSSH2_FREE(session, session->userauth_pblc_method);
session->userauth_pblc_method = NULL;
LIBSSH2_FREE(session, session->userauth_pblc_packet);
session->userauth_pblc_packet = NULL;
- if (privkeyobj->dtor) {
- privkeyobj->dtor(session, &abstract);
- }
session->userauth_pblc_state = libssh2_NB_state_idle;
return -1;
}
- if (privkeyobj->dtor) {
- privkeyobj->dtor(session, &abstract);
- }
+
+ LIBSSH2_FREE(session, buf);
/*
* If this function was restarted, pubkeydata_len might still be 0
@@ -1214,6 +1241,43 @@ userauth_publickey_fromfile(LIBSSH2_SESSION *session,
return -1;
}
+/*
+ * userauth_publickey_fromfile
+ * Authenticate using a keypair found in the named files
+ */
+static int
+userauth_publickey_fromfile(LIBSSH2_SESSION *session,
+ const char *username,
+ unsigned int username_len,
+ const char *publickey,
+ const char *privatekey,
+ const char *passphrase)
+{
+ unsigned char *pubkeydata = NULL;
+ unsigned long pubkeydata_len = 0;
+ struct privkey_file privkey_file;
+ void *abstract = &privkey_file;
+ int rc;
+
+ privkey_file.filename = privatekey;
+ privkey_file.passphrase = passphrase;
+
+ if (session->userauth_pblc_state == libssh2_NB_state_idle) {
+ if (file_read_publickey(session, &session->userauth_pblc_method,
+ &session->userauth_pblc_method_len,
+ &pubkeydata, &pubkeydata_len, publickey)) {
+ return -1;
+ }
+ }
+
+ rc = userauth_publickey(session, username, username_len,
+ pubkeydata, pubkeydata_len,
+ sign_fromfile, &abstract);
+ LIBSSH2_FREE(session, pubkeydata);
+
+ return rc;
+}
+
/* libssh2_userauth_publickey_fromfile_ex
* Authenticate using a keypair found in the named files
*/
@@ -1233,6 +1297,25 @@ libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session,
return rc;
}
+/* libssh2_userauth_publickey_ex
+ * Authenticate using an external callback function
+ */
+LIBSSH2_API int
+libssh2_userauth_publickey(LIBSSH2_SESSION *session,
+ const char *user,
+ const unsigned char *pubkeydata,
+ size_t pubkeydata_len,
+ LIBSSH2_USERAUTH_PUBLICKEY_SIGN_FUNC((*sign_callback)),
+ void **abstract)
+{
+ int rc;
+ BLOCK_ADJUST(rc, session,
+ userauth_publickey(session, user, strlen(user),
+ pubkeydata, pubkeydata_len,
+ sign_callback, abstract));
+ return rc;
+}
+
/*
--
1.6.5.4
>From 17ee5d33a7adb0bd18669bb0e28225dea1064d47 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <[email protected]>
Date: Wed, 16 Dec 2009 08:49:57 +0900
Subject: [PATCH 2/3] Add initial ssh-agent API.
---
Makefile.inc | 2 +-
configure.ac | 1 +
include/libssh2.h | 93 ++++++++
src/agent.c | 634 +++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 729 insertions(+), 1 deletions(-)
create mode 100644 src/agent.c
diff --git a/Makefile.inc b/Makefile.inc
index 53e7a51..47a6b7a 100644
--- a/Makefile.inc
+++ b/Makefile.inc
@@ -1,5 +1,5 @@
CSOURCES = channel.c comp.c crypt.c hostkey.c kex.c mac.c misc.c \
packet.c publickey.c scp.c session.c sftp.c userauth.c transport.c \
- version.c knownhost.c openssl.c libgcrypt.c pem.c
+ version.c knownhost.c agent.c openssl.c libgcrypt.c pem.c
HHEADERS = libssh2_priv.h openssl.h libgcrypt.h transport.h channel.h comp.h mac.h misc.h
diff --git a/configure.ac b/configure.ac
index a1dbaf6..fde806f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -206,6 +206,7 @@ AC_HELP_STRING([--disable-hidden-symbols],[Leave all symbols with default visibi
AC_CHECK_HEADERS([errno.h fcntl.h stdio.h stdlib.h unistd.h sys/uio.h])
AC_CHECK_HEADERS([sys/select.h sys/socket.h sys/ioctl.h sys/time.h])
AC_CHECK_HEADERS([arpa/inet.h netinet/in.h])
+AC_CHECK_HEADERS([sys/un.h])
case $host in
*-*-cygwin* | *-*-cegcc*)
diff --git a/include/libssh2.h b/include/libssh2.h
index f771eca..9be60f4 100644
--- a/include/libssh2.h
+++ b/include/libssh2.h
@@ -247,6 +247,7 @@ typedef struct _LIBSSH2_SESSION LIBSSH2_SESSION;
typedef struct _LIBSSH2_CHANNEL LIBSSH2_CHANNEL;
typedef struct _LIBSSH2_LISTENER LIBSSH2_LISTENER;
typedef struct _LIBSSH2_KNOWNHOSTS LIBSSH2_KNOWNHOSTS;
+typedef struct _LIBSSH2_AGENT LIBSSH2_AGENT;
typedef struct _LIBSSH2_POLLFD {
unsigned char type; /* LIBSSH2_POLLFD_* below */
@@ -364,6 +365,7 @@ typedef struct _LIBSSH2_POLLFD {
#define LIBSSH2_ERROR_BAD_USE -39
#define LIBSSH2_ERROR_COMPRESS -40
#define LIBSSH2_ERROR_OUT_OF_BOUNDARY -41
+#define LIBSSH2_ERROR_AGENT_PROTOCOL -42
/* Session API */
LIBSSH2_API LIBSSH2_SESSION *
@@ -883,6 +885,97 @@ libssh2_knownhost_get(LIBSSH2_KNOWNHOSTS *hosts,
struct libssh2_knownhost **store,
struct libssh2_knownhost *prev);
+#define HAVE_LIBSSH2_AGENT_API 0x010202 /* since 1.2.2 */
+
+struct libssh2_agent_publickey {
+ unsigned int magic; /* magic stored by the library */
+ void *node; /* handle to the internal representation of key */
+ unsigned char *blob; /* public key blob */
+ size_t blob_len; /* length of the public key blob */
+ char *comment; /* comment in printable format */
+};
+
+/*
+ * libssh2_agent_init
+ *
+ * Init an ssh-agent handle. Returns the pointer to the handle.
+ *
+ */
+LIBSSH2_API LIBSSH2_AGENT *
+libssh2_agent_init(LIBSSH2_SESSION *session);
+
+/*
+ * libssh2_agent_connect()
+ *
+ * Connect to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_connect(LIBSSH2_AGENT *agent);
+
+/*
+ * libssh2_agent_list_identities()
+ *
+ * Request ssh-agent to list identities.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_list_identities(LIBSSH2_AGENT *agent);
+
+/*
+ * libssh2_agent_get_identity()
+ *
+ * Traverse the internal list of public keys. Pass NULL to 'prev' to get
+ * the first one. Or pass a poiner to the previously returned one to get the
+ * next.
+ *
+ * Returns:
+ * 0 if a fine public key was stored in 'store'
+ * 1 if end of public keys
+ * [negative] on errors
+ */
+LIBSSH2_API int
+libssh2_agent_get_identity(LIBSSH2_AGENT *agent,
+ struct libssh2_agent_publickey **store,
+ struct libssh2_agent_publickey *prev);
+
+/*
+ * libssh2_agent_userauth()
+ *
+ * Do publickey user authentication with the help of ssh-agent.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_userauth(LIBSSH2_AGENT *agent,
+ const char *username,
+ struct libssh2_agent_publickey *identity);
+
+/*
+ * libssh2_agent_disconnect()
+ *
+ * Close a connection to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_disconnect(LIBSSH2_AGENT *agent);
+
+/*
+ * libssh2_agent_free()
+ *
+ * Free a connection to an ssh-agent. This function also frees the
+ * internal list of public keys.
+ */
+LIBSSH2_API int
+libssh2_agent_free(LIBSSH2_AGENT *agent);
+
/* NOTE NOTE NOTE
libssh2_trace() has no function in builds that aren't built with debug
enabled
diff --git a/src/agent.c b/src/agent.c
new file mode 100644
index 0000000..9814abf
--- /dev/null
+++ b/src/agent.c
@@ -0,0 +1,634 @@
+/*
+ * Copyright (c) 2009 by Daiki Ueno
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms,
+ * with or without modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the copyright holder nor the names
+ * of any other contributors may be used to endorse or
+ * promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
+ * OF SUCH DAMAGE.
+ */
+
+#include "libssh2_priv.h"
+#include "misc.h"
+#include <errno.h>
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+
+/* Requests from client to agent for protocol 1 key operations */
+#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1
+#define SSH_AGENTC_RSA_CHALLENGE 3
+#define SSH_AGENTC_ADD_RSA_IDENTITY 7
+#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8
+#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9
+#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24
+
+/* Requests from client to agent for protocol 2 key operations */
+#define SSH2_AGENTC_REQUEST_IDENTITIES 11
+#define SSH2_AGENTC_SIGN_REQUEST 13
+#define SSH2_AGENTC_ADD_IDENTITY 17
+#define SSH2_AGENTC_REMOVE_IDENTITY 18
+#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19
+#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25
+
+/* Key-type independent requests from client to agent */
+#define SSH_AGENTC_ADD_SMARTCARD_KEY 20
+#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21
+#define SSH_AGENTC_LOCK 22
+#define SSH_AGENTC_UNLOCK 23
+#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26
+
+/* Generic replies from agent to client */
+#define SSH_AGENT_FAILURE 5
+#define SSH_AGENT_SUCCESS 6
+
+/* Replies from agent to client for protocol 1 key operations */
+#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2
+#define SSH_AGENT_RSA_RESPONSE 4
+
+/* Replies from agent to client for protocol 2 key operations */
+#define SSH2_AGENT_IDENTITIES_ANSWER 12
+#define SSH2_AGENT_SIGN_RESPONSE 14
+
+/* Key constraint identifiers */
+#define SSH_AGENT_CONSTRAIN_LIFETIME 1
+#define SSH_AGENT_CONSTRAIN_CONFIRM 2
+
+typedef enum {
+ agent_NB_state_init = 0,
+ agent_NB_state_request_created,
+ agent_NB_state_request_length_sent,
+ agent_NB_state_request_sent,
+ agent_NB_state_response_length_received,
+ agent_NB_state_response_received
+} agent_nonblocking_states;
+
+typedef struct agent_transaction_ctx {
+ unsigned char *request;
+ size_t request_len;
+ unsigned char *response;
+ size_t response_len;
+ agent_nonblocking_states state;
+} *agent_transaction_ctx_t;
+
+typedef int (*agent_connect_func)(LIBSSH2_AGENT *agent);
+typedef int (*agent_transact_func)(LIBSSH2_AGENT *agent,
+ agent_transaction_ctx_t transctx);
+typedef int (*agent_disconnect_func)(LIBSSH2_AGENT *agent);
+
+struct agent_publickey {
+ struct list_node node;
+
+ /* this is the struct we expose externally */
+ struct libssh2_agent_publickey external;
+};
+
+struct _LIBSSH2_AGENT
+{
+ LIBSSH2_SESSION *session; /* the session this "belongs to" */
+
+ union {
+ int fd;
+ } u;
+
+ agent_connect_func connect;
+ agent_transact_func transact;
+ agent_disconnect_func disconnect;
+
+ struct agent_transaction_ctx transctx;
+ struct agent_publickey *identity;
+ struct list_head head; /* list of public keys */
+};
+
+static int
+agent_connect_unix(LIBSSH2_AGENT *agent)
+{
+ const char *path;
+ struct sockaddr_un sun;
+
+ path = getenv("SSH_AUTH_SOCK");
+ if (!path) {
+ return -1;
+ }
+
+ agent->u.fd = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (agent->u.fd < -1) {
+ return -1;
+ }
+
+ sun.sun_family = AF_UNIX;
+ strncpy (sun.sun_path, path, sizeof sun.sun_path);
+ if (connect(agent->u.fd, (struct sockaddr*)(&sun), sizeof sun) != 0) {
+ close (agent->u.fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+agent_transact_unix(LIBSSH2_AGENT *agent, agent_transaction_ctx_t transctx)
+{
+ unsigned char buf[4], *s;
+ int rc;
+
+ /* Send the length of the request */
+ if (transctx->state == agent_NB_state_request_created) {
+ _libssh2_htonu32(buf, transctx->request_len);
+ rc = send(agent->u.fd, buf, sizeof buf, 0);
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ return -1;
+ }
+ transctx->state = agent_NB_state_request_length_sent;
+ }
+
+ /* Send the request body */
+ if (transctx->state == agent_NB_state_request_length_sent) {
+ rc = send(agent->u.fd, transctx->request,
+ transctx->request_len, 0);
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ return -1;
+ }
+ transctx->state = agent_NB_state_request_sent;
+ }
+
+ /* Receive the length of a response */
+ if (transctx->state == agent_NB_state_request_sent) {
+ rc = recv(agent->u.fd, buf, sizeof buf, 0);
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ return -1;
+ }
+ transctx->response_len = _libssh2_ntohu32(buf);
+ s = transctx->response = LIBSSH2_ALLOC(agent->session,
+ transctx->response_len);
+ if (!transctx->response) {
+ return LIBSSH2_ERROR_ALLOC;
+ }
+ transctx->state = agent_NB_state_response_length_received;
+ }
+
+ /* Receive the response body */
+ if (transctx->state == agent_NB_state_response_length_received) {
+ rc = recv(agent->u.fd, transctx->response, transctx->response_len, 0);
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return LIBSSH2_ERROR_EAGAIN;
+ return -1;
+ }
+ transctx->state = agent_NB_state_response_received;
+ }
+
+ return 0;
+}
+
+static int
+agent_disconnect_unix(LIBSSH2_AGENT *agent)
+{
+ return close(agent->u.fd);
+}
+
+static int
+agent_sign(LIBSSH2_SESSION *session, unsigned char **sig, size_t *sig_len,
+ const unsigned char *data, size_t data_len, void **abstract)
+{
+ LIBSSH2_AGENT *agent = (LIBSSH2_AGENT *) (*abstract);
+ agent_transaction_ctx_t transctx = &agent->transctx;
+ struct agent_publickey *identity = agent->identity;
+ ssize_t len = 1 + 4 + identity->external.blob_len + 4 + data_len + 4;
+ ssize_t method_len;
+ unsigned char *s;
+ int rc;
+
+ /* Create a request to sign the data */
+ if (transctx->state == agent_NB_state_init) {
+ s = transctx->request = LIBSSH2_ALLOC(session, len);
+ if (!transctx->request) {
+ return LIBSSH2_ERROR_ALLOC;
+ }
+
+ *s++ = SSH2_AGENTC_SIGN_REQUEST;
+ /* key blob */
+ _libssh2_htonu32(s, identity->external.blob_len);
+ s += 4;
+ memcpy(s, identity->external.blob, identity->external.blob_len);
+ s += identity->external.blob_len;
+ /* data */
+ _libssh2_htonu32(s, data_len);
+ s += 4;
+ memcpy(s, data, data_len);
+ s += data_len;
+ /* flags */
+ _libssh2_htonu32(s, 0);
+ s += 4;
+ transctx->request_len = s - transctx->request;
+ transctx->state = agent_NB_state_request_created;
+ }
+
+ /* Make sure to be re-called as a result of EAGAIN. */
+ if (*transctx->request != SSH2_AGENTC_SIGN_REQUEST) {
+ return LIBSSH2_ERROR_BAD_USE;
+ }
+
+ rc = agent->transact(agent, transctx);
+ if (rc) {
+ goto error;
+ }
+ LIBSSH2_FREE(session, transctx->request);
+ transctx->request = NULL;
+
+ len = transctx->response_len;
+ s = transctx->response;
+ len--;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ if (*s != SSH2_AGENT_SIGN_RESPONSE) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ s++;
+
+ /* Skip the entire length of the signature */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ s += 4;
+
+ /* Skip signing method */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ method_len = _libssh2_ntohu32(s);
+ s += 4;
+ len -= method_len;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ s += method_len;
+
+ /* Read the signature */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ *sig_len = _libssh2_ntohu32(s);
+ s += 4;
+ len -= *sig_len;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+
+ *sig = LIBSSH2_ALLOC(session, *sig_len);
+ if (!*sig) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ goto error;
+ }
+ memcpy(*sig, s, *sig_len);
+
+ error:
+ LIBSSH2_FREE(session, transctx->request);
+ transctx->request = NULL;
+
+ LIBSSH2_FREE(session, transctx->response);
+ transctx->response = NULL;
+
+ return rc;
+}
+
+static int
+agent_list_identities(LIBSSH2_AGENT *agent)
+{
+ agent_transaction_ctx_t transctx = &agent->transctx;
+ ssize_t len, num_identities;
+ unsigned char *s;
+ int rc;
+
+ /* Create a request to list identities */
+ if (transctx->state == agent_NB_state_init) {
+ unsigned char c = SSH2_AGENTC_REQUEST_IDENTITIES;
+ transctx->request = &c;
+ transctx->request_len = 1;
+ transctx->state = agent_NB_state_request_created;
+ }
+
+ /* Make sure to be re-called as a result of EAGAIN. */
+ if (*transctx->request != SSH2_AGENTC_REQUEST_IDENTITIES) {
+ return LIBSSH2_ERROR_BAD_USE;
+ }
+
+ rc = agent->transact(agent, transctx);
+ if (rc) {
+ goto error;
+ }
+ transctx->request = NULL;
+
+ len = transctx->response_len;
+ s = transctx->response;
+ len--;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ if (*s != SSH2_AGENT_IDENTITIES_ANSWER) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ s++;
+
+ /* Read the length of identities */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ num_identities = _libssh2_ntohu32(s);
+ s += 4;
+
+ while (num_identities--) {
+ struct agent_publickey *identity;
+ ssize_t comment_len;
+
+ identity = LIBSSH2_ALLOC(agent->session, sizeof *identity);
+ if (!identity) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ goto error;
+ }
+
+ /* Read the length of the blob */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ identity->external.blob_len = _libssh2_ntohu32(s);
+ s += 4;
+
+ /* Read the blob */
+ len -= identity->external.blob_len;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ identity->external.blob = LIBSSH2_ALLOC(agent->session,
+ identity->external.blob_len);
+ if (!identity->external.blob) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ goto error;
+ }
+ memcpy(identity->external.blob, s, identity->external.blob_len);
+ s += identity->external.blob_len;
+
+ /* Read the length of the comment */
+ len -= 4;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ comment_len = _libssh2_ntohu32(s);
+ s += 4;
+
+ /* Read the comment */
+ len -= comment_len;
+ if (len < 0) {
+ rc = LIBSSH2_ERROR_AGENT_PROTOCOL;
+ goto error;
+ }
+ identity->external.comment = LIBSSH2_ALLOC(agent->session,
+ comment_len + 1);
+ if (!identity->external.comment) {
+ rc = LIBSSH2_ERROR_ALLOC;
+ goto error;
+ }
+ identity->external.comment[comment_len] = '\0';
+ memcpy(identity->external.comment, s, comment_len);
+ s += comment_len;
+
+ _libssh2_list_add(&agent->head, &identity->node);
+ }
+ error:
+ LIBSSH2_FREE(agent->session, transctx->response);
+ transctx->response = NULL;
+
+ return rc;
+}
+
+#define AGENT_PUBLICKEY_MAGIC 0x3bdefed2
+/*
+ * agent_publickey_to_external()
+ *
+ * Copies data from the internal to the external representation struct.
+ *
+ */
+static struct libssh2_agent_publickey *
+agent_publickey_to_external(struct agent_publickey *node)
+{
+ struct libssh2_agent_publickey *ext = &node->external;
+
+ ext->magic = AGENT_PUBLICKEY_MAGIC;
+ ext->node = node;
+
+ return ext;
+}
+
+/*
+ * libssh2_agent_init
+ *
+ * Init an ssh-agent handle. Returns the pointer to the handle.
+ *
+ */
+LIBSSH2_API LIBSSH2_AGENT *
+libssh2_agent_init(LIBSSH2_SESSION *session)
+{
+ LIBSSH2_AGENT *agent;
+
+ agent = LIBSSH2_ALLOC(session, sizeof *agent);
+ if (!agent) {
+ libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate space for agent connection", 0);
+ return NULL;
+ }
+ memset(agent, 0, sizeof *agent);
+ agent->session = session;
+ agent->connect = agent_connect_unix;
+ agent->transact = agent_transact_unix;
+ agent->disconnect = agent_disconnect_unix;
+
+ return agent;
+}
+
+/*
+ * libssh2_agent_connect()
+ *
+ * Connect to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_connect(LIBSSH2_AGENT *agent)
+{
+ int rc;
+ BLOCK_ADJUST(rc, agent->session, agent->connect(agent));
+ return rc;
+}
+
+/*
+ * libssh2_agent_list_identities()
+ *
+ * Request ssh-agent to list identities.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_list_identities(LIBSSH2_AGENT *agent)
+{
+ int rc;
+
+ memset(&agent->transctx, 0, sizeof agent->transctx);
+ BLOCK_ADJUST(rc, agent->session, agent_list_identities(agent));
+ return rc;
+}
+
+/*
+ * libssh2_agent_get_identity()
+ *
+ * Traverse the internal list of public keys. Pass NULL to 'prev' to get
+ * the first one. Or pass a poiner to the previously returned one to get the
+ * next.
+ *
+ * Returns:
+ * 0 if a fine public key was stored in 'store'
+ * 1 if end of public keys
+ * [negative] on errors
+ */
+LIBSSH2_API int
+libssh2_agent_get_identity(LIBSSH2_AGENT *agent,
+ struct libssh2_agent_publickey **ext,
+ struct libssh2_agent_publickey *oprev)
+{
+ struct agent_publickey *node;
+ if (oprev && oprev->node) {
+ /* we have a starting point */
+ struct agent_publickey *prev = oprev->node;
+
+ /* get the next node in the list */
+ node = _libssh2_list_next(&prev->node);
+ }
+ else
+ node = _libssh2_list_first(&agent->head);
+
+ if (!node)
+ /* no (more) node */
+ return 1;
+
+ *ext = agent_publickey_to_external(node);
+
+ return 0;
+}
+
+/*
+ * libssh2_agent_userauth()
+ *
+ * Do publickey user authentication with the help of ssh-agent.
+ *
+ * Returns 0 if succeeded, or a negative value for error.
+ */
+LIBSSH2_API int
+libssh2_agent_userauth(LIBSSH2_AGENT *agent,
+ const char *username,
+ struct libssh2_agent_publickey *identity)
+{
+ void *abstract = agent;
+ int rc;
+
+ memset(&agent->transctx, 0, sizeof agent->transctx);
+ agent->identity = identity->node;
+ BLOCK_ADJUST(rc, agent->session,
+ libssh2_userauth_publickey(agent->session, username,
+ identity->blob,
+ identity->blob_len,
+ agent_sign,
+ &abstract));
+ return rc;
+}
+
+/*
+ * libssh2_agent_disconnect()
+ *
+ * Close a connection to an ssh-agent.
+ *
+ * Returns:
+ * NULL if no ssh-agent is running, or failed to connect
+ * a pointer to a LIBSSH2_AGENT if successfully connected
+ */
+LIBSSH2_API int
+libssh2_agent_disconnect(LIBSSH2_AGENT *agent)
+{
+ int rc;
+ BLOCK_ADJUST(rc, agent->session, agent->disconnect(agent));
+ return rc;
+}
+
+/*
+ * libssh2_agent_free()
+ *
+ * Free a connection to an ssh-agent. This function also frees the
+ * internal list of public keys.
+ */
+LIBSSH2_API int
+libssh2_agent_free(LIBSSH2_AGENT *agent) {
+ struct agent_publickey *node;
+ struct agent_publickey *next;
+
+ for (node = _libssh2_list_first(&agent->head); node; node = next) {
+ next = _libssh2_list_next(&node->node);
+ LIBSSH2_FREE(agent->session, node->external.blob);
+ LIBSSH2_FREE(agent->session, node->external.comment);
+ LIBSSH2_FREE(agent->session, node);
+ }
+ LIBSSH2_FREE(agent->session, agent);
+ return 0;
+}
--
1.6.5.4
>From d8b53f493791f26e54f5ae22095571b0d5a5cfe1 Mon Sep 17 00:00:00 2001
From: Daiki Ueno <[email protected]>
Date: Wed, 16 Dec 2009 08:50:25 +0900
Subject: [PATCH 3/3] Add an example to use ssh-agent API.
---
example/simple/Makefile.am | 2 +-
example/simple/ssh2_agent.c | 229 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 230 insertions(+), 1 deletions(-)
create mode 100644 example/simple/ssh2_agent.c
diff --git a/example/simple/Makefile.am b/example/simple/Makefile.am
index cfa16d9..c396fdf 100644
--- a/example/simple/Makefile.am
+++ b/example/simple/Makefile.am
@@ -8,7 +8,7 @@ noinst_PROGRAMS = ssh2 \
sftp_write sftp_write_nonblock \
sftp_mkdir sftp_mkdir_nonblock \
sftp_RW_nonblock \
- sftpdir sftpdir_nonblock ssh2_exec
+ sftpdir sftpdir_nonblock ssh2_exec ssh2_agent
# the examples need the $(top_builddir)/src since when building outside of the
# source dir they still need to reach the libssh2_config.h header
diff --git a/example/simple/ssh2_agent.c b/example/simple/ssh2_agent.c
new file mode 100644
index 0000000..44335ba
--- /dev/null
+++ b/example/simple/ssh2_agent.c
@@ -0,0 +1,229 @@
+/*
+ * Sample showing how to do SSH2 connect using ssh-agent.
+ *
+ * The sample code has default values for host name, user name:
+ *
+ * "ssh2_agent host user"
+ */
+
+#include "libssh2_config.h"
+#include <libssh2.h>
+#include <libssh2_sftp.h>
+
+#ifdef HAVE_WINDOWS_H
+# include <windows.h>
+#endif
+#ifdef HAVE_WINSOCK2_H
+# include <winsock2.h>
+#endif
+#ifdef HAVE_SYS_SOCKET_H
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_NETINET_IN_H
+# include <netinet/in.h>
+#endif
+# ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+# ifdef HAVE_ARPA_INET_H
+#include <arpa/inet.h>
+#endif
+
+#include <sys/types.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <sys/un.h>
+#include <stdlib.h>
+
+const char *username="username";
+
+int main(int argc, char *argv[])
+{
+ unsigned long hostaddr;
+ int sock = -1, i, j, rc;
+ struct sockaddr_in sin;
+ const char *fingerprint;
+ char *userauthlist;
+ LIBSSH2_SESSION *session;
+ LIBSSH2_CHANNEL *channel;
+ LIBSSH2_AGENT *agent = NULL;
+ struct libssh2_agent_publickey *identity, *prev_identity = NULL;
+#ifdef WIN32
+ WSADATA wsadata;
+
+ WSAStartup(MAKEWORD(2,0), &wsadata);
+#endif
+
+ if (argc > 1) {
+ hostaddr = inet_addr(argv[1]);
+ } else {
+ hostaddr = htonl(0x7F000001);
+ }
+
+ if(argc > 2) {
+ username = argv[2];
+ }
+
+ /* Ultra basic "connect to port 22 on localhost". Your code is
+ * responsible for creating the socket establishing the connection
+ */
+ sock = socket(AF_INET, SOCK_STREAM, 0);
+ if (sock == -1) {
+ fprintf(stderr, "failed to create socket!\n");
+ rc = 1;
+ goto shutdown;
+ }
+
+ sin.sin_family = AF_INET;
+ sin.sin_port = htons(22);
+ sin.sin_addr.s_addr = hostaddr;
+ if (connect(sock, (struct sockaddr*)(&sin),
+ sizeof(struct sockaddr_in)) != 0) {
+ fprintf(stderr, "failed to connect!\n");
+ goto shutdown;
+ }
+
+ /* Create a session instance and start it up. This will trade welcome
+ * banners, exchange keys, and setup crypto, compression, and MAC layers
+ */
+ session = libssh2_session_init();
+ if (libssh2_session_startup(session, sock)) {
+ fprintf(stderr, "Failure establishing SSH session\n");
+ return 1;
+ }
+
+ /* Connect to the ssh-agent */
+ agent = libssh2_agent_init(session);
+ if (!agent) {
+ fprintf(stderr, "Failure initializing ssh-agent support\n");
+ rc = 1;
+ goto shutdown;
+ }
+ if (libssh2_agent_connect(agent)) {
+ fprintf(stderr, "Failure connecting to ssh-agent\n");
+ rc = 1;
+ goto shutdown;
+ }
+ if (libssh2_agent_list_identities(agent)) {
+ fprintf(stderr, "Failure requesting identities to ssh-agent\n");
+ rc = 1;
+ goto shutdown;
+ }
+ while (1) {
+ rc = libssh2_agent_get_identity(agent, &identity, prev_identity);
+ if (rc == 1)
+ break;
+ if (rc < 0) {
+ fprintf(stderr,
+ "Failure obtaining identity from ssh-agent support\n");
+ rc = 1;
+ goto shutdown;
+ }
+ if (libssh2_agent_userauth(agent, username, identity)) {
+ printf("\tAuthentication with username %s and "
+ "public key %s failed!\n",
+ username, identity->comment);
+ } else {
+ printf("\tAuthentication with username %s and "
+ "public key %s succeeded!\n",
+ username, identity->comment);
+ break;
+ }
+ }
+ if (rc) {
+ fprintf(stderr, "Couldn't continue authentication\n");
+ goto shutdown;
+ }
+
+ /* At this point we havn't authenticated. The first thing to do is check
+ * the hostkey's fingerprint against our known hosts Your app may have it
+ * hard coded, may go to a file, may present it to the user, that's your
+ * call
+ */
+ fingerprint = libssh2_hostkey_hash(session, LIBSSH2_HOSTKEY_HASH_MD5);
+ printf("Fingerprint: ");
+ for(i = 0; i < 16; i++) {
+ printf("%02X ", (unsigned char)fingerprint[i]);
+ }
+ printf("\n");
+
+ /* check what authentication methods are available */
+ userauthlist = libssh2_userauth_list(session, username, strlen(username));
+ printf("Authentication methods: %s\n", userauthlist);
+ if (strstr(userauthlist, "publickey") == NULL) {
+ fprintf(stderr, "\"publickey\" authentication is not supported\n");
+ goto shutdown;
+ }
+
+ /* Request a shell */
+ if (!(channel = libssh2_channel_open_session(session))) {
+ fprintf(stderr, "Unable to open a session\n");
+ goto shutdown;
+ }
+
+ /* Some environment variables may be set,
+ * It's up to the server which ones it'll allow though
+ */
+ libssh2_channel_setenv(channel, "FOO", "bar");
+
+ /* Request a terminal with 'vanilla' terminal emulation
+ * See /etc/termcap for more options
+ */
+ if (libssh2_channel_request_pty(channel, "vanilla")) {
+ fprintf(stderr, "Failed requesting pty\n");
+ goto skip_shell;
+ }
+
+ /* Open a SHELL on that pty */
+ if (libssh2_channel_shell(channel)) {
+ fprintf(stderr, "Unable to request shell on allocated pty\n");
+ goto shutdown;
+ }
+
+ /* At this point the shell can be interacted with using
+ * libssh2_channel_read()
+ * libssh2_channel_read_stderr()
+ * libssh2_channel_write()
+ * libssh2_channel_write_stderr()
+ *
+ * Blocking mode may be (en|dis)abled with: libssh2_channel_set_blocking()
+ * If the server send EOF, libssh2_channel_eof() will return non-0
+ * To send EOF to the server use: libssh2_channel_send_eof()
+ * A channel can be closed with: libssh2_channel_close()
+ * A channel can be freed with: libssh2_channel_free()
+ */
+
+ skip_shell:
+ if (channel) {
+ libssh2_channel_free(channel);
+ channel = NULL;
+ }
+
+ /* Other channel types are supported via:
+ * libssh2_scp_send()
+ * libssh2_scp_recv()
+ * libssh2_channel_direct_tcpip()
+ */
+
+ shutdown:
+
+ libssh2_agent_disconnect(agent);
+ libssh2_agent_free(agent);
+
+ libssh2_session_disconnect(session,
+ "Normal Shutdown, Thank you for playing");
+ libssh2_session_free(session);
+
+ if (sock != -1) {
+#ifdef WIN32
+ closesocket(sock);
+#else
+ close(sock);
+#endif
+ }
+
+ printf("all done!\n");
+ return rc;
+}
--
1.6.5.4
_______________________________________________
libssh2-devel http://cool.haxx.se/cgi-bin/mailman/listinfo/libssh2-devel