[vinagre] Initial support to ssh tunneling
- From: Jonh Wendell <jwendell src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [vinagre] Initial support to ssh tunneling
- Date: Mon, 25 Jan 2010 17:42:08 +0000 (UTC)
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]