0.9.3claws10
[claws.git] / src / imap.c
index c79f52dad66568c1cae1bd58a9186a9fad4eb291..fd890dd7736dcf14f78c283f18d9c6a7c1770167 100644 (file)
@@ -49,6 +49,8 @@
 #include "procheader.h"
 #include "prefs_account.h"
 #include "codeconv.h"
+#include "md5.h"
+#include "base64.h"
 #include "utils.h"
 #include "inputdialog.h"
 #include "log.h"
@@ -82,6 +84,8 @@ struct _IMAPSession
        time_t last_access_time;
        gboolean authenticated;
        guint cmd_count;
+       gboolean folder_content_changed;
+       guint exists;
 };
 
 struct _IMAPNameSpace
@@ -99,6 +103,7 @@ struct _IMAPNameSpace
 #define IMAP_ERROR     7
 
 #define IMAPBUFSIZE    8192
+#define IMAPCMDLIMIT   1000
 
 typedef enum
 {
@@ -169,7 +174,6 @@ static gint imap_remove_all_msg(Folder * folder, FolderItem * item);
 static gboolean imap_is_msg_changed(Folder * folder,
                                    FolderItem * item, MsgInfo * msginfo);
 
-static gint imap_scan_folder(Folder * folder, FolderItem * item);
 static void imap_scan_tree(Folder * folder);
 
 static gint imap_create_tree(Folder * folder);
@@ -192,9 +196,14 @@ static void imap_folder_item_destroy       (Folder         *folder,
 
 static IMAPSession *imap_session_get   (Folder         *folder);
 
+static gint imap_auth                  (IMAPSession    *session,
+                                        const gchar    *user,
+                                        const gchar    *pass,
+                                        IMAPAuthType    type);
+
 static gint imap_scan_tree_recursive   (IMAPSession    *session,
                                         FolderItem     *item);
-static GSList *imap_parse_list         (Folder         *folder,
+static GSList *imap_parse_list         (IMAPFolder     *folder,
                                         IMAPSession    *session,
                                         const gchar    *real_path,
                                         gchar          *separator);
@@ -280,9 +289,15 @@ static gint imap_greeting          (IMAPSession    *session);
 static gboolean imap_has_capability    (IMAPSession    *session,
                                         const gchar    *cap);
 void imap_free_capabilities            (IMAPSession    *session);
-static const IMAPSet numberlist_to_imapset(MsgNumberList *list);
+static const IMAPSet numberlist_to_imapset
+                                       (MsgNumberList *list);
 
 /* low-level IMAP4rev1 commands */
+static gint imap_cmd_authenticate
+                               (IMAPSession    *session,
+                                const gchar    *user,
+                                const gchar    *pass,
+                                IMAPAuthType    type);
 static gint imap_cmd_login     (IMAPSession    *sock,
                                 const gchar    *user,
                                 const gchar    *pass);
@@ -382,6 +397,9 @@ static void imap_change_flags                       (Folder         *folder,
                                                 FolderItem     *item,
                                                 MsgInfo        *msginfo,
                                                 MsgPermFlags   newflags);
+static gchar *imap_folder_get_path             (Folder         *folder);
+static gchar *imap_item_get_path               (Folder         *folder,
+                                                FolderItem     *item);
 
 FolderClass imap_class =
 {
@@ -398,6 +416,7 @@ FolderClass imap_class =
        /* FolderItem functions */
        imap_folder_item_new,
        imap_folder_item_destroy,
+       imap_item_get_path,
        imap_create_folder,
        imap_rename_folder,
        imap_remove_folder,
@@ -439,7 +458,7 @@ void imap_folder_destroy(Folder *folder)
 {
        gchar *dir;
 
-       dir = folder_get_path(folder);
+       dir = imap_folder_get_path(folder);
        if (is_dir_exist(dir))
                remove_dir_recursive(dir);
        g_free(dir);
@@ -496,25 +515,24 @@ static void imap_reset_uid_lists(Folder *folder)
        g_node_traverse(folder->node, G_IN_ORDER, G_TRAVERSE_ALL, -1, imap_reset_uid_lists_func, NULL); 
 }
 
+static gint imap_auth(IMAPSession *session, const gchar *user, const gchar *pass,
+                     IMAPAuthType type)
+{
+       if (type == 0 || type == IMAP_AUTH_LOGIN)
+               return imap_cmd_login(session, user, pass);
+       else
+               return imap_cmd_authenticate(session, user, pass, type);
+}
+
 static IMAPSession *imap_session_get(Folder *folder)
 {
        RemoteFolder *rfolder = REMOTE_FOLDER(folder);
        IMAPSession *session = NULL;
-       gushort port;
 
        g_return_val_if_fail(folder != NULL, NULL);
        g_return_val_if_fail(FOLDER_CLASS(folder) == &imap_class, NULL);
        g_return_val_if_fail(folder->account != NULL, NULL);
 
-#if USE_OPENSSL
-       port = folder->account->set_imapport ? folder->account->imapport
-               : folder->account->ssl_imap == SSL_TUNNEL
-               ? IMAPS_PORT : IMAP4_PORT;
-#else
-       port = folder->account->set_imapport ? folder->account->imapport
-               : IMAP4_PORT;
-#endif
-
        /* Make sure we have a session */
        if (rfolder->session != NULL) {
                session = IMAP_SESSION(rfolder->session);
@@ -559,14 +577,14 @@ static IMAPSession *imap_session_get(Folder *folder)
                        /* Check if this is the first try to establish a
                           connection, if yes we don't try to reconnect */
                        if (rfolder->session == NULL) {
-                               log_warning(_("Connecting %s:%d failed"),
-                                           folder->account->recv_server, port);
+                               log_warning(_("Connecting %s failed"),
+                                           folder->account->recv_server);
                                session_destroy(SESSION(session));
                                session = NULL;
                        } else {
-                               log_warning(_("IMAP4 connection to %s:%d has been"
+                               log_warning(_("IMAP4 connection to %s has been"
                                              " disconnected. Reconnecting...\n"),
-                                           folder->account->recv_server, port);
+                                           folder->account->recv_server);
                                session_destroy(SESSION(session));
                                /* Clear folders session to make imap_session_get create
                                   a new session, because of rfolder->session == NULL
@@ -590,7 +608,6 @@ IMAPSession *imap_session_new(const PrefsAccount *account)
        IMAPSession *session;
        SockInfo *imap_sock;
        gushort port;
-       gboolean is_preauth;
 
 #ifdef USE_OPENSSL
        /* FIXME: IMAP over SSL only... */ 
@@ -641,7 +658,7 @@ IMAPSession *imap_session_new(const PrefsAccount *account)
        session->capability = NULL;
 
        session->mbox = NULL;
-       session->authenticated = is_preauth;
+       session->authenticated = FALSE;
        session->cmd_count = 0;
 
        /* Only need to log in if the connection was not PREAUTH */
@@ -666,7 +683,7 @@ IMAPSession *imap_session_new(const PrefsAccount *account)
                }
 
                imap_free_capabilities(session);
-               session->authenticated = is_preauth;
+               session->authenticated = FALSE;
                session->cmd_count = 1;
 
                if (imap_greeting(session) != IMAP_SUCCESS) {
@@ -676,7 +693,7 @@ IMAPSession *imap_session_new(const PrefsAccount *account)
        }
 #endif
        log_message("IMAP connection is %s-authenticated\n",
-                   (is_preauth) ? "pre" : "un");
+                   (session->authenticated) ? "pre" : "un");
 
        return session;
 }
@@ -697,7 +714,7 @@ void imap_session_authenticate(IMAPSession *session, const PrefsAccount *account
                g_free(tmp_pass);
        }
 
-       if (imap_cmd_login(session, account->userid, pass) != IMAP_SUCCESS) {
+       if (imap_auth(session, account->userid, pass, account->imap_auth_type) != IMAP_SUCCESS) {
                imap_cmd_logout(session);
                return;
        }
@@ -966,32 +983,6 @@ gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
        return FALSE;
 }
 
-gint imap_scan_folder(Folder *folder, FolderItem *item)
-{
-       IMAPSession *session;
-       gint messages, recent, unseen;
-       guint32 uid_next, uid_validity;
-       gint ok;
-
-       g_return_val_if_fail(folder != NULL, -1);
-       g_return_val_if_fail(item != NULL, -1);
-
-       session = imap_session_get(folder);
-       if (!session) return -1;
-
-       ok = imap_status(session, IMAP_FOLDER(folder), item->path,
-                        &messages, &recent, &uid_next, &uid_validity, &unseen);
-       if (ok != IMAP_SUCCESS) return -1;
-
-       item->new_msgs = unseen > 0 ? recent : 0;
-       item->unread_msgs = unseen;
-       item->total_msgs = messages;
-       item->last_num = (messages > 0 && uid_next > 0) ? uid_next - 1 : 0;
-       /* item->mtime = uid_validity; */
-
-       return 0;
-}
-
 void imap_scan_tree(Folder *folder)
 {
        FolderItem *item;
@@ -1070,7 +1061,7 @@ static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
                      wildcard_path);
 
        strtailchomp(real_path, separator);
-       item_list = imap_parse_list(folder, session, real_path, NULL);
+       item_list = imap_parse_list(imapfolder, session, real_path, NULL);
        g_free(real_path);
 
        for (cur = item_list; cur != NULL; cur = cur->next) {
@@ -1103,8 +1094,6 @@ static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
                        }
                }
                folder_item_append(item, new_item);
-               if (new_item->no_select == FALSE)
-                       imap_scan_folder(folder, new_item);
                if (new_item->no_sub == FALSE)
                        imap_scan_tree_recursive(session, new_item);
        }
@@ -1112,7 +1101,7 @@ static gint imap_scan_tree_recursive(IMAPSession *session, FolderItem *item)
        return IMAP_SUCCESS;
 }
 
-static GSList *imap_parse_list(Folder *folder, IMAPSession *session,
+static GSList *imap_parse_list(IMAPFolder *folder, IMAPSession *session,
                               const gchar *real_path, gchar *separator)
 {
        gchar buf[IMAPBUFSIZE];
@@ -1179,7 +1168,7 @@ static GSList *imap_parse_list(Folder *folder, IMAPSession *session,
 
                loc_name = imap_modified_utf7_to_locale(name);
                loc_path = imap_modified_utf7_to_locale(buf);
-               new_item = folder_item_new(folder, loc_name, loc_path);
+               new_item = folder_item_new(FOLDER(folder), loc_name, loc_path);
                if (strcasestr(flags, "\\Noinferiors") != NULL)
                        new_item->no_sub = TRUE;
                if (strcmp(buf, "INBOX") != 0 &&
@@ -1265,6 +1254,52 @@ static FolderItem *imap_create_special_folder(Folder *folder,
        return new_item;
 }
 
+static gchar *imap_folder_get_path(Folder *folder)
+{
+       gchar *folder_path;
+
+       g_return_val_if_fail(folder != NULL, NULL);
+        g_return_val_if_fail(folder->account != NULL, NULL);
+
+        folder_path = g_strconcat(get_imap_cache_dir(),
+                                  G_DIR_SEPARATOR_S,
+                                  folder->account->recv_server,
+                                  G_DIR_SEPARATOR_S,
+                                  folder->account->userid,
+                                  NULL);
+
+       return folder_path;
+}
+
+static gchar *imap_item_get_path(Folder *folder, FolderItem *item)
+{
+       gchar *folder_path, *path;
+
+       g_return_val_if_fail(folder != NULL, NULL);
+       g_return_val_if_fail(item != NULL, NULL);
+       folder_path = imap_folder_get_path(folder);
+
+       g_return_val_if_fail(folder_path != NULL, NULL);
+        if (folder_path[0] == G_DIR_SEPARATOR) {
+                if (item->path)
+                        path = g_strconcat(folder_path, G_DIR_SEPARATOR_S,
+                                           item->path, NULL);
+                else
+                        path = g_strdup(folder_path);
+        } else {
+                if (item->path)
+                        path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
+                                           folder_path, G_DIR_SEPARATOR_S,
+                                           item->path, NULL);
+                else
+                        path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
+                                           folder_path, NULL);
+        }
+        g_free(folder_path);
+
+       return path;
+}
+
 FolderItem *imap_create_folder(Folder *folder, FolderItem *parent,
                               const gchar *name)
 {
@@ -1497,64 +1532,69 @@ static GSList *imap_get_uncached_messages(IMAPSession *session,
        GSList *llast = NULL;
        GString *str;
        MsgInfo *msginfo;
+       IMAPSet imapset;
 
        g_return_val_if_fail(session != NULL, NULL);
        g_return_val_if_fail(item != NULL, NULL);
        g_return_val_if_fail(item->folder != NULL, NULL);
        g_return_val_if_fail(FOLDER_CLASS(item->folder) == &imap_class, NULL);
 
-       if (imap_cmd_envelope(session, numberlist_to_imapset(numlist))
-           != IMAP_SUCCESS) {
-               log_warning(_("can't get envelope\n"));
-               return NULL;
-       }
+       imapset = numberlist_to_imapset(numlist);
+       while (imapset != NULL) {
+               if (imap_cmd_envelope(session, imapset)
+                   != IMAP_SUCCESS) {
+                       log_warning(_("can't get envelope\n"));
+                       continue;
+               }
 
-       str = g_string_new(NULL);
+               str = g_string_new(NULL);
 
-       for (;;) {
-               if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
-                       log_warning(_("error occurred while getting envelope.\n"));
-                       g_string_free(str, TRUE);
-                       return newlist;
-               }
-               strretchomp(tmp);
-               if (tmp[0] != '*' || tmp[1] != ' ') {
-                       log_print("IMAP4< %s\n", tmp);
-                       g_free(tmp);
-                       break;
-               }
-               if (strstr(tmp, "FETCH") == NULL) {
+               for (;;) {
+                       if ((tmp = sock_getline(SESSION(session)->sock)) == NULL) {
+                               log_warning(_("error occurred while getting envelope.\n"));
+                               g_string_free(str, TRUE);
+                               break;
+                       }
+                       strretchomp(tmp);
+                       if (tmp[0] != '*' || tmp[1] != ' ') {
+                               log_print("IMAP4< %s\n", tmp);
+                               g_free(tmp);
+                               break;
+                       }
+                       if (strstr(tmp, "FETCH") == NULL) {
+                               log_print("IMAP4< %s\n", tmp);
+                               g_free(tmp);
+                               continue;
+                       }
                        log_print("IMAP4< %s\n", tmp);
+                       g_string_assign(str, tmp);
                        g_free(tmp);
-                       continue;
-               }
-               log_print("IMAP4< %s\n", tmp);
-               g_string_assign(str, tmp);
-               g_free(tmp);
-
-               msginfo = imap_parse_envelope
-                       (SESSION(session)->sock, item, str);
-               if (!msginfo) {
-                       log_warning(_("can't parse envelope: %s\n"), str->str);
-                       continue;
-               }
-               if (item->stype == F_QUEUE) {
-                       MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
-               } else if (item->stype == F_DRAFT) {
-                       MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
-               }
 
-               msginfo->folder = item;
+                       msginfo = imap_parse_envelope
+                               (SESSION(session)->sock, item, str);
+                       if (!msginfo) {
+                               log_warning(_("can't parse envelope: %s\n"), str->str);
+                               continue;
+                       }
+                       if (item->stype == F_QUEUE) {
+                               MSG_SET_TMP_FLAGS(msginfo->flags, MSG_QUEUED);
+                       } else if (item->stype == F_DRAFT) {
+                               MSG_SET_TMP_FLAGS(msginfo->flags, MSG_DRAFT);
+                       }
+
+                       msginfo->folder = item;
 
-               if (!newlist)
-                       llast = newlist = g_slist_append(newlist, msginfo);
-               else {
-                       llast = g_slist_append(llast, msginfo);
-                       llast = llast->next;
+                       if (!newlist)
+                               llast = newlist = g_slist_append(newlist, msginfo);
+                       else {
+                               llast = g_slist_append(llast, msginfo);
+                               llast = llast->next;
+                       }
                }
-       }
 
-       g_string_free(str, TRUE);
+               g_string_free(str, TRUE);
+               imapset = numberlist_to_imapset(NULL);
+       }
 
        return newlist;
 }
@@ -1743,7 +1783,7 @@ static void imap_get_namespace_by_list(IMAPSession *session, IMAPFolder *folder)
                return;
 
        imap_gen_send(session, "LIST \"\" \"\"");
-       item_list = imap_parse_list(NULL, session, "", &separator);
+       item_list = imap_parse_list(folder, session, "", &separator);
        for (cur = item_list; cur != NULL; cur = cur->next)
                folder_item_destroy(FOLDER_ITEM(cur->data));
        g_slist_free(item_list);
@@ -1859,6 +1899,7 @@ static gchar *imap_parse_atom(SockInfo *sock, gchar *src,
 
                cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
                len = atoi(buf);
+               g_return_val_if_fail(len > 0, cur_pos);
 
                g_string_truncate(str, 0);
                cur_pos = str->str;
@@ -1901,6 +1942,7 @@ static gchar *imap_get_header(SockInfo *sock, gchar *cur_pos, gchar **headers,
 
        cur_pos = strchr_cpy(cur_pos + 1, '}', buf, sizeof(buf));
        len = atoi(buf);
+       g_return_val_if_fail(len > 0, cur_pos);
 
        g_string_truncate(str, 0);
        cur_pos = str->str;
@@ -2070,6 +2112,7 @@ static gint imap_set_message_flags(IMAPSession *session,
 {
        GString *buf;
        gint ok;
+       IMAPSet imapset;
 
        buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
 
@@ -2084,8 +2127,12 @@ static gint imap_set_message_flags(IMAPSession *session,
 
        g_string_append_c(buf, ')');
 
-       ok = imap_cmd_store(session, numberlist_to_imapset(numlist),
-                           buf->str);
+       imapset = numberlist_to_imapset(numlist);
+       while (imapset != NULL) {
+               ok = imap_cmd_store(session, imapset,
+                                   buf->str);
+               imapset = numberlist_to_imapset(NULL);
+       }
        g_string_free(buf, TRUE);
 
        return ok;
@@ -2117,8 +2164,10 @@ static gint imap_select(IMAPSession *session, IMAPFolder *folder,
                             exists, recent, unseen, uid_validity);
        if (ok != IMAP_SUCCESS)
                log_warning(_("can't select folder: %s\n"), real_path);
-       else
+       else {
                session->mbox = g_strdup(path);
+               session->folder_content_changed = FALSE;
+       }
        g_free(real_path);
 
        return ok;
@@ -2194,6 +2243,51 @@ catch:
 
 /* low-level IMAP4rev1 commands */
 
+static gint imap_cmd_authenticate(IMAPSession *session, const gchar *user,
+                                 const gchar *pass, IMAPAuthType type)
+{
+       gchar *auth_type;
+       gint ok;
+       gchar *buf;
+       gchar *challenge;
+       gint challenge_len;
+       gchar hexdigest[33];
+       gchar *response;
+       gchar *response64;
+
+       auth_type = "CRAM-MD5";
+
+       imap_gen_send(session, "AUTHENTICATE %s", auth_type);
+       ok = imap_gen_recv(session, &buf);
+       if (ok != IMAP_SUCCESS || buf[0] != '+' || buf[1] != ' ') {
+               g_free(buf);
+               return IMAP_ERROR;
+       }
+
+       challenge = g_malloc(strlen(buf + 2) + 1);
+       challenge_len = base64_decode(challenge, buf + 2, -1);
+       challenge[challenge_len] = '\0';
+       log_print("IMAP< [Decoded: %s]\n", challenge);
+       g_free(buf);
+
+       md5_hex_hmac(hexdigest, challenge, challenge_len, pass, strlen(pass));
+       g_free(challenge);
+
+       response = g_strdup_printf("%s %s", user, hexdigest);
+       log_print("IMAP> [Encoded: %s]\n", response);
+       response64 = g_malloc((strlen(response) + 3) * 2 + 1);
+       base64_encode(response64, response, strlen(response));
+       g_free(response);
+
+       log_print("IMAP> %s\n", response64);
+       sock_puts(SESSION(session)->sock, response64);
+       ok = imap_cmd_ok(session, NULL);
+       if (ok != IMAP_SUCCESS)
+               log_warning(_("IMAP4 authentication failed.\n"));
+
+       return ok;
+}
+
 static gint imap_cmd_login(IMAPSession *session,
                           const gchar *user, const gchar *pass)
 {
@@ -2267,7 +2361,7 @@ void imap_free_capabilities(IMAPSession *session)
 static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
 {
        static GString *imapset = NULL;
-       MsgNumberList *numlist, *elem;
+       static MsgNumberList *numlist, *elem;
        guint first, last, next;
 
        if (imapset == NULL)
@@ -2275,13 +2369,19 @@ static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
        else
                g_string_truncate(imapset, 0);
 
-       numlist = g_slist_copy(list);
-       numlist = g_slist_sort(numlist, g_int_compare);
+       if (list != NULL) {
+               g_slist_free(numlist);
+               numlist = g_slist_copy(list);
+               numlist = g_slist_sort(numlist, g_int_compare);
+       } else if (numlist == NULL) {
+               return NULL;
+       }
 
        first = GPOINTER_TO_INT(numlist->data);
        last = first;
        for(elem = g_slist_next(numlist); elem != NULL; elem = g_slist_next(elem)) {
                next = GPOINTER_TO_INT(elem->data);
+
                if(next != (last + 1)) {
                        if (imapset->len > 0)
                                g_string_append(imapset, ",");
@@ -2290,18 +2390,34 @@ static const IMAPSet numberlist_to_imapset(MsgNumberList *list)
                        else
                                g_string_sprintfa(imapset, "%d:%d", first, last);
 
+                       if (imapset->len > IMAPCMDLIMIT) {
+                               last = 0;
+                               break;
+                       }
+
                        first = next;
                }
                last = next;
        }
-       if (imapset->len > 0)
-               g_string_append(imapset, ",");
-       if (first == last)
-               g_string_sprintfa(imapset, "%d", first);
-       else
-               g_string_sprintfa(imapset, "%d:%d", first, last);
+       if (last != 0) {
+               if (imapset->len > 0)
+                       g_string_append(imapset, ",");
+               if (first == last)
+                       g_string_sprintfa(imapset, "%d", first);
+               else
+                       g_string_sprintfa(imapset, "%d:%d", first, last);
 
-       g_slist_free(numlist);
+               g_slist_free(numlist);
+               numlist = NULL;
+       } else {
+               MsgNumberList *remaining;
+
+               remaining = elem->next;
+               remaining = g_slist_prepend(remaining, elem->data);
+               elem->next = NULL;
+               g_slist_free(numlist);
+               numlist = remaining;
+       }
 
        return imapset->str;
 }
@@ -2535,21 +2651,26 @@ static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filen
                }
                if (strstr(buf, "FETCH") != NULL)
                        break;
+               g_free(buf);
        }
-       if (ok != IMAP_SUCCESS)
+       if (ok != IMAP_SUCCESS) {
+               g_free(buf);
                return ok;
+       }
 
        cur_pos = strchr(buf, '{');
        if (cur_pos == NULL) {
                g_free(buf);
                return IMAP_ERROR;
        }
+
        cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
        if (cur_pos == NULL) {
                g_free(buf);
                return IMAP_ERROR;
        }
        size_num = atol(size_str);
+       g_return_val_if_fail(size_num > 0, IMAP_ERROR);
 
        if (*cur_pos != '\0') {
                g_free(buf);
@@ -2750,32 +2871,46 @@ static gint imap_cmd_expunge(IMAPSession *session)
 
 static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
 {
-       gint ok;
+       gint ok = IMAP_SUCCESS;
        gchar *buf;
        gint cmd_num;
-       gchar cmd_status[IMAPBUFSIZE];
+       gchar *data;
 
        while ((ok = imap_gen_recv(session, &buf))
               == IMAP_SUCCESS) {
+               // make sure data is long enough for any substring of buf
+               data = alloca(strlen(buf) + 1);
+
+               // untagged line read
                if (buf[0] == '*' && buf[1] == ' ') {
+                       gint num;
                        if (argbuf)
                                g_ptr_array_add(argbuf, g_strdup(buf + 2));
-                       continue;
-               }
 
-               if (sscanf(buf, "%d %s", &cmd_num, cmd_status) < 2) {
-                       g_free(buf);
-                       return IMAP_ERROR;
-               } else if (cmd_num == session->cmd_count &&
-                        !strcmp(cmd_status, "OK")) {
+                       if (sscanf(buf + 2, "%d %s", &num, data) >= 2) {
+                               if (!strcmp(data, "EXISTS")) {
+                                       session->exists = num;
+                                       session->folder_content_changed = TRUE;
+                               }
+
+                               if(!strcmp(data, "EXPUNGE")) {
+                                       session->exists--;
+                                       session->folder_content_changed = TRUE;
+                               }
+                       }
+               // tagged line with correct tag and OK response found
+               } else if ((sscanf(buf, "%d %s", &cmd_num, data) >= 2) &&
+                          (cmd_num == session->cmd_count) &&
+                          !strcmp(data, "OK")) {
                        if (argbuf)
                                g_ptr_array_add(argbuf, g_strdup(buf));
-                       g_free(buf);
-                       return IMAP_SUCCESS;
+                       break;
+               // everything else
                } else {
-                       g_free(buf);
-                       return IMAP_ERROR;
+                       ok = IMAP_ERROR;
+                       break;
                }
+               g_free(buf);
        }
        g_free(buf);
 
@@ -2784,18 +2919,18 @@ static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
 
 static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
 {
-       gchar buf[IMAPBUFSIZE];
-       gchar tmp[IMAPBUFSIZE];
+       gchar *buf;
+       gchar *tmp;
        gchar *p;
        va_list args;
 
        va_start(args, format);
-       g_vsnprintf(tmp, sizeof(tmp), format, args);
+       tmp = g_strdup_vprintf(format, args);
        va_end(args);
 
        session->cmd_count++;
 
-       g_snprintf(buf, sizeof(buf), "%d %s\r\n", session->cmd_count, tmp);
+       buf = g_strdup_printf("%d %s\r\n", session->cmd_count, tmp);
        if (!strncasecmp(tmp, "LOGIN ", 6) && (p = strchr(tmp + 6, ' '))) {
                *p = '\0';
                log_print("IMAP4> %d %s ********\n", session->cmd_count, tmp);
@@ -2803,6 +2938,8 @@ static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
                log_print("IMAP4> %d %s\n", session->cmd_count, tmp);
 
        sock_write_all(SESSION(session)->sock, buf, strlen(buf));
+       g_free(tmp);
+       g_free(buf);
 }
 
 static gint imap_gen_recv(IMAPSession *session, gchar **buf)
@@ -2912,7 +3049,22 @@ static void imap_path_separator_subst(gchar *str, gchar separator)
 static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
 {
 #if !HAVE_ICONV
-       return g_strdup(mutf7_str);
+       const gchar *from_p;
+       gchar *to, *to_p;
+
+       to = g_malloc(strlen(mutf7_str) + 1);
+       to_p = to;
+
+       for (from_p = mutf7_str; *from_p != '\0'; from_p++) {
+               if (*from_p == '&' && *(from_p + 1) == '-') {
+                       *to_p++ = '&';
+                       from_p++;
+               } else
+                       *to_p++ = *from_p;
+       }
+       *to_p = '\0';
+
+       return to;
 #else
        static iconv_t cd = (iconv_t)-1;
        static gboolean iconv_ok = TRUE;
@@ -2986,7 +3138,22 @@ static gchar *imap_modified_utf7_to_locale(const gchar *mutf7_str)
 static gchar *imap_locale_to_modified_utf7(const gchar *from)
 {
 #if !HAVE_ICONV
-       return g_strdup(from);
+       const gchar *from_p;
+       gchar *to, *to_p;
+
+       to = g_malloc(strlen(from) * 2 + 1);
+       to_p = to;
+
+       for (from_p = from; *from_p != '\0'; from_p++) {
+               if (*from_p == '&') {
+                       *to_p++ = '&';
+                       *to_p++ = '-';
+               } else
+                       *to_p++ = *from_p;
+       }
+       *to_p = '\0';
+
+       return to;
 #else
        static iconv_t cd = (iconv_t)-1;
        static gboolean iconv_ok = TRUE;
@@ -3017,22 +3184,37 @@ static gchar *imap_locale_to_modified_utf7(const gchar *from)
 #define IS_PRINT(ch) (isprint(ch) && isascii(ch))
 
        while (from_len > 0) {
-               if (IS_PRINT(*from_tmp)) {
+               if (*from_tmp == '+') {
+                       *norm_utf7_p++ = '+';
+                       *norm_utf7_p++ = '-';
+                       norm_utf7_len -= 2;
+                       from_tmp++;
+                       from_len--;
+               } else if (IS_PRINT(*from_tmp)) {
                        /* printable ascii char */
                        *norm_utf7_p = *from_tmp;
                        norm_utf7_p++;
+                       norm_utf7_len--;
                        from_tmp++;
                        from_len--;
                } else {
-                       size_t mblen;
+                       size_t mb_len = 0, conv_len = 0;
 
                        /* unprintable char: convert to UTF-7 */
-                       for (mblen = 0;
-                            !IS_PRINT(from_tmp[mblen]) && mblen < from_len;
-                            mblen++)
-                               ;
-                       from_len -= mblen;
-                       if (iconv(cd, (ICONV_CONST gchar **)&from_tmp, &mblen,
+                       p = from_tmp;
+                       while (!IS_PRINT(*p) && conv_len < from_len) {
+                               mb_len = mblen(p, MB_LEN_MAX);
+                               if (mb_len <= 0) {
+                                       g_warning("wrong multibyte sequence\n");
+                                       return g_strdup(from);
+                               }
+                               conv_len += mb_len;
+                               p += mb_len;
+                       }
+
+                       from_len -= conv_len;
+                       if (iconv(cd, (ICONV_CONST gchar **)&from_tmp,
+                                 &conv_len,
                                  &norm_utf7_p, &norm_utf7_len) == -1) {
                                g_warning("iconv cannot convert %s to UTF-7\n",
                                          conv_get_current_charset_str());
@@ -3051,17 +3233,23 @@ static gchar *imap_locale_to_modified_utf7(const gchar *from)
        for (p = norm_utf7; p < norm_utf7_p; p++) {
                /* replace: '&' -> "&-",
                            '+' -> '&',
-                           escaped '/' -> ',' */
+                           "+-" -> '+',
+                           BASE64 '/' -> ',' */
                if (!in_escape && *p == '&') {
                        g_string_append(to_str, "&-");
                } else if (!in_escape && *p == '+') {
-                       g_string_append_c(to_str, '&');
-                       in_escape = TRUE;
+                       if (*(p + 1) == '-') {
+                               g_string_append_c(to_str, '+');
+                               p++;
+                       } else {
+                               g_string_append_c(to_str, '&');
+                               in_escape = TRUE;
+                       }
                } else if (in_escape && *p == '/') {
                        g_string_append_c(to_str, ',');
                } else if (in_escape && *p == '-') {
-                       in_escape = FALSE;
                        g_string_append_c(to_str, '-');
+                       in_escape = FALSE;
                } else {
                        g_string_append_c(to_str, *p);
                }
@@ -3190,6 +3378,7 @@ gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
        gint ok, nummsgs = 0, exists, recent, unseen, uid_val, uid_next;
        GSList *uidlist;
        gchar *dir;
+       gboolean selected_folder;
 
        g_return_val_if_fail(folder != NULL, -1);
        g_return_val_if_fail(item != NULL, -1);
@@ -3200,16 +3389,26 @@ gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
        session = imap_session_get(folder);
        g_return_val_if_fail(session != NULL, -1);
 
-       ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
-                        &exists, &recent, &uid_next, &uid_val, &unseen);
-       if (ok != IMAP_SUCCESS)
-               return -1;
+       selected_folder = (session->mbox != NULL) &&
+                         (!strcmp(session->mbox, item->item.path));
+       if (selected_folder) {
+               ok = imap_cmd_noop(session);
+               if (ok != IMAP_SUCCESS)
+                       return -1;
+               exists = session->exists;
+       } else {
+               ok = imap_status(session, IMAP_FOLDER(folder), item->item.path,
+                                &exists, &recent, &uid_next, &uid_val, &unseen);
+               if (ok != IMAP_SUCCESS)
+                       return -1;
+       }
 
        /* If old uid_next matches new uid_next we can be sure no message
           was added to the folder */
-       if (uid_next == item->uid_next) {
+       if (( selected_folder && !session->folder_content_changed) ||
+           (!selected_folder && uid_next == item->uid_next)) {
                nummsgs = g_slist_length(item->uid_list);
-               
+
                /* If number of messages is still the same we
                    know our caches message numbers are still valid,
                    otherwise if the number of messages has decrease
@@ -3225,7 +3424,8 @@ gint imap_get_num_list(Folder *folder, FolderItem *_item, GSList **msgnum_list)
                        item->uid_list = NULL;
                }
        }
-       item->uid_next = uid_next;
+       if (!selected_folder)
+               item->uid_next = uid_next;
 
        if (exists == 0) {
                *msgnum_list = NULL;
@@ -3391,7 +3591,7 @@ gboolean imap_check_msgnum_validity(Folder *folder, FolderItem *_item)
        if(item->item.mtime == uid_validity)
                return TRUE;
 
-       debug_print("Freeing imap uid cache");
+       debug_print("Freeing imap uid cache\n");
        item->lastuid = 0;
        g_slist_free(item->uid_list);
        item->uid_list = NULL;