[vinagre] Initial support to ssh tunneling



commit 3f8aada4afdfe78d1a6579c035f6f73fce784357
Author: Jonh Wendell <jwendell voipforall com br>
Date:   Mon Jan 25 14:22:22 2010 -0300

    Initial support to ssh tunneling

 configure.ac                     |   26 ++-
 plugins/vnc/Makefile.am          |    3 +-
 plugins/vnc/vinagre-vnc-tab.c    |   38 ++-
 plugins/vnc/vinagre-vnc-tunnel.c |  126 ++++++
 plugins/vnc/vinagre-vnc-tunnel.h |   42 ++
 vinagre/Makefile.am              |    7 +
 vinagre/pty_open.c               |  843 ++++++++++++++++++++++++++++++++++++++
 vinagre/pty_open.h               |   65 +++
 vinagre/vinagre-ssh.c            |  751 +++++++++++++++++++++++++++++++++
 vinagre/vinagre-ssh.h            |   57 +++
 vinagre/vinagre-utils.c          |    7 +
 11 files changed, 1959 insertions(+), 6 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 2a747cb..5fcb2dc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,6 +72,7 @@ AM_CONDITIONAL(AVAHI, test "x$enable_avahi" = "xyes")
 PKG_CHECK_MODULES(VINAGRE, \ 
   glib-2.0 >= $GLIB_REQUIRED \
   gio-2.0 >= $GLIB_REQUIRED \
+  gio-unix-2.0 >= $GLIB_REQUIRED \
   gtk+-2.0 >= $GTK_REQUIRED \ 
   gconf-2.0 >= $GCONF_REQUIRED \
   gthread-2.0 >= $GTHREAD_REQUIRED \
@@ -88,7 +89,30 @@ PKG_CHECK_MODULES(VNC, \
 AC_SUBST(VNC_CFLAGS)
 AC_SUBST(VNC_LIBS)
 
-# Check for SSH
+dnl ****************************
+dnl *** Checks for pty stuff ***
+dnl ****************************
+
+AC_CHECK_HEADERS(sys/un.h stropts.h termios.h utmp.h sys/uio.h sys/param.h)
+
+# Check for PTY handling functions.
+AC_CHECK_FUNCS(getpt posix_openpt grantpt unlockpt ptsname ptsname_r)
+
+# Pull in the right libraries for various functions which might not be
+# bundled into an exploded libc.
+AC_CHECK_FUNC(socketpair,[have_socketpair=1],AC_CHECK_LIB(socket,socketpair,[have_socketpair=1; LIBS="$LIBS -lsocket"]))
+if test x$have_socketpair = x1 ; then
+	AC_DEFINE(HAVE_SOCKETPAIR,1,[Define if you have the socketpair function.])
+fi
+
+AC_SEARCH_LIBS(login_tty, util, [AC_DEFINE([HAVE_LOGIN_TTY],[],[Whether login_tty is available])])
+
+dnl ****************************
+dnl *** Checks for SSH stuff ***
+dnl ****************************
+
+AC_PATH_PROG(SSH_PROGRAM, ssh, "ssh")
+
 AC_ARG_ENABLE(ssh,
               AS_HELP_STRING([--enable-ssh],
                              [Enable SSH plugin (default=no)]),
diff --git a/plugins/vnc/Makefile.am b/plugins/vnc/Makefile.am
index 3b2927c..65c941d 100644
--- a/plugins/vnc/Makefile.am
+++ b/plugins/vnc/Makefile.am
@@ -14,7 +14,8 @@ libvnc_la_SOURCES = 						\
 	vinagre-vnc-connection.h vinagre-vnc-connection.c	\
 	vinagre-vnc-tab.h vinagre-vnc-tab.c			\
 	vinagre-vnc-listener.h vinagre-vnc-listener.c		\
-	vinagre-vnc-listener-dialog.h vinagre-vnc-listener-dialog.c
+	vinagre-vnc-listener-dialog.h vinagre-vnc-listener-dialog.c \
+	vinagre-vnc-tunnel.h vinagre-vnc-tunnel.c
 
 libvnc_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 libvnc_la_LIBADD  = $(VNC_LIBS)
diff --git a/plugins/vnc/vinagre-vnc-tab.c b/plugins/vnc/vinagre-vnc-tab.c
index 9fb72d8..da5b295 100644
--- a/plugins/vnc/vinagre-vnc-tab.c
+++ b/plugins/vnc/vinagre-vnc-tab.c
@@ -28,6 +28,7 @@
 
 #include "vinagre-vnc-tab.h"
 #include "vinagre-vnc-connection.h"
+#include "vinagre-vnc-tunnel.h"
 
 #define VINAGRE_VNC_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), VINAGRE_TYPE_VNC_TAB, VinagreVncTabPrivate))
 
@@ -242,15 +243,26 @@ vinagre_vnc_tab_class_init (VinagreVncTabClass *klass)
   g_type_class_add_private (object_class, sizeof (VinagreVncTabPrivate));
 }
 
+static gboolean
+idle_close (VinagreTab *tab)
+{
+  vinagre_notebook_close_tab (vinagre_tab_get_notebook (tab), tab);
+  return FALSE;
+}
+
 static void
 open_vnc (VinagreVncTab *vnc_tab)
 {
-  gchar      *host, *port_str;
+  gchar      *host, *port_str, *ssh_tunnel_host;
   gint       port, shared, fd, depth_profile;
   gboolean   scaling, success, lossy_encoding;
+  GError     *error;
   VncDisplay *vnc = VNC_DISPLAY (vnc_tab->priv->vnc);
   VinagreTab *tab = VINAGRE_TAB (vnc_tab);
 
+  success = TRUE;
+  error = NULL;
+
   g_object_get (vinagre_tab_get_conn (tab),
 		"port", &port,
 		"host", &host,
@@ -259,6 +271,7 @@ open_vnc (VinagreVncTab *vnc_tab)
 		"fd", &fd,
 		"depth-profile", &depth_profile,
 		"lossy-encoding", &lossy_encoding,
+		"ssh-tunnel-host", &ssh_tunnel_host,
 		NULL);
 
   port_str = g_strdup_printf ("%d", port);
@@ -275,17 +288,34 @@ open_vnc (VinagreVncTab *vnc_tab)
   if (fd > 0)
     success = vnc_display_open_fd (vnc, fd);
   else
-    success = vnc_display_open_host (vnc, host, port_str);
+    {
+      if (ssh_tunnel_host && *ssh_tunnel_host)
+	if (!vinagre_vnc_tunnel_create (&host, &port_str, ssh_tunnel_host, &error))
+	  {
+	    success = FALSE;
+	    vinagre_utils_show_error (_("Error creating the SSH tunnel"),
+				      error ? error->message : _("Unknown reason"),
+				      GTK_WINDOW (vinagre_tab_get_window (tab)));
+	    goto out;
+	  }
+      success = vnc_display_open_host (vnc, host, port_str);
+    }
 
   if (success)
     gtk_widget_grab_focus (GTK_WIDGET (vnc));
   else
-    vinagre_utils_show_error (NULL,
-			      _("Error connecting to host."),
+    vinagre_utils_show_error (_("Error connecting to host."),
+			      error ? error->message : _("Unknown reason"),
 			      GTK_WINDOW (vinagre_tab_get_window (tab)));
 
+out:
   g_free (port_str);
   g_free (host);
+  g_free (ssh_tunnel_host);
+  g_clear_error (&error);
+
+  if (!success)
+    g_idle_add ((GSourceFunc)idle_close, vnc_tab);
 }
 
 static void
diff --git a/plugins/vnc/vinagre-vnc-tunnel.c b/plugins/vnc/vinagre-vnc-tunnel.c
new file mode 100644
index 0000000..00eb0a3
--- /dev/null
+++ b/plugins/vnc/vinagre-vnc-tunnel.c
@@ -0,0 +1,126 @@
+/*
+ * vinagre-vnc-tunnel.c
+ * VNC SSH Tunneling for Vinagre
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2009 - Jonh Wendell <wendell bani com br>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <glib/gi18n.h>
+
+#include <vinagre/vinagre-ssh.h>
+#include "vinagre-vnc-tunnel.h"
+
+#define TUNNEL_PORT_OFFSET 5500
+
+static int
+find_free_port (void)
+{
+  int sock, port;
+  struct sockaddr_in6 addr;
+
+  memset (&addr, 0, sizeof (addr));
+  addr.sin6_family = AF_INET6;
+  addr.sin6_addr = in6addr_any;
+
+  sock = socket (AF_INET6, SOCK_STREAM, IPPROTO_TCP);
+  if (sock < 0)
+    return 0;
+
+  for (port = TUNNEL_PORT_OFFSET + 99; port > TUNNEL_PORT_OFFSET; port--)
+    {
+      addr.sin6_port = htons (port);
+      if (bind (sock, (struct sockaddr *)&addr, sizeof (addr)) == 0)
+	{
+	  close (sock);
+	  return port;
+	}
+    }
+
+  close (sock);
+  return 0;
+}
+
+gboolean
+vinagre_vnc_tunnel_create (gchar **original_host, gchar **original_port, gchar *gateway, GError **error)
+{
+  int local_port;
+  gchar **tunnel_str, **command_str;
+
+  local_port = find_free_port ();
+  if (local_port == 0)
+    {
+      g_set_error (error,
+		   VINAGRE_VNC_TUNNEL_ERROR,
+		   VINAGRE_VNC_TUNNEL_ERROR_NO_FREE_PORT,
+		   _("Unable to find a free TCP port"));
+      return FALSE;
+    }
+
+  tunnel_str = g_new (gchar *, 4);
+  tunnel_str[0] = g_strdup ("-f");
+  tunnel_str[1] = g_strdup ("-L");
+  tunnel_str[2] = g_strdup_printf ("%d:%s:%s",
+				   local_port,
+				   *original_host,
+				   *original_port);
+  tunnel_str[3] = NULL;
+
+  command_str = g_new (gchar *, 5);
+  command_str[0] = g_strdup ("echo");
+  command_str[1] = g_strdup_printf ("%s;", VINAGRE_SSH_CHECK);
+  command_str[2] = g_strdup ("sleep");
+  command_str[3] = g_strdup ("15");
+  command_str[4] = NULL;
+
+  if (!vinagre_ssh_connect (gateway,
+			    22,
+			    NULL,
+			    tunnel_str,
+			    command_str,
+			    NULL,
+			    error))
+    {
+      g_strfreev (tunnel_str);
+      g_strfreev (command_str);
+      return FALSE;
+    }
+
+  g_strfreev (tunnel_str);
+  g_strfreev (command_str);
+  g_free (*original_host);
+  *original_host = g_strdup ("localhost");
+  g_free (*original_port);
+  *original_port = g_strdup_printf ("%d", local_port);
+
+  return TRUE;
+}
+
+GQuark 
+vinagre_vnc_tunnel_error_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (!quark)
+    quark = g_quark_from_string ("vinagre_vnc_tunnel_error");
+
+  return quark;
+}
+
+/* vim: set ts=8: */
diff --git a/plugins/vnc/vinagre-vnc-tunnel.h b/plugins/vnc/vinagre-vnc-tunnel.h
new file mode 100644
index 0000000..c9e695c
--- /dev/null
+++ b/plugins/vnc/vinagre-vnc-tunnel.h
@@ -0,0 +1,42 @@
+/*
+ * vinagre-vnc-tunnel.h
+ * VNC SSH Tunneling for Vinagre
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2009 - Jonh Wendell <wendell bani com br>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VINAGRE_VNC_TUNNEL_H__
+#define __VINAGRE_VNC_TUNNEL_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  VINAGRE_VNC_TUNNEL_ERROR_NO_FREE_PORT = 1
+} VinagreVncTunnelError;
+
+#define VINAGRE_VNC_TUNNEL_ERROR vinagre_vnc_tunnel_error_quark()
+GQuark vinagre_vnc_tunnel_error_quark (void);
+
+gboolean vinagre_vnc_tunnel_create (gchar **original_host, gchar **original_port, gchar *gateway, GError **error);
+
+G_END_DECLS
+
+#endif  /* __VINAGRE_VNC_TUNNEL_H__  */
+/* vim: set ts=8: */
diff --git a/vinagre/Makefile.am b/vinagre/Makefile.am
index 3e178ad..c91d079 100644
--- a/vinagre/Makefile.am
+++ b/vinagre/Makefile.am
@@ -10,6 +10,7 @@ INCLUDES = 						\
 	-DLIBDIR=\""$(libdir)"\" 			\
 	-DVINAGRE_DATADIR=\""$(datadir)/vinagre"\"	\
 	-DPACKAGE_LOCALE_DIR=\""$(datadir)/locale"\"	\
+	-DSSH_PROGRAM=\"$(SSH_PROGRAM)\"		\
 	$(VINAGRE_CFLAGS)				\
 	$(AVAHI_CFLAGS)					\
 	$(NULL)
@@ -27,6 +28,7 @@ NOINST_H_FILES = \
   vinagre-plugins-engine.h \
   vinagre-window-private.h \
   vinagre-spinner.h \
+  pty_open.h \
   $(NULL)
 
 INST_H_FILES = \
@@ -52,6 +54,7 @@ INST_H_FILES = \
   vinagre-utils.h \
   vinagre-window.h \
   vinagre-dnd.h \
+  vinagre-ssh.h \
   $(NULL)
 
 headerdir = $(prefix)/include/vinagre- VINAGRE_API_VERSION@/vinagre
@@ -91,6 +94,8 @@ handwritten_sources = \
   vinagre-utils.c \
   vinagre-window.c \
   vinagre-spinner.c \
+  pty_open.c \
+  vinagre-ssh.c \
   $(NULL)
 
 libvinagre_la_SOURCES = \
@@ -188,6 +193,8 @@ vinagre_applet_SOURCES =					\
 	vinagre-connect.h vinagre-connect.c \
 	vinagre-spinner.h vinagre-spinner.c \
 	vinagre-marshal.h vinagre-marshal.c \
+	pty_open.h pty_open.c \
+	vinagre-ssh.h vinagre-ssh.c \
 	$(NULL)
 
 if AVAHI
diff --git a/vinagre/pty_open.c b/vinagre/pty_open.c
new file mode 100644
index 0000000..ec12624
--- /dev/null
+++ b/vinagre/pty_open.c
@@ -0,0 +1,843 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+/*
+ * Copyright (C) 2001,2002,2004 Red Hat, Inc.
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/* Originally from vte */
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#ifdef HAVE_SYS_UN_H
+#include <sys/un.h>
+#endif
+#ifdef HAVE_STROPTS_H
+#include <stropts.h>
+#endif
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#ifdef HAVE_UTMP_H
+#include <utmp.h>
+#endif
+#ifdef HAVE_UTIL_H
+#include <util.h>
+#endif
+#include <glib.h>
+#include "pty_open.h"
+
+int _pty_set_size(int master, int columns, int rows);
+
+/* Solaris does not have the login_tty() function so implement locally. */
+#ifndef HAVE_LOGIN_TTY
+static int login_tty(int pts)
+{
+#if defined(HAVE_STROPTS_H)
+  /* push a terminal onto stream head */
+  if (ioctl(pts, I_PUSH, "ptem")   == -1) return -1;
+  if (ioctl(pts, I_PUSH, "ldterm") == -1) return -1;
+#endif
+  setsid();
+#if defined(TIOCSCTTY)
+  ioctl(pts, TIOCSCTTY, 0);
+#endif
+  dup2(pts, 0);
+  dup2(pts, 1);
+  dup2(pts, 2);
+  if (pts > 2) close(pts);
+  return 0;
+}
+#endif
+
+/* Reset the handlers for all known signals to their defaults.  The parent
+ * (or one of the libraries it links to) may have changed one to be ignored. */
+static void
+_pty_reset_signal_handlers(void)
+{
+	signal(SIGHUP,  SIG_DFL);
+	signal(SIGINT,  SIG_DFL);
+	signal(SIGILL,  SIG_DFL);
+	signal(SIGABRT, SIG_DFL);
+	signal(SIGFPE,  SIG_DFL);
+	signal(SIGKILL, SIG_DFL);
+	signal(SIGSEGV, SIG_DFL);
+	signal(SIGPIPE, SIG_DFL);
+	signal(SIGALRM, SIG_DFL);
+	signal(SIGTERM, SIG_DFL);
+	signal(SIGCHLD, SIG_DFL);
+	signal(SIGCONT, SIG_DFL);
+	signal(SIGSTOP, SIG_DFL);
+	signal(SIGTSTP, SIG_DFL);
+	signal(SIGTTIN, SIG_DFL);
+	signal(SIGTTOU, SIG_DFL);
+#ifdef SIGBUS
+	signal(SIGBUS,  SIG_DFL);
+#endif
+#ifdef SIGPOLL
+	signal(SIGPOLL, SIG_DFL);
+#endif
+#ifdef SIGPROF
+	signal(SIGPROF, SIG_DFL);
+#endif
+#ifdef SIGSYS
+	signal(SIGSYS,  SIG_DFL);
+#endif
+#ifdef SIGTRAP
+	signal(SIGTRAP, SIG_DFL);
+#endif
+#ifdef SIGURG
+	signal(SIGURG,  SIG_DFL);
+#endif
+#ifdef SIGVTALARM
+	signal(SIGVTALARM, SIG_DFL);
+#endif
+#ifdef SIGXCPU
+	signal(SIGXCPU, SIG_DFL);
+#endif
+#ifdef SIGXFSZ
+	signal(SIGXFSZ, SIG_DFL);
+#endif
+#ifdef SIGIOT
+	signal(SIGIOT,  SIG_DFL);
+#endif
+#ifdef SIGEMT
+	signal(SIGEMT,  SIG_DFL);
+#endif
+#ifdef SIGSTKFLT
+	signal(SIGSTKFLT, SIG_DFL);
+#endif
+#ifdef SIGIO
+	signal(SIGIO,   SIG_DFL);
+#endif
+#ifdef SIGCLD
+	signal(SIGCLD,  SIG_DFL);
+#endif
+#ifdef SIGPWR
+	signal(SIGPWR,  SIG_DFL);
+#endif
+#ifdef SIGINFO
+	signal(SIGINFO, SIG_DFL);
+#endif
+#ifdef SIGLOST
+	signal(SIGLOST, SIG_DFL);
+#endif
+#ifdef SIGWINCH
+	signal(SIGWINCH, SIG_DFL);
+#endif
+#ifdef SIGUNUSED
+	signal(SIGUNUSED, SIG_DFL);
+#endif
+}
+
+#ifdef HAVE_SOCKETPAIR
+static int
+_pty_pipe_open(int *a, int *b)
+{
+	int p[2], ret = -1;
+#ifdef PF_UNIX
+#ifdef SOCK_STREAM
+	ret = socketpair(PF_UNIX, SOCK_STREAM, 0, p);
+#else
+#ifdef SOCK_DGRAM
+	ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, p);
+#endif
+#endif
+	if (ret == 0) {
+		*a = p[0];
+		*b = p[1];
+		return 0;
+	}
+#endif
+	return ret;
+}
+#else
+static int
+_pty_pipe_open(int *a, int *b)
+{
+	int p[2], ret = -1;
+
+	ret = pipe(p);
+
+	if (ret == 0) {
+		*a = p[0];
+		*b = p[1];
+	}
+	return ret;
+}
+#endif
+
+static int
+_pty_pipe_open_bi(int *a, int *b, int *c, int *d)
+{
+	int ret;
+	ret = _pty_pipe_open(a, b);
+	if (ret != 0) {
+		return ret;
+	}
+	ret = _pty_pipe_open(c, d);
+	if (ret != 0) {
+		close(*a);
+		close(*b);
+	}
+	return ret;
+}
+
+/* Like read, but hide EINTR and EAGAIN. */
+static ssize_t
+n_read(int fd, void *buffer, size_t count)
+{
+	size_t n = 0;
+	char *buf = buffer;
+	int i;
+	while (n < count) {
+		i = read(fd, buf + n, count - n);
+		switch (i) {
+		case 0:
+			return n;
+			break;
+		case -1:
+			switch (errno) {
+			case EINTR:
+			case EAGAIN:
+#ifdef ERESTART
+			case ERESTART:
+#endif
+				break;
+			default:
+				return -1;
+			}
+			break;
+		default:
+			n += i;
+			break;
+		}
+	}
+	return n;
+}
+
+/* Like write, but hide EINTR and EAGAIN. */
+static ssize_t
+n_write(int fd, const void *buffer, size_t count)
+{
+	size_t n = 0;
+	const char *buf = buffer;
+	int i;
+	while (n < count) {
+		i = write(fd, buf + n, count - n);
+		switch (i) {
+		case 0:
+			return n;
+			break;
+		case -1:
+			switch (errno) {
+			case EINTR:
+			case EAGAIN:
+#ifdef ERESTART
+			case ERESTART:
+#endif
+				break;
+			default:
+				return -1;
+			}
+			break;
+		default:
+			n += i;
+			break;
+		}
+	}
+	return n;
+}
+
+/* Run the given command (if specified), using the given descriptor as the
+ * controlling terminal. */
+static int
+_pty_run_on_pty(int fd, gboolean login,
+			  int stdin_fd, int stdout_fd, int stderr_fd, 
+			  int ready_reader, int ready_writer,
+			  char **env_add, const char *command, char **argv,
+			  const char *directory)
+{
+	int i;
+	char c;
+	char **args, *arg;
+
+#ifdef HAVE_STROPTS_H
+	if (!ioctl (fd, I_FIND, "ptem") && ioctl (fd, I_PUSH, "ptem") == -1) {
+		close (fd);
+		_exit (0);
+		return -1;
+	}
+
+	if (!ioctl (fd, I_FIND, "ldterm") && ioctl (fd, I_PUSH, "ldterm") == -1) {
+		close (fd);
+		_exit (0);
+		return -1;
+	}
+
+	if (!ioctl (fd, I_FIND, "ttcompat") && ioctl (fd, I_PUSH, "ttcompat") == -1) {
+		perror ("ioctl (fd, I_PUSH, \"ttcompat\")");
+		close (fd);
+		_exit (0);
+		return -1;
+	}
+#endif /* HAVE_STROPTS_H */
+
+	/* Set any environment variables. */
+	for (i = 0; (env_add != NULL) && (env_add[i] != NULL); i++) {
+		if (putenv(g_strdup(env_add[i])) != 0) {
+			g_warning("Error adding `%s' to environment, "
+				    "continuing.", env_add[i]);
+		}
+	}
+
+	/* Reset our signals -- our parent may have done any number of
+	 * weird things to them. */
+	_pty_reset_signal_handlers();
+
+	/* Change to the requested directory. */
+	if (directory != NULL) {
+		i = chdir(directory);
+	}
+
+#ifdef HAVE_UTMP_H
+	/* This sets stdin, stdout, stderr to the socket */	
+	if (login && login_tty (fd) == -1) {
+		g_printerr ("mount child process login_tty failed: %s\n", g_strerror (errno));
+		return -1;
+	}
+#endif
+	
+	/* Signal to the parent that we've finished setting things up by
+	 * sending an arbitrary byte over the status pipe and waiting for
+	 * a response.  This synchronization step ensures that the pty is
+	 * fully initialized before the parent process attempts to do anything
+	 * with it, and is required on systems where additional setup, beyond
+	 * merely opening the device, is required.  This is at least the case
+	 * on Solaris. */
+	/* Initialize so valgrind doesn't complain */
+	c = 0;
+	n_write(ready_writer, &c, 1);
+	fsync(ready_writer);
+	n_read(ready_reader, &c, 1);
+	close(ready_writer);
+	if (ready_writer != ready_reader) {
+		close(ready_reader);
+	}
+
+	/* If the caller provided a command, we can't go back, ever. */
+	if (command != NULL) {
+		/* Outta here. */
+		if (argv != NULL) {
+			for (i = 0; (argv[i] != NULL); i++) ;
+			args = g_malloc0(sizeof(char*) * (i + 1));
+			for (i = 0; (argv[i] != NULL); i++) {
+				args[i] = g_strdup(argv[i]);
+			}
+			execvp(command, args);
+		} else {
+			arg = g_strdup(command);
+			execlp(command, arg, NULL);
+		} 
+
+		/* Avoid calling any atexit() code. */
+		_exit(0);
+		g_assert_not_reached();
+	}
+
+	return 0;
+}
+
+/* Open the named PTY slave, fork off a child (storing its PID in child),
+ * and exec the named command in its own session as a process group leader */
+static int
+_pty_fork_on_pty_name(const char *path, int parent_fd, char **env_add,
+		      const char *command, char **argv,
+		      const char *directory,
+		      int columns, int rows, 
+		      int *stdin_fd, int *stdout_fd, int *stderr_fd, 
+		      pid_t *child, gboolean reapchild, gboolean login)
+{
+	int fd, i;
+	char c;
+	int ready_a[2] = { 0, 0 };
+	int ready_b[2] = { 0, 0 };
+	pid_t pid, grandchild_pid;
+	int pid_pipe[2];
+	int stdin_pipe[2];
+	int stdout_pipe[2];
+	int stderr_pipe[2];
+
+	/* Open pipes for synchronizing between parent and child. */
+	if (_pty_pipe_open_bi(&ready_a[0], &ready_a[1],
+			      &ready_b[0], &ready_b[1]) == -1) {
+		/* Error setting up pipes.  Bail. */
+		goto bail_ready;
+	}
+
+	if (reapchild && pipe(pid_pipe)) {
+		/* Error setting up pipes. Bail. */
+		goto bail_pid;
+	}
+	
+	if (pipe(stdin_pipe)) {
+		/* Error setting up pipes.  Bail. */
+		goto bail_stdin;
+	}
+	if (pipe(stdout_pipe)) {
+		/* Error setting up pipes.  Bail. */
+		goto bail_stdout;
+	}
+	if (pipe(stderr_pipe)) {
+		/* Error setting up pipes.  Bail. */
+		goto bail_stderr;
+	}
+
+	/* Start up a child. */
+	pid = fork();
+	switch (pid) {
+	case -1:
+		/* Error fork()ing.  Bail. */
+		*child = -1;
+		return -1;
+		break;
+	case 0:
+		/* Child. Close the parent's ends of the pipes. */
+		close(ready_a[0]);
+		close(ready_b[1]);
+	
+		close(stdin_pipe[1]);
+		close(stdout_pipe[0]);
+		close(stderr_pipe[0]);
+
+		if(reapchild) {
+			close(pid_pipe[0]);
+
+			/* Fork a intermediate child. This is needed to not
+			 * produce zombies! */
+			grandchild_pid = fork();
+
+			if (grandchild_pid < 0) {
+				/* Error during fork! */
+				n_write (pid_pipe[1], &grandchild_pid, 
+					 sizeof (grandchild_pid));
+				_exit (1);
+			} else if (grandchild_pid > 0) {
+				/* Parent! (This is the actual intermediate child;
+				 * so write the pid to the parent and then exit */
+				n_write (pid_pipe[1], &grandchild_pid, 
+					 sizeof (grandchild_pid));
+				close (pid_pipe[1]);
+				_exit (0);
+			}
+		
+			/* Start a new session and become process-group leader. */
+			setsid();
+			setpgid(0, 0);
+		}
+
+		/* Close most descriptors. */
+		for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) {
+			if ((i != ready_b[0]) && 
+			    (i != ready_a[1]) &&
+			    (i != stdin_pipe[0]) &&
+			    (i != stdout_pipe[1]) &&
+			    (i != stderr_pipe[1])) {
+				close(i);
+			}
+		}
+
+		/* Set up stdin/out/err */
+		dup2(stdin_pipe[0], STDIN_FILENO);
+		close (stdin_pipe[0]);
+		dup2(stdout_pipe[1], STDOUT_FILENO);
+		close (stdout_pipe[1]);
+		dup2(stderr_pipe[1], STDERR_FILENO);
+		close (stderr_pipe[1]);
+
+		/* Open the slave PTY, acquiring it as the controlling terminal
+		 * for this process and its children. */
+		fd = open(path, O_RDWR);
+		if (fd == -1) {
+			return -1;
+		}
+#ifdef TIOCSCTTY
+		/* TIOCSCTTY is defined?  Let's try that, too. */
+		ioctl(fd, TIOCSCTTY, fd);
+#endif
+		/* Store 0 as the "child"'s ID to indicate to the caller that
+		 * it is now the child. */
+		*child = 0;
+		return _pty_run_on_pty(fd, login,
+				       stdin_pipe[1], stdout_pipe[1], stderr_pipe[1], 
+				       ready_b[0], ready_a[1],
+				       env_add, command, argv, directory);
+		break;
+	default:
+		/* Parent.  Close the child's ends of the pipes, do the ready
+		 * handshake, and return the child's PID. */
+		close(ready_b[0]);
+		close(ready_a[1]);
+
+		close(stdin_pipe[0]);
+		close(stdout_pipe[1]);
+		close(stderr_pipe[1]);
+
+		if (reapchild) {
+			close(pid_pipe[1]);
+
+			/* Reap the intermediate child */
+        	wait_again:	
+			if (waitpid (pid, NULL, 0) < 0) {
+				if (errno == EINTR) {
+					goto wait_again;
+				} else if (errno == ECHILD) {
+					; /* NOOP! Child already reaped. */
+				} else {
+					g_warning ("waitpid() should not fail in pty-open.c");
+				}
+			}
+	
+			/*
+			 * Read the child pid from the pid_pipe 
+			 * */
+			if (n_read (pid_pipe[0], child, sizeof (pid_t)) 
+			  	!= sizeof (pid_t) || *child == -1) {
+				g_warning ("Error while spanning child!");
+				goto bail_fork;
+			}
+			
+			close(pid_pipe[0]);
+
+		} else {
+			/* No intermediate child, simple */
+			*child = pid;
+		}
+		
+		/* Wait for the child to be ready, set the window size, then
+		 * signal that we're ready.  We need to synchronize here to
+		 * avoid possible races when the child has to do more setup
+		 * of the terminal than just opening it. */
+		n_read(ready_a[0], &c, 1);
+		_pty_set_size(parent_fd, columns, rows);
+		n_write(ready_b[1], &c, 1);
+		close(ready_a[0]);
+		close(ready_b[1]);
+
+		*stdin_fd = stdin_pipe[1];
+		*stdout_fd = stdout_pipe[0];
+		*stderr_fd = stderr_pipe[0];
+
+		return 0;
+		break;
+	}
+	g_assert_not_reached();
+	return -1;
+
+ bail_fork:
+	close(stderr_pipe[0]);
+	close(stderr_pipe[1]);
+ bail_stderr:
+	close(stdout_pipe[0]);
+	close(stdout_pipe[1]);
+ bail_stdout:
+	close(stdin_pipe[0]);
+	close(stdin_pipe[1]);
+ bail_stdin:
+	if(reapchild) {
+		close(pid_pipe[0]);
+		close(pid_pipe[1]);
+	}
+ bail_pid:
+	close(ready_a[0]);
+	close(ready_a[1]);
+	close(ready_b[0]);
+	close(ready_b[1]);
+ bail_ready:
+	*child = -1;
+	return -1;
+}
+
+/**
+ * pty_set_size:
+ * @master: the file descriptor of the pty master
+ * @columns: the desired number of columns
+ * @rows: the desired number of rows
+ *
+ * Attempts to resize the pseudo terminal's window size.  If successful, the
+ * OS kernel will send #SIGWINCH to the child process group.
+ *
+ * Returns: 0 on success, -1 on failure.
+ */
+int
+_pty_set_size(int master, int columns, int rows)
+{
+	struct winsize size;
+	int ret;
+	memset(&size, 0, sizeof(size));
+	size.ws_row = rows ? rows : 24;
+	size.ws_col = columns ? columns : 80;
+	ret = ioctl(master, TIOCSWINSZ, &size);
+	return ret;
+}
+
+
+static char *
+_pty_ptsname(int master)
+{
+#if defined(HAVE_PTSNAME_R)
+	gsize len = 1024;
+	char *buf = NULL;
+	int i;
+	do {
+		buf = g_malloc0(len);
+		i = ptsname_r(master, buf, len - 1);
+		switch (i) {
+		case 0:
+			/* Return the allocated buffer with the name in it. */
+			return buf;
+			break;
+		default:
+			g_free(buf);
+			buf = NULL;
+			break;
+		}
+		len *= 2;
+	} while ((i != 0) && (errno == ERANGE));
+#elif defined(HAVE_PTSNAME)
+	char *p;
+	if ((p = ptsname(master)) != NULL) {
+		return g_strdup(p);
+	}
+#elif defined(TIOCGPTN)
+	int pty = 0;
+	if (ioctl(master, TIOCGPTN, &pty) == 0) {
+		return g_strdup_printf("/dev/pts/%d", pty);
+	}
+#endif
+	return NULL;
+}
+
+static int
+_pty_getpt(void)
+{
+	int fd, flags;
+#ifdef HAVE_GETPT
+	/* Call the system's function for allocating a pty. */
+	fd = getpt();
+#elif defined(HAVE_POSIX_OPENPT)
+	fd = posix_openpt(O_RDWR | O_NOCTTY);
+#else
+	/* Try to allocate a pty by accessing the pty master multiplex. */
+	fd = open("/dev/ptmx", O_RDWR | O_NOCTTY);
+	if ((fd == -1) && (errno == ENOENT)) {
+		fd = open("/dev/ptc", O_RDWR | O_NOCTTY); /* AIX */
+	}
+#endif
+	/* Set it to blocking. */
+	flags = fcntl(fd, F_GETFL);
+	flags &= ~(O_NONBLOCK);
+	fcntl(fd, F_SETFL, flags);
+	return fd;
+}
+
+static int
+_pty_grantpt(int master)
+{
+#ifdef HAVE_GRANTPT
+	return grantpt(master);
+#else
+	return 0;
+#endif
+}
+
+static int
+_pty_unlockpt(int fd)
+{
+#ifdef HAVE_UNLOCKPT
+	return unlockpt(fd);
+#elif defined(TIOCSPTLCK)
+	int zero = 0;
+	return ioctl(fd, TIOCSPTLCK, &zero);
+#else
+	return -1;
+#endif
+}
+
+static int
+_pty_open_unix98(pid_t *child, guint flags, char **env_add,
+			   const char *command, char **argv,
+			   const char *directory, int columns, int rows,
+			   int *stdin_fd, int *stdout_fd, int *stderr_fd)
+{
+	int fd;
+	char *buf;
+
+	/* Attempt to open the master. */
+	fd = _pty_getpt();
+	if (fd != -1) {
+		/* Read the slave number and unlock it. */
+		if (((buf = _pty_ptsname(fd)) == NULL) ||
+		    (_pty_grantpt(fd) != 0) ||
+		    (_pty_unlockpt(fd) != 0)) {
+			close(fd);
+			fd = -1;
+		} else {
+			/* Start up a child process with the given command. */
+			if (_pty_fork_on_pty_name(buf, fd, env_add, command,
+						  argv, directory,
+						  columns, rows,
+						  stdin_fd, stdout_fd, stderr_fd, 
+						  child, 
+						  flags & PTY_REAP_CHILD, 
+						  flags & PTY_LOGIN_TTY) != 0) {
+				close(fd);
+				fd = -1;
+			}
+			g_free(buf);
+		}
+	}
+	return fd;
+}
+
+/**
+ * pty_open:
+ * @child: location to store the new process's ID
+ * @env_add: a list of environment variables to add to the child's environment
+ * @command: name of the binary to run
+ * @argv: arguments to pass to @command
+ * @directory: directory to start the new command in, or NULL
+ * @columns: desired window columns
+ * @rows: desired window rows
+ * @lastlog: TRUE if the lastlog should be updated
+ * @utmp: TRUE if the utmp or utmpx log should be updated
+ * @wtmp: TRUE if the wtmp or wtmpx log should be updated
+ *
+ * Starts a new copy of @command running under a psuedo-terminal, optionally in
+ * the supplied @directory, with window size set to @rows x @columns
+ * and variables in @env_add added to its environment.  If any combination of
+ * @lastlog, @utmp, and @wtmp is set, then the session is logged in the
+ * corresponding system files.
+ *
+ * Returns: an open file descriptor for the pty master, -1 on failure
+ */
+int
+pty_open(pid_t *child, guint flags, char **env_add, 
+	 const char *command, char **argv, const char *directory,
+	 int columns, int rows,
+	 int *stdin_fd, int *stdout_fd, int *stderr_fd)
+{
+	int ret = -1;
+	if (ret == -1) {
+		ret = _pty_open_unix98(child, flags, env_add, command, 
+						 argv, directory, columns, rows,
+						 stdin_fd, stdout_fd, stderr_fd);
+	}
+	return ret;
+}
+
+
+#ifdef PTY_MAIN
+int fd;
+
+static void
+sigchld_handler(int signum)
+{
+}
+
+int
+main(int argc, char **argv)
+{
+	pid_t child = 0;
+	char c;
+	int ret;
+	signal(SIGCHLD, sigchld_handler);
+	fd = pty_open(&child, 0, NULL,
+		      (argc > 1) ? argv[1] : NULL,
+		      (argc > 1) ? argv + 1 : NULL,
+		      NULL,
+		      0, 0,
+		      NULL, NULL, NULL);
+	if (child == 0) {
+		int i;
+		for (i = 0; ; i++) {
+			switch (i % 3) {
+			case 0:
+			case 1:
+				fprintf(stdout, "%d\n", i);
+				break;
+			case 2:
+				fprintf(stderr, "%d\n", i);
+				break;
+			default:
+				g_assert_not_reached();
+				break;
+			}
+			sleep(1);
+		}
+	}
+	g_print("Child pid is %d.\n", (int)child);
+	do {
+		ret = n_read(fd, &c, 1);
+		if (ret == 0) {
+			break;
+		}
+		if ((ret == -1) && (errno != EAGAIN) && (errno != EINTR)) {
+			break;
+		}
+		if (argc < 2) {
+			n_write(STDOUT_FILENO, "[", 1);
+		}
+		n_write(STDOUT_FILENO, &c, 1);
+		if (argc < 2) {
+			n_write(STDOUT_FILENO, "]", 1);
+		}
+	} while (TRUE);
+	return 0;
+}
+#endif
diff --git a/vinagre/pty_open.h b/vinagre/pty_open.h
new file mode 100644
index 0000000..c9bc086
--- /dev/null
+++ b/vinagre/pty_open.h
@@ -0,0 +1,65 @@
+/* GIO - GLib Input, Output and Streaming Library
+ * 
+ * Copyright (C) 2006-2007 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Alexander Larsson <alexl redhat com>
+ */
+
+/*
+ * Copyright (C) 2001,2002,2004 Red Hat, Inc.
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Library General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef PTY_OPEN_H
+#define PTY_OPEN_H
+
+#include <sys/types.h>
+
+G_BEGIN_DECLS
+
+enum {
+	PTY_REAP_CHILD = 1,
+	PTY_LOGIN_TTY = 2
+};
+
+/* Start up the given binary (exact path, not interpreted at all) in a
+ * pseudo-terminal of its own, returning the descriptor for the master
+ * side of the PTY pair, logging the session to the specified files, and
+ * storing the child's PID in the given argument. */
+int pty_open(pid_t *child, guint flags, char **env_add,
+	     const char *command, char **argv, const char *directory,
+	     int columns, int rows,
+	     int *stdin_fd, int *stdout_fd, int *stderr_fd);
+int pty_get_size(int master, int *columns, int *rows);
+
+G_END_DECLS
+
+#endif
diff --git a/vinagre/vinagre-ssh.c b/vinagre/vinagre-ssh.c
new file mode 100644
index 0000000..da34e64
--- /dev/null
+++ b/vinagre/vinagre-ssh.c
@@ -0,0 +1,751 @@
+/*
+ * vinagre-ssh.c
+ * SSH Utilities for Vinagre
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2009 - Jonh Wendell <wendell bani com br>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <gio/gunixinputstream.h>
+#include <gio/gunixoutputstream.h>
+#include <glib/gi18n.h>
+#include <gnome-keyring.h>
+
+#include "vinagre-ssh.h"
+#include "vinagre-utils.h"
+#include "pty_open.h"
+
+#define SSH_READ_TIMEOUT 40   /* seconds */
+
+#ifdef HAVE_GRANTPT
+/* We only use this on systems with unix98 ptys */
+#define USE_PTY 1
+#endif
+
+typedef enum {
+  SSH_VENDOR_INVALID = 0,
+  SSH_VENDOR_OPENSSH,
+  SSH_VENDOR_SSH
+} SSHClientVendor;
+
+static SSHClientVendor vendor = SSH_VENDOR_INVALID;
+
+static SSHClientVendor
+get_ssh_client_vendor (void)
+{
+  char *ssh_stderr;
+  char *args[3];
+  gint ssh_exitcode;
+  SSHClientVendor res = SSH_VENDOR_INVALID;
+  
+  args[0] = g_strdup (SSH_PROGRAM);
+  args[1] = g_strdup ("-V");
+  args[2] = NULL;
+  if (g_spawn_sync (NULL, args, NULL,
+		    G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL,
+		    NULL, NULL,
+		    NULL, &ssh_stderr,
+		    &ssh_exitcode, NULL))
+    {
+      if (ssh_stderr == NULL)
+	res = SSH_VENDOR_INVALID;
+      else if ((strstr (ssh_stderr, "OpenSSH") != NULL) ||
+	       (strstr (ssh_stderr, "Sun_SSH") != NULL))
+	res = SSH_VENDOR_OPENSSH;
+      else if (strstr (ssh_stderr, "SSH Secure Shell") != NULL)
+	res = SSH_VENDOR_SSH;
+      else
+	res = SSH_VENDOR_INVALID;
+    }
+  
+  g_free (ssh_stderr);
+  g_free (args[0]);
+  g_free (args[1]);
+  
+  return res;
+}
+
+static gboolean
+wait_for_reply (int stdout_fd, GError **error)
+{
+  fd_set ifds;
+  struct timeval tv;
+  int ret;
+  
+  FD_ZERO (&ifds);
+  FD_SET (stdout_fd, &ifds);
+  
+  tv.tv_sec = SSH_READ_TIMEOUT;
+  tv.tv_usec = 0;
+      
+  ret = select (stdout_fd+1, &ifds, NULL, NULL, &tv);
+
+  if (ret <= 0)
+    {
+      g_set_error_literal (error,
+			   VINAGRE_SSH_ERROR,
+			   VINAGRE_SSH_ERROR_TIMEOUT,
+			   _("Timed out when logging into SSH host"));
+      return FALSE;
+    }
+  return TRUE;
+}
+
+static char **
+setup_ssh_commandline (const gchar *host,
+		       gint port,
+		       const gchar *username,
+		       gchar **extra_arguments,
+		       gchar **command)
+{
+  guint last_arg;
+  gchar **args;
+  int extra_size, command_size, i;
+
+  extra_size = extra_arguments != NULL ? g_strv_length (extra_arguments) : 0;
+  command_size = command != NULL ? g_strv_length (command) : 0;
+  args = g_new0 (gchar *, 15 + extra_size + command_size);
+
+  last_arg = 0;
+  args[last_arg++] = g_strdup (SSH_PROGRAM);
+
+  for (i=0; i<extra_size; i++)
+    args[last_arg++] = g_strdup (extra_arguments[i]);
+
+  if (vendor == SSH_VENDOR_OPENSSH)
+    {
+      args[last_arg++] = g_strdup ("-oForwardX11=no");
+      args[last_arg++] = g_strdup ("-oForwardAgent=no");
+      args[last_arg++] = g_strdup ("-oProtocol=2");
+#ifndef USE_PTY
+      args[last_arg++] = g_strdup ("-oBatchMode=yes");
+#endif
+    }
+  else if (vendor == SSH_VENDOR_SSH)
+    args[last_arg++] = g_strdup ("-x");
+
+  args[last_arg++] = g_strdup ("-p");
+  args[last_arg++] = g_strdup_printf ("%d", port);
+
+  args[last_arg++] = g_strdup ("-l");
+  args[last_arg++] = g_strdup (username);
+
+  args[last_arg++] = g_strdup (host);
+
+  for (i=0; i<command_size; i++)
+    args[last_arg++] = g_strdup (command[i]);
+
+  args[last_arg++] = NULL;
+
+  return args;
+}
+
+static gboolean
+spawn_ssh (char *args[],
+           pid_t *pid,
+           int *tty_fd,
+           int *stdin_fd,
+           int *stdout_fd,
+           int *stderr_fd,
+           GError **error)
+{
+#ifdef USE_PTY
+  *tty_fd = pty_open(pid, PTY_REAP_CHILD, NULL,
+		     args[0], args, NULL,
+		     300, 300, 
+		     stdin_fd, stdout_fd, stderr_fd);
+  if (*tty_fd == -1)
+    {
+      g_set_error_literal (error,
+			   VINAGRE_SSH_ERROR,
+			   VINAGRE_SSH_ERROR_FAILED,
+			   _("Unable to spawn ssh program"));
+      return FALSE;
+    }
+#else
+  GError *my_error;
+  GPid gpid;
+  
+  *tty_fd = -1;
+
+  my_error = NULL;
+  if (!g_spawn_async_with_pipes (NULL, args, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
+				 &gpid,
+				 stdin_fd, stdout_fd, stderr_fd, &my_error))
+    {
+      g_set_error (error,
+                   VINAGRE_SSH_ERROR,
+                   VINAGRE_SSH_ERROR_FAILED,
+                   _("Unable to spawn ssh program: %s"), my_error->message);
+      g_error_free (my_error);
+      return FALSE;
+    }
+  *pid = gpid;
+#endif
+  
+  return TRUE;
+}
+
+static const gchar *
+get_authtype_from_password_line (const char *password_line)
+{
+  return g_str_has_prefix (password_line, "Enter passphrase for key") ?
+	  "publickey" : "password";
+}
+
+static char *
+get_object_from_password_line (const char *password_line)
+{
+  char *chr, *ptr, *object = NULL;
+
+  if (g_str_has_prefix (password_line, "Enter passphrase for key"))
+    {
+      ptr = strchr (password_line, '\'');
+      if (ptr != NULL)
+        {
+	  ptr += 1;
+	  chr = strchr (ptr, '\'');
+	  if (chr != NULL)
+	    {
+	      object = g_strndup (ptr, chr - ptr);
+	    }
+	  else
+	    {
+	      object = g_strdup (ptr);
+	    }
+	}
+    }
+  return object;
+}
+
+static gboolean
+get_hostname_and_fingerprint_from_line (const gchar *buffer,
+                                        gchar      **hostname_out,
+                                        gchar      **fingerprint_out)
+{
+  gchar *pos;
+  gchar *startpos;
+  gchar *endpos;
+  gchar *hostname = NULL;
+  gchar *fingerprint = NULL;
+  
+  if (g_str_has_prefix (buffer, "The authenticity of host '"))
+    {
+      /* OpenSSH */
+      pos = strchr (&buffer[26], '\'');
+      if (pos == NULL)
+        return FALSE;
+
+      hostname = g_strndup (&buffer[26], pos - (&buffer[26]));
+
+      startpos = strstr (pos, " key fingerprint is ");
+      if (startpos == NULL)
+        {
+          g_free (hostname);
+          return FALSE;
+        }
+
+      startpos = startpos + 20;
+      endpos = strchr (startpos, '.');
+      if (endpos == NULL)
+        {
+          g_free (hostname);
+          return FALSE;
+        }
+      
+      fingerprint = g_strndup (startpos, endpos - startpos);
+    }
+  else if (strstr (buffer, "Key fingerprint:") != NULL)
+    {
+      /* SSH.com*/
+      startpos = strstr (buffer, "Key fingerprint:");
+      if (startpos == NULL)
+        {
+          g_free (hostname);
+          return FALSE;
+        }
+      
+      startpos = startpos + 18;
+      endpos = strchr (startpos, '\r');
+      fingerprint = g_strndup (startpos, endpos - startpos);
+    }
+
+  *hostname_out = hostname;
+  *fingerprint_out = fingerprint;
+
+  return TRUE;
+}
+
+static gchar *
+ask_password (const gchar *user, const gchar *host)
+{
+  GtkWidget *dialog, *label, *entry, *content_area;
+  gchar *str, *result;
+
+  result = NULL;
+  dialog = gtk_dialog_new_with_buttons (_("Authentication needed"),
+                                        NULL,
+                                        GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_MODAL,
+                                        GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
+                                        GTK_STOCK_CANCEL, GTK_RESPONSE_NONE,
+                                        NULL);
+  content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+  str = g_strdup_printf (_("Enter the SSH password for the username and host %s %s:"), user, host);
+  label = gtk_label_new (str);
+  g_free (str);
+  gtk_container_add (GTK_CONTAINER (content_area), label);
+
+  entry = gtk_entry_new ();
+  gtk_entry_set_visibility (GTK_ENTRY (entry), FALSE);
+  gtk_container_add (GTK_CONTAINER (content_area), entry);
+
+  gtk_widget_show_all (dialog);
+  if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
+    {
+      str = (gchar *)gtk_entry_get_text (GTK_ENTRY (entry));
+      if (str && *str)
+        result = g_strdup (str);
+    }
+
+  gtk_widget_destroy (dialog);
+  return result;
+}
+
+static gboolean
+handle_login (const   gchar *host,
+	      int     port,
+	      const   gchar *user,
+	      int     tty_fd,
+	      int     stdout_fd,
+	      int     stderr_fd,
+              GError  **error)
+{
+  GInputStream *prompt_stream, *stdout_stream;
+  GOutputStream *reply_stream;
+  fd_set ifds;
+  struct timeval tv;
+  int ret;
+  int prompt_fd;
+  char buffer[1024];
+  gsize len;
+  gboolean aborted = FALSE;
+  gboolean ret_val;
+  char *password = NULL;
+  gsize bytes_written;
+  gboolean password_in_keyring = FALSE;
+  const gchar *authtype = NULL;
+  gchar *object = NULL;
+  GnomeKeyringResult result;
+  GList *matches;
+  GnomeKeyringNetworkPasswordData *found_item;
+  
+  if (vendor == SSH_VENDOR_SSH) 
+    prompt_fd = stderr_fd;
+  else
+    prompt_fd = tty_fd;
+
+  prompt_stream = g_unix_input_stream_new (prompt_fd, FALSE);
+  stdout_stream = g_unix_input_stream_new (stdout_fd, FALSE);
+  reply_stream = g_unix_output_stream_new (tty_fd, FALSE);
+
+  ret_val = TRUE;
+  while (1)
+    {
+      FD_ZERO (&ifds);
+      FD_SET (stdout_fd, &ifds);
+      FD_SET (prompt_fd, &ifds);
+      
+      tv.tv_sec = SSH_READ_TIMEOUT;
+      tv.tv_usec = 0;
+      
+      ret = select (MAX (stdout_fd, prompt_fd)+1, &ifds, NULL, NULL, &tv);
+      
+      if (ret <= 0)
+        {
+          g_set_error_literal (error,
+	                       G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
+        	               _("Timed out when logging in"));
+          ret_val = FALSE;
+          break;
+        }
+
+      if (FD_ISSET (stdout_fd, &ifds))  /* Got reply to our check */
+        {
+          len = g_input_stream_read (stdout_stream,
+                                     buffer, sizeof (buffer) - 1,
+                                     NULL, error);
+          if (len == -1)
+            {
+              ret_val = FALSE;
+              break;
+            }
+          buffer[len] = 0;
+          if (strncmp (buffer, VINAGRE_SSH_CHECK, VINAGRE_SSH_CHECK_LENGTH) == 0)
+            break;
+          else
+            {
+	      g_set_error_literal (error,
+				   VINAGRE_SSH_ERROR,
+				   VINAGRE_SSH_ERROR_PERMISSION_DENIED,
+				   _("Permission denied"));
+              ret_val = FALSE;
+              break;
+            }
+        }
+
+      g_assert (FD_ISSET (prompt_fd, &ifds));
+
+      len = g_input_stream_read (prompt_stream,
+                                 buffer, sizeof (buffer) - 1,
+                                 NULL, error);
+      if (len == -1)
+        {
+          ret_val = FALSE;
+          break;
+        }
+      buffer[len] = 0;
+
+      if (strncmp (buffer, "\r\n", 2) == 0)
+        continue;
+        
+      else if (g_str_has_suffix (buffer, "password: ") ||
+          g_str_has_suffix (buffer, "Password: ") ||
+          g_str_has_suffix (buffer, "Password:")  ||
+          g_str_has_prefix (buffer, "Password for ") ||
+          g_str_has_prefix (buffer, "Enter Kerberos password") ||
+          g_str_has_prefix (buffer, "Enter passphrase for key"))
+        {
+	  authtype = get_authtype_from_password_line (buffer);
+	  object = get_object_from_password_line (buffer);
+
+	  /* Search password in the keyring */
+	  result = gnome_keyring_find_network_password_sync (user,		/* username */
+							     NULL,		/* domain */
+							     host,	        /* server */
+							     object,		/* object */
+							     "ssh",		/* protocol */
+							     authtype,		/* authtype */
+							     port,		/* port */
+							     &matches);
+
+	  /* If the password was not found in keyring then ask for it */
+	  if (result != GNOME_KEYRING_RESULT_OK || matches == NULL || matches->data == NULL)
+	    {
+	      password = ask_password (user, host);
+              if (!password)
+                {
+                  g_set_error_literal (error,
+				       VINAGRE_SSH_ERROR,
+                                       VINAGRE_SSH_ERROR_PASSWORD_CANCELED,
+        	                       _("Password dialog canceled"));
+                  ret_val = FALSE;
+                  break;
+                }
+            }
+	  else
+	    {
+	      found_item = (GnomeKeyringNetworkPasswordData *) matches->data;
+	      password = g_strdup (found_item->password);
+	      password_in_keyring = TRUE;
+	      gnome_keyring_network_password_list_free (matches);
+	    }
+
+          if (!g_output_stream_write_all (reply_stream,
+                                          password, strlen (password),
+                                          &bytes_written,
+                                          NULL, NULL) ||
+              !g_output_stream_write_all (reply_stream,
+                                          "\n", 1,
+                                          &bytes_written,
+                                          NULL, NULL))
+            {
+              g_set_error_literal (error,
+	                           G_IO_ERROR, G_IO_ERROR_PERMISSION_DENIED,
+        	                   _("Could not send password"));
+              ret_val = FALSE;
+              break;
+            }
+        }
+      else if (g_str_has_prefix (buffer, "The authenticity of host '") ||
+               strstr (buffer, "Key fingerprint:") != NULL)
+        {
+	  const gchar *choices[] = {_("Log In Anyway"), _("Cancel Login"), NULL};
+	  const gchar *choice_string;
+	  gchar *hostname = NULL;
+	  gchar *fingerprint = NULL;
+	  gint choice;
+	  gchar *message;
+
+	  get_hostname_and_fingerprint_from_line (buffer, &hostname, &fingerprint);
+
+	  message = g_strdup_printf (_("The identity of the remote computer (%s) is unknown.\n"
+				       "This happens when you log in to a computer the first time.\n\n"
+				       "The identity sent by the remote computer is %s. "
+				       "If you want to be absolutely sure it is safe to continue, "
+				       "contact the system administrator."),
+				     hostname ? hostname : host, fingerprint);
+
+	  g_free (hostname);
+	  g_free (fingerprint);
+
+	  if (!vinagre_utils_ask_question (NULL,
+					   message,
+					   (char **)choices,
+					   &choice))
+	    {
+	      g_set_error_literal (error,
+				   VINAGRE_SSH_ERROR,
+				   VINAGRE_SSH_ERROR_PASSWORD_CANCELED,
+				   _("Login dialog canceled"));
+	      g_free (message);
+	      ret_val = FALSE;
+	      break;
+	    }
+	  g_free (message);
+
+	  choice_string = (choice == 0) ? "yes" : "no";
+	  if (!g_output_stream_write_all (reply_stream,
+					  choice_string,
+					  strlen (choice_string),
+					  &bytes_written,
+					  NULL, NULL) ||
+	      !g_output_stream_write_all (reply_stream,
+					  "\n", 1,
+					  &bytes_written,
+					  NULL, NULL))
+	    {
+	      g_set_error_literal (error,
+				   VINAGRE_SSH_ERROR,
+				   VINAGRE_SSH_ERROR_IO_ERROR,
+				   _("Can't send host identity confirmation"));
+	      ret_val = FALSE;
+	      break;
+	    }
+	}
+      else
+        {
+	  g_set_error_literal (error,
+			       VINAGRE_SSH_ERROR,
+			       VINAGRE_SSH_ERROR_PERMISSION_DENIED,
+			       _("Permission denied"));
+	  ret_val = FALSE;
+	  break;
+        }
+      g_free (password);
+    }
+  
+  if (ret_val)
+    {
+      /* Login succeed, save password in keyring */
+/*      g_vfs_keyring_save_password (op_backend->user,
+                                   op_backend->host,
+                                   NULL,
+                                   "sftp",
+				   object,
+				   authtype,
+				   op_backend->port != -1 ?
+				   op_backend->port
+				   :
+				   0, 
+                                   new_password,
+                                   password_save);
+*/
+    }
+
+  g_free (object);
+  g_object_unref (prompt_stream);
+  g_object_unref (stdout_stream);
+  g_object_unref (reply_stream);
+  return ret_val;
+}
+
+static gchar *
+get_username (const gchar *host, const gchar *username)
+{
+  gchar *pos;
+
+  if (username)
+    return g_strdup (username);
+
+  pos = strchr (host, '@');
+  if (pos)
+    return g_strndup (host, pos - host);
+
+  return g_strdup (g_get_user_name ());
+}
+
+static gchar *
+get_hostname (const gchar *host)
+{
+  gchar *pos;
+
+  pos = strchr (host, '@');
+  if (pos)
+    return g_strdup (pos+1);
+
+  return g_strdup (host);
+}
+
+static gboolean
+look_for_stderr_errors (GDataInputStream *error_stream, GError **error)
+{
+  char *line;
+
+  while (1)
+    {
+      line = g_data_input_stream_read_line (error_stream, NULL, NULL, NULL);
+
+      if (line == NULL)
+        {
+          return TRUE;
+        }
+    
+      if (strstr (line, "Permission denied") != NULL)
+        {
+          g_set_error_literal (error,
+	                       VINAGRE_SSH_ERROR, VINAGRE_SSH_ERROR_PERMISSION_DENIED,
+        	               _("Permission denied"));
+          return FALSE;
+        }
+      else if (strstr (line, "Name or service not known") != NULL)
+        {
+          g_set_error_literal (error,
+	                       VINAGRE_SSH_ERROR, VINAGRE_SSH_ERROR_HOST_NOT_FOUND,
+        	               _("Hostname not known"));
+          return FALSE;
+        }
+      else if (strstr (line, "No route to host") != NULL)
+        {
+          g_set_error_literal (error,
+	                       VINAGRE_SSH_ERROR, VINAGRE_SSH_ERROR_HOST_NOT_FOUND,
+        	               _("No route to host"));
+          return FALSE;
+        }
+      else if (strstr (line, "Connection refused") != NULL)
+        {
+          g_set_error_literal (error,
+	                       VINAGRE_SSH_ERROR, VINAGRE_SSH_ERROR_PERMISSION_DENIED,
+        	               _("Connection refused by server"));
+          return FALSE;
+        }
+      else if (strstr (line, "Host key verification failed") != NULL) 
+        {
+          g_set_error_literal (error,
+	                       VINAGRE_SSH_ERROR, VINAGRE_SSH_ERROR_FAILED,
+        	               _("Host key verification failed"));
+          return FALSE;
+        }
+      
+      g_free (line);
+    }
+
+  return TRUE;
+}
+
+gboolean
+vinagre_ssh_connect (const gchar *hostname,
+		     gint port,
+		     const gchar *username,
+		     gchar **extra_arguments,
+		     gchar **command,
+		     gint *tty,
+		     GError **error)
+{
+  int tty_fd, stdin_fd, stdout_fd, stderr_fd;
+  pid_t pid;
+  gchar *user, *host, **args;
+  gboolean res;
+  GInputStream *is;
+  GDataInputStream *error_stream;
+
+  g_return_val_if_fail (hostname != NULL, FALSE);
+
+  if (vendor == SSH_VENDOR_INVALID)
+    vendor = get_ssh_client_vendor ();
+  if (vendor == SSH_VENDOR_INVALID)
+    {
+      g_set_error_literal (error,
+			   VINAGRE_SSH_ERROR,
+			   VINAGRE_SSH_ERROR_INVALID_CLIENT,
+			   _("Unable to find a valid SSH program"));
+      return FALSE;
+    }
+
+  if (port <= 0)
+    port = 22;
+
+  user = get_username (hostname, username);
+  host = get_hostname (hostname);
+
+  args = setup_ssh_commandline (host, port, user, extra_arguments, command);
+  if (!spawn_ssh (args,
+		  &pid,
+		  &tty_fd, &stdin_fd, &stdout_fd, &stderr_fd,
+		  error))
+    {
+      g_strfreev (args);
+      g_free (user);
+      g_free (host);
+      return FALSE;
+    }
+
+  if (tty_fd == -1)
+    res = wait_for_reply (stdout_fd, error);
+  else
+    res = handle_login (host, port, user, tty_fd, stdout_fd, stderr_fd, error);
+
+  g_strfreev (args);
+  g_free (user);
+  g_free (host);
+
+  if (!res)
+    return FALSE;
+
+  fcntl (stderr_fd, F_SETFL, O_NONBLOCK | fcntl (stderr_fd, F_GETFL));
+  is = g_unix_input_stream_new (stderr_fd, FALSE);
+  error_stream = g_data_input_stream_new (is);
+  g_object_unref (is);
+  
+  res = look_for_stderr_errors (error_stream, error);
+  g_object_unref (error_stream);
+
+  if (!res)
+    return FALSE;
+
+  if (tty)
+    *tty = tty_fd;
+
+  return TRUE;
+}
+
+GQuark 
+vinagre_ssh_error_quark (void)
+{
+  static GQuark quark = 0;
+
+  if (!quark)
+    quark = g_quark_from_string ("vinagre_ssh_error");
+
+  return quark;
+}
+
+/* vim: set ts=8: */
diff --git a/vinagre/vinagre-ssh.h b/vinagre/vinagre-ssh.h
new file mode 100644
index 0000000..277516a
--- /dev/null
+++ b/vinagre/vinagre-ssh.h
@@ -0,0 +1,57 @@
+/*
+ * vinagre-ssh.h
+ * SSH Utilities for Vinagre
+ * This file is part of vinagre
+ *
+ * Copyright (C) 2009 - Jonh Wendell <wendell bani com br>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __VINAGRE_SSH_H__
+#define __VINAGRE_SSH_H__
+
+#define VINAGRE_SSH_CHECK "ViNagRE_CHEck"
+#define VINAGRE_SSH_CHECK_LENGTH 13
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+typedef enum 
+{
+  VINAGRE_SSH_ERROR_FAILED = 1,
+  VINAGRE_SSH_ERROR_INVALID_CLIENT,
+  VINAGRE_SSH_ERROR_TIMEOUT,
+  VINAGRE_SSH_ERROR_PASSWORD_CANCELED,
+  VINAGRE_SSH_ERROR_IO_ERROR,
+  VINAGRE_SSH_ERROR_PERMISSION_DENIED,
+  VINAGRE_SSH_ERROR_HOST_NOT_FOUND
+} VinagreSshError;
+
+#define VINAGRE_SSH_ERROR vinagre_ssh_error_quark()
+GQuark vinagre_ssh_error_quark (void);
+
+gboolean vinagre_ssh_connect (const gchar *hostname,
+			      gint port,
+			      const gchar *username,
+			      gchar **extra_arguments,
+			      gchar **command,
+			      gint *tty,
+			      GError **error);
+
+G_END_DECLS
+
+#endif  /* __VINAGRE_SSH_H__  */
+/* vim: set ts=8: */
diff --git a/vinagre/vinagre-utils.c b/vinagre/vinagre-utils.c
index b0c2944..3469312 100644
--- a/vinagre/vinagre-utils.c
+++ b/vinagre/vinagre-utils.c
@@ -601,4 +601,11 @@ vinagre_utils_ask_question (GtkWindow  *parent,
   return TRUE;
 }
 
+#include "vinagre-ssh.h"
+static void
+shit (void)
+{
+  vinagre_ssh_connect (NULL, -1, NULL, NULL, NULL, NULL, NULL);
+}
+
 /* vim: set ts=8: */



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]