add SSH tunnel stuff
authorAlfons Hoogervorst <alfons@proteus.demon.nl>
Sat, 22 Dec 2001 11:22:38 +0000 (11:22 +0000)
committerAlfons Hoogervorst <alfons@proteus.demon.nl>
Sat, 22 Dec 2001 11:22:38 +0000 (11:22 +0000)
ChangeLog.claws
configure.in
src/imap.c
src/imap.h
src/prefs_account.c
src/prefs_account.h
src/socket.c
src/socket.h

index 4c8b29d22ef21c2090be05a113cfbf241f7cf879..bcade6edc780ed8e68777e56f1ceaa8dcc6ef30b 100644 (file)
@@ -1,3 +1,11 @@
+2001-12-22 [alfons]    0.6.6claws8
+
+       * src/socket.[ch]
+       * src/imap.[ch]
+       * src/prefs_account.[ch]
+               allow IMAP over SSH tunnel (patch submitted by 
+               Martin Pool <mbp@samba.org>)
+
 2001-12-21 [alfons]    0.6.6claws7
 
        * sync with sylpheed 0.6.6cvs4-9
index c94e30d532fa1b2a8b42687885607d3445fcab76..c6f45e2f7943fe3f271dfcb6448989b2cf83b6fd 100644 (file)
@@ -8,7 +8,7 @@ MINOR_VERSION=6
 MICRO_VERSION=6
 INTERFACE_AGE=0
 BINARY_AGE=0
-EXTRA_VERSION=claws7
+EXTRA_VERSION=claws8
 VERSION=$MAJOR_VERSION.$MINOR_VERSION.$MICRO_VERSION$EXTRA_VERSION
 
 dnl
index 979658292b514172061e62dbf960610c110af8a1..b7737f81b5772353aa81f78392c12c293d860f81 100644 (file)
@@ -33,6 +33,7 @@
 #include <time.h>
 
 #include "intl.h"
+#include "prefs_account.h"
 #include "imap.h"
 #include "socket.h"
 #include "ssl.h"
@@ -41,7 +42,6 @@
 #include "procheader.h"
 #include "folder.h"
 #include "statusbar.h"
-#include "prefs_account.h"
 #include "codeconv.h"
 #include "utils.h"
 #include "inputdialog.h"
@@ -103,14 +103,17 @@ static void imap_delete_all_cached_messages       (FolderItem     *item);
 #if USE_SSL
 static SockInfo *imap_open             (const gchar    *server,
                                         gushort         port,
-                                        gchar          *buf,
                                         gboolean        use_ssl);
 #else
 static SockInfo *imap_open             (const gchar    *server,
-                                        gushort         port,
-                                        gchar          *buf);
+                                        gushort         port);
 #endif
 
+static SockInfo *imap_open_tunnel(const gchar *server,
+                                 const gchar *tunnelcmd);
+
+static SockInfo *imap_init_sock(SockInfo *sock);
+
 static gint imap_set_message_flags     (IMAPSession    *session,
                                         guint32         first_uid,
                                         guint32         last_uid,
@@ -159,6 +162,8 @@ static gchar *imap_parse_address    (SockInfo       *sock,
 static MsgFlags imap_parse_flags       (const gchar    *flag_str);
 static MsgInfo *imap_parse_envelope    (SockInfo       *sock,
                                         GString        *line_str);
+static gint imap_greeting              (SockInfo *sock,
+                                        gboolean *is_preauth);
 
 /* low-level IMAP4rev1 commands */
 static gint imap_cmd_login     (SockInfo       *sock,
@@ -238,6 +243,8 @@ static gchar *search_array_contain_str              (GPtrArray      *array,
                                                 gchar          *str);
 static gchar *search_array_str                 (GPtrArray      *array,
                                                 gchar          *str);
+static gchar *search_array_starts              (GPtrArray *array,
+                                                const gchar *str);
 static void imap_path_separator_subst          (gchar          *str,
                                                 gchar           separator);
 
@@ -260,16 +267,7 @@ static IMAPSession *imap_session_get(Folder *folder)
 
        if (!rfolder->session) {
                rfolder->session =
-#if USE_SSL
-                       imap_session_new(folder->account->recv_server, port,
-                                        folder->account->userid,
-                                        folder->account->passwd,
-                                        folder->account->ssl_imap);
-#else
-                       imap_session_new(folder->account->recv_server, port,
-                                        folder->account->userid,
-                                        folder->account->passwd);
-#endif
+                       imap_session_new(folder->account);
                if (rfolder->session) {
                        imap_parse_namespace(IMAP_SESSION(rfolder->session),
                                             IMAP_FOLDER(folder));
@@ -279,6 +277,14 @@ static IMAPSession *imap_session_get(Folder *folder)
                return IMAP_SESSION(rfolder->session);
        }
 
+       /* I think the point of this code is to avoid sending a
+        * keepalive if we've used the session recently and therefore
+        * think it's still alive.  Unfortunately, most of the code
+        * does not yet check for errors on the socket, and so if the
+        * connection drops we don't notice until the timeout expires.
+        * A better solution than sending a NOOP every time would be
+        * for every command to be prepared to retry until it is
+        * successfully sent. -- mbp */
        if (time(NULL) - rfolder->session->last_access_time < SESSION_TIMEOUT) {
                rfolder->session->last_access_time = time(NULL);
                statusbar_pop_all();
@@ -291,16 +297,7 @@ static IMAPSession *imap_session_get(Folder *folder)
                            folder->account->recv_server, port);
                session_destroy(rfolder->session);
                rfolder->session =
-#if USE_SSL
-                       imap_session_new(folder->account->recv_server, port,
-                                        folder->account->userid,
-                                        folder->account->passwd,
-                                        folder->account->ssl_imap);
-#else
-                       imap_session_new(folder->account->recv_server, port,
-                                        folder->account->userid,
-                                        folder->account->passwd);
-#endif
+                       imap_session_new(folder->account);
                if (rfolder->session)
                        imap_parse_namespace(IMAP_SESSION(rfolder->session),
                                             IMAP_FOLDER(folder));
@@ -325,49 +322,69 @@ static gchar *imap_query_password(const gchar *server, const gchar *user)
        return pass;
 }
 
-#if USE_SSL
-Session *imap_session_new(const gchar *server, gushort port,
-                         const gchar *user, const gchar *pass,
-                         gboolean use_ssl)
-#else
-Session *imap_session_new(const gchar *server, gushort port,
-                         const gchar *user, const gchar *pass)
-#endif
+Session *imap_session_new(const PrefsAccount *account)
 {
-       gchar buf[IMAPBUFSIZE];
        IMAPSession *session;
        SockInfo *imap_sock;
+       gushort port;
+       gchar *pass;
+       gboolean is_preauth;
 
-       g_return_val_if_fail(server != NULL, NULL);
-       g_return_val_if_fail(user != NULL, NULL);
+#ifdef USE_SSL
+       port = account->set_imapport ? account->imapport
+               : account->ssl_imap ? IMAPS_PORT : IMAP4_PORT;
+#else
+       port = account->set_imapport ? account->imapport
+               : IMAP4_PORT;
+#endif
 
-       if (!pass) {
-               gchar *tmp_pass;
-               tmp_pass = imap_query_password(server, user);
-               if (!tmp_pass)
+       if (account->set_tunnelcmd) {
+               log_message(_("creating tunneled IMAP4 connection\n"));
+               if ((imap_sock = imap_open_tunnel(account->recv_server, 
+                                                 account->tunnelcmd)) == NULL)
                        return NULL;
-               Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return NULL;});
-               g_free(tmp_pass);
-       }
-
-       log_message(_("creating IMAP4 connection to %s:%d ...\n"),
-                   server, port);
+       } else {
+               g_return_val_if_fail(account->recv_server != NULL, NULL);
 
+               log_message(_("creating IMAP4 connection to %s:%d ...\n"),
+                           account->recv_server, port);
+               
 #if USE_SSL
-       if ((imap_sock = imap_open(server, port, buf, use_ssl)) == NULL)
+               if ((imap_sock = imap_open(account->recv_server, port,
+                                          account->use_ssl)) == NULL)
 #else
-       if ((imap_sock = imap_open(server, port, buf)) == NULL)
+               if ((imap_sock = imap_open(account->recv_server, port)) == NULL)
 #endif
-               return NULL;
-       if (imap_cmd_login(imap_sock, user, pass) != IMAP_SUCCESS) {
-               imap_cmd_logout(imap_sock);
-               sock_close(imap_sock);
-               return NULL;
+                       return NULL;
+       }
+
+       /* Only need to log in if the connection was not PREAUTH */
+       imap_greeting(imap_sock, &is_preauth);
+       log_message("IMAP connection is %s-authenticated\n",
+                   (is_preauth) ? "pre" : "un");
+       if (!is_preauth) {
+               g_return_val_if_fail(account->userid != NULL, NULL);
+
+               pass = account->passwd;
+               if (!pass) {
+                       gchar *tmp_pass;
+                       tmp_pass = imap_query_password(account->recv_server, account->userid);
+                       if (!tmp_pass)
+                               return NULL;
+                       Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return NULL;});
+                       g_free(tmp_pass);
+               }
+
+               if (imap_cmd_login(imap_sock, account->userid, pass) != IMAP_SUCCESS) {
+                       imap_cmd_logout(imap_sock);
+                       sock_close(imap_sock);
+                       return NULL;
+               }
        }
 
        session = g_new(IMAPSession, 1);
        SESSION(session)->type             = SESSION_IMAP;
-       SESSION(session)->server           = g_strdup(server);
+       SESSION(session)->server           = g_strdup(account->recv_server);
        SESSION(session)->sock             = imap_sock;
        SESSION(session)->connected        = TRUE;
        SESSION(session)->phase            = SESSION_READY;
@@ -1320,11 +1337,24 @@ static void imap_delete_all_cached_messages(FolderItem *item)
        debug_print(_("done.\n"));
 }
 
+
+static SockInfo *imap_open_tunnel(const gchar *server,
+                          const gchar *tunnelcmd)
+{
+       SockInfo *sock;
+
+       if ((sock = sock_connect_cmd(server, tunnelcmd)) == NULL)
+               return NULL;
+
+       return imap_init_sock(sock);
+}
+
+
 #if USE_SSL
-static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf,
+static SockInfo *imap_open(const gchar *server, gushort port,
                           gboolean use_ssl)
 #else
-static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf)
+static SockInfo *imap_open(const gchar *server, gushort port)
 #endif
 {
        SockInfo *sock;
@@ -1342,16 +1372,18 @@ static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf)
        }
 #endif
 
-       imap_cmd_count = 0;
+       return imap_init_sock(sock);
+}
 
-       if (imap_cmd_noop(sock) != IMAP_SUCCESS) {
-               sock_close(sock);
-               return NULL;
-       }
 
+static SockInfo *imap_init_sock(SockInfo *sock)
+{
+       imap_cmd_count = 0;
        return sock;
 }
 
+
+
 #define THROW goto catch
 
 static void imap_parse_namespace(IMAPSession *session, IMAPFolder *folder)
@@ -2003,6 +2035,21 @@ static gint imap_cmd_logout(SockInfo *sock)
        return imap_cmd_ok(sock, NULL);
 }
 
+/* Send a NOOP, and examine the server's response to see whether this
+ * connection is pre-authenticated or not. */
+static gint imap_greeting(SockInfo *sock, gboolean *is_preauth)
+{
+       GPtrArray *argbuf;
+       gint r;
+
+       imap_cmd_gen_send(sock, "NOOP");
+       argbuf = g_ptr_array_new(); /* will hold messages sent back */
+       r = imap_cmd_ok(sock, argbuf);
+       *is_preauth = search_array_starts(argbuf, "PREAUTH") != NULL;
+       
+       return r;
+}
+
 static gint imap_cmd_noop(SockInfo *sock)
 {
        imap_cmd_gen_send(sock, "NOOP");
@@ -2275,6 +2322,7 @@ static gint imap_cmd_expunge(SockInfo *sock)
        return IMAP_SUCCESS;
 }
 
+
 static gint imap_cmd_ok(SockInfo *sock, GPtrArray *argbuf)
 {
        gint ok;
@@ -2381,6 +2429,22 @@ static gchar *get_quoted(const gchar *src, gchar ch, gchar *dest, gint len)
        return (gchar *)(*p == ch ? p + 1 : p);
 }
 
+static gchar *search_array_starts(GPtrArray *array, const gchar *str)
+{
+       gint i;
+       size_t len;
+
+       g_return_val_if_fail(str != NULL, NULL);
+       len = strlen(str);
+       for (i = 0; i < array->len; i++) {
+               gchar *tmp;
+               tmp = g_ptr_array_index(array, i);
+               if (strncmp(tmp, str, len) == 0)
+                       return tmp;
+       }
+       return NULL;
+}
+
 static gchar *search_array_contain_str(GPtrArray *array, gchar *str)
 {
        gint i;
index 4d350422e20a20cf8270f9297261eae5ca6d829c..4c3beeeb46de56c546496365bee0a4257ded925a 100644 (file)
@@ -77,18 +77,7 @@ typedef enum
 #define IMAP_IS_DELETED(flags) ((flags & IMAP_FLAG_DELETED) != 0)
 #define IMAP_IS_DRAFT(flags)   ((flags & IMAP_FLAG_DRAFT) != 0)
 
-#if USE_SSL
-Session *imap_session_new              (const gchar    *server,
-                                        gushort         port,
-                                        const gchar    *user,
-                                        const gchar    *pass,
-                                        gboolean        use_ssl);
-#else
-Session *imap_session_new              (const gchar    *server,
-                                        gushort         port,
-                                        const gchar    *user,
-                                        const gchar    *pass);
-#endif
+Session *imap_session_new              (const PrefsAccount *account);
 void imap_session_destroy              (IMAPSession    *session);
 void imap_session_destroy_all          (void);
 
index 009eec3e3d12f0bc0fd29f51e522f68b870c5fb4..17dc10e228550b167e74ba40a2883e2a77e9930c 100644 (file)
@@ -155,6 +155,8 @@ static struct Advanced {
        GtkWidget *nntpport_entry;
        GtkWidget *domain_chkbtn;
        GtkWidget *domain_entry;
+       GtkWidget *tunnelcmd_chkbtn;
+       GtkWidget *tunnelcmd_entry;
 } advanced;
 
 static void prefs_account_fix_size                     (void);
@@ -367,6 +369,14 @@ static PrefParam param[] = {
         &advanced.domain_entry,
         prefs_set_data_from_entry, prefs_set_entry},
 
+       {"set_tunnelcmd", "FALSE", &tmp_ac_prefs.set_tunnelcmd, P_BOOL,
+        &advanced.tunnelcmd_chkbtn,
+        prefs_set_data_from_toggle, prefs_set_toggle},
+
+       {"tunnelcmd", NULL, &tmp_ac_prefs.tunnelcmd, P_STRING,
+        &advanced.tunnelcmd_entry,
+        prefs_set_data_from_entry, prefs_set_entry},
+
        {NULL, NULL, NULL, P_OTHER, NULL, NULL, NULL}
 };
 
@@ -1401,21 +1411,23 @@ static void prefs_account_advanced_create(void)
        GtkWidget *entry_nntpport;
        GtkWidget *checkbtn_domain;
        GtkWidget *entry_domain;
+       GtkWidget *checkbtn_tunnelcmd;
+       GtkWidget *entry_tunnelcmd;
 
 #define PACK_HBOX(hbox) \
-{ \
+       { \
        hbox = gtk_hbox_new (FALSE, 8); \
        gtk_widget_show (hbox); \
        gtk_box_pack_start (GTK_BOX (vbox2), hbox, FALSE, FALSE, 0); \
-}
+       }
 
 #define PACK_PORT_ENTRY(box, entry) \
-{ \
+       { \
        entry = gtk_entry_new_with_max_length (5); \
        gtk_widget_show (entry); \
        gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0); \
        gtk_widget_set_usize (entry, 64, -1); \
-}
+       }
 
        vbox1 = gtk_vbox_new (FALSE, VSPACING);
        gtk_widget_show (vbox1);
@@ -1457,6 +1469,15 @@ static void prefs_account_advanced_create(void)
        gtk_box_pack_start (GTK_BOX (hbox1), entry_domain, TRUE, TRUE, 0);
        SET_TOGGLE_SENSITIVITY (checkbtn_domain, entry_domain);
 
+       
+       PACK_HBOX (hbox1);
+       PACK_CHECK_BUTTON (hbox1, checkbtn_tunnelcmd,
+                          _("Tunnel command to open connection"));
+       entry_tunnelcmd = gtk_entry_new ();
+       gtk_widget_show (entry_tunnelcmd);
+       gtk_box_pack_start (GTK_BOX (hbox1), entry_tunnelcmd, TRUE, TRUE, 0);
+       SET_TOGGLE_SENSITIVITY (checkbtn_tunnelcmd, entry_tunnelcmd);
+
 #undef PACK_HBOX
 #undef PACK_PORT_ENTRY
 
@@ -1473,6 +1494,8 @@ static void prefs_account_advanced_create(void)
        advanced.nntpport_entry         = entry_nntpport;
        advanced.domain_chkbtn          = checkbtn_domain;
        advanced.domain_entry           = entry_domain;
+       advanced.tunnelcmd_chkbtn       = checkbtn_tunnelcmd;
+       advanced.tunnelcmd_entry        = entry_tunnelcmd;
 }
 
 static gint prefs_account_deleted(GtkWidget *widget, GdkEventAny *event,
index 43e6fbb4971d05fd5d50f18742085aff7502a2cb..20e9370b7164f70cb359b655ec3c4ec05b3afabb 100644 (file)
@@ -135,6 +135,17 @@ struct _PrefsAccount
        gboolean  set_domain;
        gchar    *domain;
 
+       /* Use this command to open a socket, rather than doing so
+        * directly.  Good if you want to perhaps use a special socks
+        * tunnel command, or run IMAP-over-SSH.  In this case the
+        * server, port etc are only for the user's own information
+        * and are not used.  username and password are used to
+        * authenticate the account only if necessary, since some
+        * tunnels will implicitly authenticate by running e.g. imapd
+        * as a particular user. */
+       gboolean  set_tunnelcmd;
+       gchar     *tunnelcmd;
+
        /* Default or not */
        gboolean is_default;
        /* Unique account ID */
index 93617be52f7dbb4088aa7ac9ff7f82183f8b1fd6..3395369e217f1b22a82c74737ba27a92fad9a0bd 100644 (file)
@@ -29,6 +29,7 @@
 #include <netinet/in.h>
 #include <arpa/inet.h>
 #include <netdb.h>
+#include <signal.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <string.h>
@@ -41,6 +42,7 @@
 #endif
 
 #include "socket.h"
+#include "utils.h"
 #if USE_SSL
 #  include "ssl.h"
 #endif
@@ -60,6 +62,9 @@ static gint sock_connect_by_getaddrinfo       (const gchar    *hostname,
                                         gushort         port);
 #endif
 
+static SockInfo *sockinfo_from_fd(const gchar *hostname,
+                                 gushort port,
+                                 gint sock);
 
 gint fd_connect_unix(const gchar *path)
 {
@@ -261,10 +266,38 @@ static gint sock_connect_by_getaddrinfo(const gchar *hostname, gushort    port)
 }
 #endif /* !INET6 */
 
+
+/* Open a connection using an external program.  May be useful when
+ * you need to tunnel through a SOCKS or other firewall, or to
+ * establish an IMAP-over-SSH connection. */
+/* TODO: Recreate this for sock_connect_thread() */
+SockInfo *sock_connect_cmd(const gchar *hostname, const gchar *tunnelcmd)
+{
+       gint fd[2];
+       int r;
+                    
+       if ((r = socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) == -1) {
+               perror("socketpair");
+               return NULL;
+       }
+       log_message("launching tunnel command \"%s\"\n", tunnelcmd);
+       if (fork() == 0) {
+               close(fd[0]);
+               close(0);
+               close(1);
+               dup(fd[1]);     /* set onto stdin */
+               dup(fd[1]);
+               execlp("/bin/sh", "/bin/sh", "-c", tunnelcmd, NULL);
+       }
+
+       close(fd[1]);
+       return sockinfo_from_fd(hostname, 0, fd[0]);
+}
+
+
 SockInfo *sock_connect(const gchar *hostname, gushort port)
 {
        gint sock;
-       SockInfo *sockinfo;
 
 #ifdef INET6
        if ((sock = sock_connect_by_getaddrinfo(hostname, port)) < 0)
@@ -282,6 +315,16 @@ SockInfo *sock_connect(const gchar *hostname, gushort port)
        }
 #endif /* INET6 */
 
+       return sockinfo_from_fd(hostname, port, sock);
+}
+
+
+static SockInfo *sockinfo_from_fd(const gchar *hostname,
+                                 gushort port,
+                                 gint sock)
+{
+       SockInfo *sockinfo;
+       
        sockinfo = g_new0(SockInfo, 1);
        sockinfo->sock = sock;
        sockinfo->hostname = g_strdup(hostname);
@@ -393,9 +436,12 @@ gint fd_write(gint fd, const gchar *buf, gint len)
        gint n, wrlen = 0;
 
        while (len) {
+               signal(SIGPIPE, SIG_IGN);
                n = write(fd, buf, len);
-               if (n <= 0)
+               if (n <= 0) {
+                       log_error("write on fd%d: %s\n", fd, strerror(errno));
                        return -1;
+               }
                len -= n;
                wrlen += n;
                buf += n;
@@ -497,6 +543,13 @@ gchar *fd_getline(gint fd)
                if (buf[len - 1] == '\n')
                        break;
        }
+       if (len == -1) {
+               log_error("Read from socket fd%d failed: %s\n",
+                         fd, strerror(errno));
+               if (str)
+                       g_free(str);
+               return NULL;
+       }
 
        return str;
 }
index b5a3d456acb902e991940d746d71d292940862b4..4b1bb02c9ccb30cc8d7242915bb408f79c926bb1 100644 (file)
@@ -73,7 +73,7 @@ gboolean sock_is_nonblocking_mode     (SockInfo *sock);
 
 SockInfo *sock_connect_nb              (const gchar *hostname, gushort port);
 SockInfo *sock_connect                 (const gchar *hostname, gushort port);
-
+SockInfo *sock_connect_cmd             (const gchar *hostname, const gchar *tunnelcmd);
 
 #if USE_THREADS
 SockInfo *sock_connect_with_thread     (const gchar *hostname, gushort port);