quick fix for martin's omission of a account specific setting. imap over ssh tunnel...
[claws.git] / src / imap.c
index 43e4b944dff8db24ade471153a221316833eb0cc..68f5dc58e96bede209cfea8d3cb52cf22530a8bd 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"
 #define IMAPS_PORT     993
 #endif
 
+#define QUOTE_IF_REQUIRED(out, str) \
+{ \
+       if (*str != '"' && strchr(str, ' ')) { \
+               gchar *__tmp; \
+               gint len; \
+ \
+               len = strlen(str) + 3; \
+               Xalloca(__tmp, len, return IMAP_ERROR); \
+               g_snprintf(__tmp, len, "\"%s\"", str); \
+               out = __tmp; \
+       } else { \
+               Xstrdup_a(out, str, return IMAP_ERROR); \
+       } \
+}
+
 static GList *session_list = NULL;
 
 static gint imap_cmd_count = 0;
@@ -59,7 +74,7 @@ static IMAPSession *imap_session_get  (Folder         *folder);
 static gchar *imap_query_password      (const gchar    *server,
                                         const gchar    *user);
 
-static void imap_scan_tree_recursive   (IMAPSession    *session,
+static gint imap_scan_tree_recursive   (IMAPSession    *session,
                                         FolderItem     *item,
                                         IMAPNameSpace  *namespace);
 static GSList *imap_parse_list         (IMAPSession    *session,
@@ -88,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,
@@ -144,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,
@@ -221,6 +241,10 @@ static gchar *get_quoted                   (const gchar    *src,
                                                 gint            len);
 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);
 
@@ -243,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));
@@ -262,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();
@@ -274,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));
@@ -308,49 +322,73 @@ 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
+       /* FIXME: IMAP over SSL only... */ 
+       gboolean use_ssl;
 
-       if (!pass) {
-               gchar *tmp_pass;
-               tmp_pass = imap_query_password(server, user);
-               if (!tmp_pass)
-                       return NULL;
-               Xstrdup_a(pass, tmp_pass, {g_free(tmp_pass); return NULL;});
-               g_free(tmp_pass);
-       }
+       port = account->set_imapport ? account->imapport
+               : account->ssl_imap ? IMAPS_PORT : IMAP4_PORT;
+       use_ssl = account->ssl_imap ? TRUE : FALSE;     
+#else
+       port = account->set_imapport ? account->imapport
+               : IMAP4_PORT;
+#endif
 
-       log_message(_("creating IMAP4 connection to %s:%d ...\n"),
-                   server, port);
+       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;
+       } 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,
+                                          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;
@@ -708,18 +746,6 @@ gint imap_remove_msg(Folder *folder, FolderItem *item, gint uid)
        return IMAP_SUCCESS;
 }
 
-#define QUOTE_IF_REQUIRED(out, str) \
-{ \
-       if (*str != '"' && strchr(str, ' ')) { \
-               gint len; \
-               len = strlen(str) + 3; \
-               Xalloca(out, len, return IMAP_ERROR); \
-               g_snprintf(out, len, "\"%s\"", str); \
-       } else { \
-               Xstrdup_a(out, str, return IMAP_ERROR); \
-       } \
-}
-
 gint imap_remove_all_msg(Folder *folder, FolderItem *item)
 {
        gint exists, recent, unseen;
@@ -833,48 +859,53 @@ void imap_scan_tree(Folder *folder)
                imap_create_trash(folder);
 }
 
-static void imap_scan_tree_recursive(IMAPSession *session,
-                                    FolderItem *item,
+static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item,
                                     IMAPNameSpace *namespace)
 {
        IMAPFolder *imapfolder;
        FolderItem *new_item;
        GSList *item_list, *cur;
-       gchar *real_path, *wildcard_path, *wildcard_path_;
+       gchar *real_path;
+       gchar *wildcard_path;
+       gchar separator = '/';
+       gchar wildcard[3];
 
-       g_return_if_fail(item != NULL);
-       g_return_if_fail(item->folder != NULL);
-       g_return_if_fail(item->no_sub == FALSE);
+       g_return_val_if_fail(item != NULL, -1);
+       g_return_val_if_fail(item->folder != NULL, -1);
+       g_return_val_if_fail(item->no_sub == FALSE, -1);
 
        imapfolder = IMAP_FOLDER(item->folder);
 
+       if (namespace && namespace->separator)
+               separator = namespace->separator;
+
        if (item->folder->ui_func)
                item->folder->ui_func(item->folder, item,
                                      item->folder->ui_func_data);
 
        if (item->path) {
+               wildcard[0] = separator;
+               wildcard[1] = '%';
+               wildcard[2] = '\0';
                real_path = imap_get_real_path(imapfolder, item->path);
-               Xstrconcat_a(wildcard_path, real_path,"/%", return IMAP_ERROR);
-               QUOTE_IF_REQUIRED(wildcard_path_, wildcard_path);
-               imap_cmd_gen_send(SESSION(session)->sock, "LIST \"\" %s",
-                                 wildcard_path_,
-                                 namespace && namespace->separator
-                                 ? namespace->separator : '/');
-               g_free(wildcard_path);
        } else {
+               wildcard[0] = '%';
+               wildcard[1] = '\0';
                real_path = g_strdup(namespace && namespace->name
                                     ? namespace->name : "");
-               Xstrconcat_a(wildcard_path, real_path, "%", return IMAP_ERROR);
-               QUOTE_IF_REQUIRED(wildcard_path_, wildcard_path);
-               imap_cmd_gen_send(SESSION(session)->sock, "LIST \"\" %s",
-                                 wildcard_path_);
-               g_free(wildcard_path);
        }
 
-       strtailchomp(real_path, namespace && namespace->separator
-                    ? namespace->separator : '/');
+       Xstrcat_a(wildcard_path, real_path, wildcard,
+                 {g_free(real_path); return IMAP_ERROR;});
+       QUOTE_IF_REQUIRED(wildcard_path, wildcard_path);
 
+       imap_cmd_gen_send(SESSION(session)->sock, "LIST \"\" %s",
+                         wildcard_path);
+
+       strtailchomp(real_path, separator);
        item_list = imap_parse_list(session, real_path);
+       g_free(real_path);
+
        for (cur = item_list; cur != NULL; cur = cur->next) {
                new_item = cur->data;
                if (!strcmp(new_item->path, "INBOX")) {
@@ -897,6 +928,8 @@ static void imap_scan_tree_recursive(IMAPSession *session,
                if (new_item->no_sub == FALSE)
                        imap_scan_tree_recursive(session, new_item, namespace);
        }
+
+       return IMAP_SUCCESS;
 }
 
 static GSList *imap_parse_list(IMAPSession *session, const gchar *path)
@@ -1308,11 +1341,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;
@@ -1330,16 +1376,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)
@@ -1600,6 +1648,8 @@ static MsgFlags imap_parse_flags(const gchar *flag_str)
                        MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
                } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
                        MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
+               } else if (g_strncasecmp(p, "Answered", 8) == 0) {
+                       MSG_SET_PERM_FLAGS(flags, MSG_REPLIED);
                }
        }
 
@@ -1758,6 +1808,72 @@ static MsgInfo *imap_parse_envelope(SockInfo *sock, GString *line_str)
        return msginfo;
 }
 
+gint imap_msg_set_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
+{
+       Folder *folder;
+       IMAPSession *session;
+       IMAPFlags iflags = 0;
+       gint ok = IMAP_SUCCESS;
+
+       g_return_val_if_fail(msginfo != NULL, -1);
+       g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1);
+       g_return_val_if_fail(msginfo->folder != NULL, -1);
+       g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
+
+       folder = msginfo->folder->folder;
+       g_return_val_if_fail(folder->type == F_IMAP, -1);
+
+       session = imap_session_get(folder);
+       if (!session) return -1;
+
+       if (flags & MSG_MARKED)  iflags |= IMAP_FLAG_FLAGGED;
+       if (flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED;
+       if (iflags) {
+               ok = imap_set_message_flags(session, msginfo->msgnum,
+                                           msginfo->msgnum, iflags, TRUE);
+               if (ok != IMAP_SUCCESS) return ok;
+       }
+
+       if (flags & MSG_UNREAD)
+               ok = imap_set_message_flags(session, msginfo->msgnum,
+                                           msginfo->msgnum, IMAP_FLAG_SEEN,
+                                           FALSE);
+       return ok;
+}
+
+gint imap_msg_unset_perm_flags(MsgInfo *msginfo, MsgPermFlags flags)
+{
+       Folder *folder;
+       IMAPSession *session;
+       IMAPFlags iflags = 0;
+       gint ok = IMAP_SUCCESS;
+
+       g_return_val_if_fail(msginfo != NULL, -1);
+       g_return_val_if_fail(MSG_IS_IMAP(msginfo->flags), -1);
+       g_return_val_if_fail(msginfo->folder != NULL, -1);
+       g_return_val_if_fail(msginfo->folder->folder != NULL, -1);
+
+       folder = msginfo->folder->folder;
+       g_return_val_if_fail(folder->type == F_IMAP, -1);
+
+       session = imap_session_get(folder);
+       if (!session) return -1;
+
+       if (flags & MSG_MARKED)  iflags |= IMAP_FLAG_FLAGGED;
+       if (flags & MSG_REPLIED) iflags |= IMAP_FLAG_ANSWERED;
+       if (iflags) {
+               ok = imap_set_message_flags(session, msginfo->msgnum,
+                                           msginfo->msgnum, iflags, FALSE);
+               if (ok != IMAP_SUCCESS) return ok;
+       }
+
+       if (flags & MSG_UNREAD)
+               ok = imap_set_message_flags(session, msginfo->msgnum,
+                                           msginfo->msgnum, IMAP_FLAG_SEEN,
+                                           TRUE);
+       return ok;
+}
+
 static gint imap_set_message_flags(IMAPSession *session,
                                   guint32 first_uid,
                                   guint32 last_uid,
@@ -1860,7 +1976,7 @@ static gint imap_status(IMAPSession *session, IMAPFolder *folder,
        ok = imap_cmd_ok(SESSION(session)->sock, argbuf);
        if (ok != IMAP_SUCCESS) THROW(ok);
 
-       str = search_array_contain_str(argbuf, "STATUS");
+       str = search_array_str(argbuf, "STATUS");
        if (!str) THROW(IMAP_ERROR);
 
        str = strchr(str, '(');
@@ -1923,6 +2039,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");
@@ -1942,7 +2073,7 @@ static gint imap_cmd_namespace(SockInfo *sock, gchar **ns_str)
        imap_cmd_gen_send(sock, "NAMESPACE");
        if ((ok = imap_cmd_ok(sock, argbuf)) != IMAP_SUCCESS) THROW(ok);
 
-       str = search_array_contain_str(argbuf, "NAMESPACE");
+       str = search_array_str(argbuf, "NAMESPACE");
        if (!str) THROW(IMAP_ERROR);
 
        *ns_str = g_strdup(str);
@@ -2121,7 +2252,7 @@ static gint imap_cmd_append(SockInfo *sock, const gchar *destfolder,
 {
        gint ok;
        gint size;
-        gchar *destfolder_;
+       gchar *destfolder_;
 
        g_return_val_if_fail(file != NULL, IMAP_ERROR);
 
@@ -2195,6 +2326,7 @@ static gint imap_cmd_expunge(SockInfo *sock)
        return IMAP_SUCCESS;
 }
 
+
 static gint imap_cmd_ok(SockInfo *sock, GPtrArray *argbuf)
 {
        gint ok;
@@ -2301,6 +2433,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;
@@ -2316,6 +2464,24 @@ static gchar *search_array_contain_str(GPtrArray *array, gchar *str)
        return NULL;
 }
 
+static gchar *search_array_str(GPtrArray *array, gchar *str)
+{
+       gint i;
+       gint len;
+
+       len = strlen(str);
+
+       for (i = 0; i < array->len; i++) {
+               gchar *tmp;
+
+               tmp = g_ptr_array_index(array, i);
+               if (!strncmp(tmp, str, len))
+                       return tmp;
+       }
+
+       return NULL;
+}
+
 static void imap_path_separator_subst(gchar *str, gchar separator)
 {
        if (separator && separator != '/')