#include <time.h>
#include "intl.h"
+#include "prefs_account.h"
#include "imap.h"
#include "socket.h"
#include "ssl.h"
#include "procheader.h"
#include "folder.h"
#include "statusbar.h"
-#include "prefs_account.h"
#include "codeconv.h"
#include "utils.h"
#include "inputdialog.h"
#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,
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,
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);
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));
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();
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));
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;
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;
}
#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)
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");
return IMAP_SUCCESS;
}
+
static gint imap_cmd_ok(SockInfo *sock, GPtrArray *argbuf)
{
gint ok;
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;
GtkWidget *nntpport_entry;
GtkWidget *domain_chkbtn;
GtkWidget *domain_entry;
+ GtkWidget *tunnelcmd_chkbtn;
+ GtkWidget *tunnelcmd_entry;
} advanced;
static void prefs_account_fix_size (void);
&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}
};
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);
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
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,
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
+#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#endif
#include "socket.h"
+#include "utils.h"
#if USE_SSL
# include "ssl.h"
#endif
gushort port);
#endif
+static SockInfo *sockinfo_from_fd(const gchar *hostname,
+ gushort port,
+ gint sock);
gint fd_connect_unix(const gchar *path)
{
}
#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)
}
#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);
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;
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;
}