0.9.3claws64
[claws.git] / src / imap.c
index 960c24e8da350641a1ff9cf0b58dafac88fc072d..0df7e4318a27bb1af2cb067225e8c3ea2a94c685 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"
@@ -101,6 +103,7 @@ struct _IMAPNameSpace
 #define IMAP_ERROR     7
 
 #define IMAPBUFSIZE    8192
+#define IMAPCMDLIMIT   1000
 
 typedef enum
 {
@@ -160,7 +163,10 @@ static void imap_session_destroy(Session * session);
 static gchar *imap_fetch_msg(Folder * folder, FolderItem * item, gint uid);
 static gint imap_add_msg(Folder * folder,
                         FolderItem * dest,
-                        const gchar * file, gboolean remove_source);
+                        const gchar * file, MsgFlags * flags);
+static gint imap_add_msgs(Folder * folder, FolderItem * dest,
+                         GSList * file_list,
+                         GRelation *relation);
 
 static gint imap_copy_msg(Folder * folder,
                          FolderItem * dest, MsgInfo * msginfo);
@@ -171,6 +177,8 @@ 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_close(Folder * folder, FolderItem * item);
+
 static void imap_scan_tree(Folder * folder);
 
 static gint imap_create_tree(Folder * folder);
@@ -193,9 +201,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);
@@ -237,9 +250,10 @@ static SockInfo *imap_init_sock(SockInfo *sock, SSLType    ssl_type);
 static SockInfo *imap_init_sock(SockInfo *sock);
 #endif
 
+static gchar *imap_get_flag_str                (IMAPFlags       flags);
 static gint imap_set_message_flags     (IMAPSession    *session,
                                         MsgNumberList  *numlist,
-                                        IMAPFlags       flag,
+                                        IMAPFlags       flags,
                                         gboolean        is_set);
 static gint imap_select                        (IMAPSession    *session,
                                         IMAPFolder     *folder,
@@ -281,9 +295,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);
@@ -330,6 +350,7 @@ static gint imap_cmd_fetch  (IMAPSession    *sock,
 static gint imap_cmd_append    (IMAPSession    *session,
                                 const gchar    *destfolder,
                                 const gchar    *file,
+                                IMAPFlags       flags,
                                 gint32         *newuid);
 static gint imap_cmd_copy      (IMAPSession    *session,
                                 gint32         msgnum,
@@ -339,13 +360,14 @@ static gint imap_cmd_store        (IMAPSession    *sock,
                                 IMAPSet         set,
                                 gchar          *sub_cmd);
 static gint imap_cmd_expunge   (IMAPSession    *sock);
+static gint imap_cmd_close     (IMAPSession    *session);
 
 static gint imap_cmd_ok                (IMAPSession    *session,
                                 GPtrArray      *argbuf);
 static void imap_gen_send      (IMAPSession    *sock,
                                 const gchar    *format, ...);
 static gint imap_gen_recv      (IMAPSession    *sock,
-                                gchar         **buf);
+                                gchar         **ret);
 
 /* misc utility functions */
 static gchar *strchr_cpy                       (const gchar    *src,
@@ -383,6 +405,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 =
 {
@@ -399,9 +424,11 @@ 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,
+       imap_close,
        imap_get_num_list,
        NULL,
        NULL,
@@ -413,7 +440,9 @@ FolderClass imap_class =
        imap_get_msginfos,
        imap_fetch_msg,
        imap_add_msg,
+       imap_add_msgs,
        imap_copy_msg,
+       NULL,
        imap_remove_msg,
        imap_remove_all_msg,
        imap_is_msg_changed,
@@ -440,7 +469,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);
@@ -497,25 +526,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);
@@ -560,14 +588,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
@@ -591,7 +619,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... */ 
@@ -642,7 +669,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 */
@@ -667,7 +694,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) {
@@ -677,7 +704,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;
 }
@@ -698,7 +725,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;
        }
@@ -761,35 +788,79 @@ gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
        return filename;
 }
 
-gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file,
-                 gboolean remove_source)
+gint imap_add_msg(Folder *folder, FolderItem *dest, const gchar *file, MsgFlags *flags)
+{
+       gint ret;
+       GSList file_list;
+       MsgFileInfo fileinfo;
+
+       g_return_val_if_fail(file != NULL, -1);
+
+       fileinfo.file = (gchar *)file;
+       fileinfo.flags = flags;
+       file_list.data = &fileinfo;
+       file_list.next = NULL;
+
+       ret = imap_add_msgs(folder, dest, &file_list, NULL);
+       return ret;
+}
+
+gint imap_add_msgs(Folder *folder, FolderItem *dest, GSList *file_list,
+                  GRelation *relation)
 {
        gchar *destdir;
        IMAPSession *session;
-       gint ok, newuid;
+       guint32 last_uid = 0;
+       GSList *cur;
+       MsgFileInfo *fileinfo;
+       gint ok, newnum;
 
        g_return_val_if_fail(folder != NULL, -1);
        g_return_val_if_fail(dest != NULL, -1);
-       g_return_val_if_fail(file != NULL, -1);
+       g_return_val_if_fail(file_list != NULL, -1);
 
        session = imap_session_get(folder);
        if (!session) return -1;
 
        destdir = imap_get_real_path(IMAP_FOLDER(folder), dest->path);
-       ok = imap_cmd_append(session, destdir, file, &newuid);
-       g_free(destdir);
 
-       if (ok != IMAP_SUCCESS) {
-               g_warning("can't append message %s\n", file);
-               return -1;
-       }
+       for (cur = file_list; cur != NULL; cur = cur->next) {
+               IMAPFlags iflags = 0;
+
+               fileinfo = (MsgFileInfo *)cur->data;
+
+               if (fileinfo->flags) {
+                       if (MSG_IS_MARKED(*fileinfo->flags))
+                               iflags |= IMAP_FLAG_FLAGGED;
+                       if (MSG_IS_REPLIED(*fileinfo->flags))
+                               iflags |= IMAP_FLAG_ANSWERED;
+                       if (!MSG_IS_UNREAD(*fileinfo->flags))
+                               iflags |= IMAP_FLAG_SEEN;
+               }
+
+               if (dest->stype == F_OUTBOX ||
+                   dest->stype == F_QUEUE  ||
+                   dest->stype == F_DRAFT  ||
+                   dest->stype == F_TRASH)
+                       iflags |= IMAP_FLAG_SEEN;
 
-       if (remove_source) {
-               if (unlink(file) < 0)
-                       FILE_OP_ERROR(file, "unlink");
+               ok = imap_cmd_append(session, destdir, fileinfo->file, iflags, &newnum);
+
+               if (ok != IMAP_SUCCESS) {
+                       g_warning("can't append message %s\n", fileinfo->file);
+                       g_free(destdir);
+                       return -1;
+               }
+
+               if (relation != NULL)
+                       g_relation_insert(relation, fileinfo, GINT_TO_POINTER(newnum));
+               if (newnum > last_uid)
+                       last_uid = newnum;
        }
 
-       return newuid;
+       g_free(destdir);
+
+       return last_uid;
 }
 
 static gint imap_do_copy(Folder *folder, FolderItem *dest, MsgInfo *msginfo,
@@ -867,7 +938,7 @@ gint imap_copy_msg(Folder *folder, FolderItem *dest, MsgInfo *msginfo)
        srcfile = procmsg_get_message_file(msginfo);
        if (!srcfile) return -1;
 
-       ret = imap_add_msg(folder, dest, srcfile, FALSE);
+       ret = imap_add_msg(folder, dest, srcfile, NULL);
 
        g_free(srcfile);
 
@@ -967,6 +1038,30 @@ gboolean imap_is_msg_changed(Folder *folder, FolderItem *item, MsgInfo *msginfo)
        return FALSE;
 }
 
+gint imap_close(Folder *folder, FolderItem *item)
+{
+       gint ok;
+       IMAPSession *session;
+
+       g_return_val_if_fail(folder != NULL, -1);
+
+       session = imap_session_get(folder);
+       if (!session) return -1;
+
+       if (session->mbox) {
+               ok = imap_cmd_close(session);
+               if (ok != IMAP_SUCCESS)
+                       log_warning(_("can't close folder\n"));
+
+               g_free(session->mbox);
+               session->mbox = NULL;
+
+               return ok;
+       }
+
+       return 0;
+}
+
 void imap_scan_tree(Folder *folder)
 {
        FolderItem *item;
@@ -1045,7 +1140,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) {
@@ -1085,7 +1180,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];
@@ -1152,7 +1247,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 &&
@@ -1238,6 +1333,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)
 {
@@ -1470,64 +1611,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;
 }
@@ -1716,7 +1862,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);
@@ -2038,30 +2184,49 @@ static MsgInfo *imap_parse_envelope(SockInfo *sock, FolderItem *item,
        return msginfo;
 }
 
+static gchar *imap_get_flag_str(IMAPFlags flags)
+{
+       GString *str;
+       gchar *ret;
+
+       str = g_string_new(NULL);
+
+       if (IMAP_IS_SEEN(flags))        g_string_append(str, "\\Seen ");
+       if (IMAP_IS_ANSWERED(flags))    g_string_append(str, "\\Answered ");
+       if (IMAP_IS_FLAGGED(flags))     g_string_append(str, "\\Flagged ");
+       if (IMAP_IS_DELETED(flags))     g_string_append(str, "\\Deleted ");
+       if (IMAP_IS_DRAFT(flags))       g_string_append(str, "\\Draft");
+
+       if (str->len > 0 && str->str[str->len - 1] == ' ')
+               g_string_truncate(str, str->len - 1);
+
+       ret = str->str;
+       g_string_free(str, FALSE);
+
+       return ret;
+}
+
 static gint imap_set_message_flags(IMAPSession *session,
                                   MsgNumberList *numlist,
                                   IMAPFlags flags,
                                   gboolean is_set)
 {
-       GString *buf;
+       gchar *cmd;
+       gchar *flag_str;
        gint ok;
+       IMAPSet imapset;
 
-       buf = g_string_new(is_set ? "+FLAGS (" : "-FLAGS (");
-
-       if (IMAP_IS_SEEN(flags))        g_string_append(buf, "\\Seen ");
-       if (IMAP_IS_ANSWERED(flags))    g_string_append(buf, "\\Answered ");
-       if (IMAP_IS_FLAGGED(flags))     g_string_append(buf, "\\Flagged ");
-       if (IMAP_IS_DELETED(flags))     g_string_append(buf, "\\Deleted ");
-       if (IMAP_IS_DRAFT(flags))       g_string_append(buf, "\\Draft");
+       flag_str = imap_get_flag_str(flags);
+       cmd = g_strconcat(is_set ? "+FLAGS.SILENT (" : "-FLAGS.SILENT (",
+                         flag_str, ")", NULL);
+       g_free(flag_str);
 
-       if (buf->str[buf->len - 1] == ' ')
-               g_string_truncate(buf, buf->len - 1);
-
-       g_string_append_c(buf, ')');
-
-       ok = imap_cmd_store(session, numberlist_to_imapset(numlist),
-                           buf->str);
-       g_string_free(buf, TRUE);
+       imapset = numberlist_to_imapset(numlist);
+       while (imapset != NULL) {
+               ok = imap_cmd_store(session, imapset, cmd);
+               imapset = numberlist_to_imapset(NULL);
+       }
+       g_free(cmd);
 
        return ok;
 }
@@ -2171,6 +2336,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 = NULL;
+       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';
+       g_free(buf);
+       log_print("IMAP< [Decoded: %s]\n", challenge);
+
+       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)
 {
@@ -2244,7 +2454,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)
@@ -2252,13 +2462,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, ",");
@@ -2267,18 +2483,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;
 }
@@ -2495,7 +2727,7 @@ static gint imap_cmd_search(IMAPSession *session, const gchar *criteria, GSList
 static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filename)
 {
        gint ok;
-       gchar *buf;
+       gchar *buf = NULL;
        gchar *cur_pos;
        gchar size_str[32];
        glong size_num;
@@ -2504,40 +2736,40 @@ static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filen
 
        imap_gen_send(session, "UID FETCH %d BODY.PEEK[]", uid);
 
-       while ((ok = imap_gen_recv(session, &buf))
-              == IMAP_SUCCESS) {
+       while ((ok = imap_gen_recv(session, &buf)) == IMAP_SUCCESS) {
                if (buf[0] != '*' || buf[1] != ' ') {
                        g_free(buf);
                        return IMAP_ERROR;
                }
-               if (strstr(buf, "FETCH") != NULL)
-                       break;
+               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;
+#define RETURN_ERROR_IF_FAIL(cond)     \
+       if (!(cond)) {                  \
+               g_free(buf);            \
+               return IMAP_ERROR;      \
        }
+
+       cur_pos = strchr(buf, '{');
+       RETURN_ERROR_IF_FAIL(cur_pos != NULL);
        cur_pos = strchr_cpy(cur_pos + 1, '}', size_str, sizeof(size_str));
-       if (cur_pos == NULL) {
-               g_free(buf);
-               return IMAP_ERROR;
-       }
+       RETURN_ERROR_IF_FAIL(cur_pos != NULL);
        size_num = atol(size_str);
-       g_return_val_if_fail(size_num > 0, IMAP_ERROR);
+       RETURN_ERROR_IF_FAIL(size_num > 0);
 
-       if (*cur_pos != '\0') {
-               g_free(buf);
-               return IMAP_ERROR;
-       }
+       RETURN_ERROR_IF_FAIL(*cur_pos == '\0');
 
-       if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0) {
-               g_free(buf);
+#undef RETURN_ERROR_IF_FAIL
+
+       g_free(buf);
+
+       if (recv_bytes_write_to_file(SESSION(session)->sock, size_num, filename) != 0)
                return IMAP_ERROR;
-       }
 
        if (imap_gen_recv(session, &buf) != IMAP_SUCCESS) {
                g_free(buf);
@@ -2548,20 +2780,22 @@ static gint imap_cmd_fetch(IMAPSession *session, guint32 uid, const gchar *filen
                g_free(buf);
                return IMAP_ERROR;
        }
-
        g_free(buf);
+
        ok = imap_cmd_ok(session, NULL);
 
        return ok;
 }
 
 static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
-                           const gchar *file, gint32 *new_uid)
+                           const gchar *file, IMAPFlags flags, gint32 *new_uid)
 {
        gint ok;
        gint size, newuid;
        gchar *destfolder_;
-       gchar buf[BUFFSIZE], *imapbuf;
+       gchar *flag_str;
+       gchar *ret = NULL;
+       gchar buf[BUFFSIZE];
        FILE *fp;
        GPtrArray *reply;
        gchar *okmsginfo;
@@ -2574,16 +2808,19 @@ static gint imap_cmd_append(IMAPSession *session, const gchar *destfolder,
                return -1;
        }
        QUOTE_IF_REQUIRED(destfolder_, destfolder);
-       imap_gen_send(session, "APPEND %s (\\Seen) {%d}", destfolder_, size);
+       flag_str = imap_get_flag_str(flags);
+       imap_gen_send(session, "APPEND %s (%s) {%d}", 
+                     destfolder_, flag_str, size);
+       g_free(flag_str);
 
-       ok = imap_gen_recv(session, &imapbuf);
-       if (ok != IMAP_SUCCESS || imapbuf[0] != '+' || imapbuf[1] != ' ') {
+       ok = imap_gen_recv(session, &ret);
+       if (ok != IMAP_SUCCESS || ret[0] != '+' || ret[1] != ' ') {
                log_warning(_("can't append %s to %s\n"), file, destfolder_);
-               g_free(imapbuf);
+               g_free(ret);
                fclose(fp);
                return IMAP_ERROR;
        }
-       g_free(imapbuf);
+       g_free(ret);
 
        log_print("IMAP4> %s\n", _("(sending file...)"));
 
@@ -2726,9 +2963,20 @@ static gint imap_cmd_expunge(IMAPSession *session)
        return IMAP_SUCCESS;
 }
 
-static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
+static gint imap_cmd_close(IMAPSession *session)
 {
        gint ok;
+
+       imap_gen_send(session, "CLOSE");
+       if ((ok = imap_cmd_ok(session, NULL)) != IMAP_SUCCESS)
+               log_warning(_("error while imap command: CLOSE\n"));
+
+       return ok;
+}
+
+static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
+{
+       gint ok = IMAP_SUCCESS;
        gchar *buf;
        gint cmd_num;
        gchar *data;
@@ -2738,40 +2986,36 @@ static gint imap_cmd_ok(IMAPSession *session, GPtrArray *argbuf)
                // 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));
 
-                       if (sscanf(buf + 2, "%d %s", &num, data) < 2)
-                               continue;
-
-                       if (!strcmp(data, "EXISTS")) {
-                               session->exists = num;
-                               session->folder_content_changed = TRUE;
-                       }
+                       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;
+                               if(!strcmp(data, "EXPUNGE")) {
+                                       session->exists--;
+                                       session->folder_content_changed = TRUE;
+                               }
                        }
-
-                       continue;
-               }
-
-               if (sscanf(buf, "%d %s", &cmd_num, data) < 2) {
-                       g_free(buf);
-                       return IMAP_ERROR;
-               } else if (cmd_num == session->cmd_count &&
-                        !strcmp(data, "OK")) {
+               // 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);
 
@@ -2803,14 +3047,14 @@ static void imap_gen_send(IMAPSession *session, const gchar *format, ...)
        g_free(buf);
 }
 
-static gint imap_gen_recv(IMAPSession *session, gchar **buf)
+static gint imap_gen_recv(IMAPSession *session, gchar **ret)
 {
-       if ((*buf = sock_getline(SESSION(session)->sock)) == NULL)
+       if ((*ret = sock_getline(SESSION(session)->sock)) == NULL)
                return IMAP_SOCKET;
 
-       strretchomp(*buf);
+       strretchomp(*ret);
 
-       log_print("IMAP4< %s\n", *buf);
+       log_print("IMAP4< %s\n", *ret);
 
        return IMAP_SUCCESS;
 }