SMTP over SSL (untested, feedback requested)
[claws.git] / src / imap.c
index 416df1ee1937a5d7c58063f3d887e5ca0bf50ece..bb0494fa36ed8fcd87761979e30f37352d8aa623 100644 (file)
 #include "codeconv.h"
 #include "utils.h"
 #include "inputdialog.h"
+#include "ssl.h"
 
 #define IMAP4_PORT     143
+#if USE_SSL
+#define IMAPS_PORT     993
+#endif
 
 static GList *session_list = NULL;
 
@@ -57,6 +61,7 @@ static void imap_scan_tree_recursive  (IMAPSession    *session,
                                         IMAPNameSpace  *namespace);
 static GSList *imap_parse_list         (IMAPSession    *session,
                                         const gchar    *path);
+static gint imap_create_trash          (Folder         *folder);
 
 static gint imap_do_copy               (Folder         *folder,
                                         FolderItem     *dest,
@@ -77,9 +82,16 @@ static GSList *imap_delete_cached_messages   (GSList         *mlist,
                                                 guint32         last_uid);
 static void imap_delete_all_cached_messages    (FolderItem     *item);
 
+#if !USE_SSL
 static SockInfo *imap_open             (const gchar    *server,
                                         gushort         port,
                                         gchar          *buf);
+#else
+static SockInfo *imap_open             (const gchar    *server,
+                                        gushort         port,
+                                        gchar          *buf,
+                                        gboolean        use_ssl);
+#endif
 
 static gint imap_set_message_flags     (IMAPSession    *session,
                                         guint32         first_uid,
@@ -96,12 +108,13 @@ static gint imap_select                    (IMAPSession    *session,
 static gint imap_get_uid               (IMAPSession    *session,
                                         gint            msgnum,
                                         guint32        *uid);
-#if 0
-static gint imap_status_uidnext                (IMAPSession    *session,
+static gint imap_status                        (IMAPSession    *session,
                                         IMAPFolder     *folder,
                                         const gchar    *path,
-                                        guint32        *uid_next);
-#endif
+                                        gint           *messages,
+                                        gint           *recent,
+                                        gint           *unseen,
+                                        guint32        *uid_validity);
 
 static void imap_parse_namespace               (IMAPSession    *session,
                                                 IMAPFolder     *folder);
@@ -141,18 +154,25 @@ static gint imap_cmd_list (SockInfo       *sock,
                                 const gchar    *ref,
                                 const gchar    *mailbox,
                                 GPtrArray      *argbuf);
+static gint imap_cmd_do_select (SockInfo       *sock,
+                                const gchar    *folder,
+                                gboolean        examine,
+                                gint           *exists,
+                                gint           *recent,
+                                gint           *unseen,
+                                guint32        *uid_validity);
 static gint imap_cmd_select    (SockInfo       *sock,
                                 const gchar    *folder,
                                 gint           *exists,
                                 gint           *recent,
                                 gint           *unseen,
                                 guint32        *uid_validity);
-#if 0
-static gint imap_cmd_status    (SockInfo       *sock,
+static gint imap_cmd_examine   (SockInfo       *sock,
                                 const gchar    *folder,
-                                const gchar    *status,
-                                gint           *value);
-#endif
+                                gint           *exists,
+                                gint           *recent,
+                                gint           *unseen,
+                                guint32        *uid_validity);
 static gint imap_cmd_create    (SockInfo       *sock,
                                 const gchar    *folder);
 static gint imap_cmd_delete    (SockInfo       *sock,
@@ -200,17 +220,32 @@ static void imap_path_separator_subst             (gchar          *str,
 static IMAPSession *imap_session_get(Folder *folder)
 {
        RemoteFolder *rfolder = REMOTE_FOLDER(folder);
+       gushort port;
 
        g_return_val_if_fail(folder != NULL, NULL);
        g_return_val_if_fail(folder->type == F_IMAP, NULL);
        g_return_val_if_fail(folder->account != NULL, NULL);
 
+#if !USE_SSL
+       port = folder->account->set_imapport ? folder->account->imapport
+               : IMAP4_PORT;
+#else
+       port = folder->account->set_imapport ? folder->account->imapport
+               : (folder->account->imap_ssl ? IMAPS_PORT : IMAP4_PORT);
+#endif
+
        if (!rfolder->session) {
                rfolder->session =
-                       imap_session_new(folder->account->recv_server,
-                                        IMAP4_PORT,
+#if !USE_SSL
+                       imap_session_new(folder->account->recv_server, port,
                                         folder->account->userid,
                                         folder->account->passwd);
+#else
+                       imap_session_new(folder->account->recv_server, port,
+                                        folder->account->userid,
+                                        folder->account->passwd,
+                                        folder->account->imap_ssl);
+#endif
                if (rfolder->session)
                        imap_parse_namespace(IMAP_SESSION(rfolder->session),
                                             IMAP_FOLDER(folder));
@@ -221,12 +256,19 @@ static IMAPSession *imap_session_get(Folder *folder)
        if (imap_cmd_noop(rfolder->session->sock) != IMAP_SUCCESS) {
                log_warning(_("IMAP4 connection to %s:%d has been"
                              " disconnected. Reconnecting...\n"),
-                           folder->account->recv_server, IMAP4_PORT);
+                           folder->account->recv_server, port);
                session_destroy(rfolder->session);
                rfolder->session =
-                       imap_session_new(folder->account->recv_server,
-                                        IMAP4_PORT, folder->account->userid,
+#if !USE_SSL
+                       imap_session_new(folder->account->recv_server, port,
+                                        folder->account->userid,
                                         folder->account->passwd);
+#else
+                       imap_session_new(folder->account->recv_server, port,
+                                        folder->account->userid,
+                                        folder->account->passwd,
+                                        folder->account->imap_ssl);
+#endif
                if (rfolder->session)
                        imap_parse_namespace(IMAP_SESSION(rfolder->session),
                                             IMAP_FOLDER(folder));
@@ -249,8 +291,14 @@ 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)
+#else
+Session *imap_session_new(const gchar *server, gushort port,
+                         const gchar *user, const gchar *pass,
+                         gboolean use_ssl)
+#endif
 {
        gchar buf[IMAPBUFSIZE];
        IMAPSession *session;
@@ -271,7 +319,11 @@ Session *imap_session_new(const gchar *server, gushort port,
        log_message(_("creating IMAP4 connection to %s:%d ...\n"),
                    server, port);
 
+#if !USE_SSL
        if ((imap_sock = imap_open(server, port, buf)) == NULL)
+#else
+       if ((imap_sock = imap_open(server, port, buf, use_ssl)) == NULL)
+#endif
                return NULL;
        if (imap_cmd_login(imap_sock, user, pass) != IMAP_SUCCESS) {
                imap_cmd_logout(imap_sock);
@@ -295,6 +347,9 @@ Session *imap_session_new(const gchar *server, gushort port,
 
 void imap_session_destroy(IMAPSession *session)
 {
+#if USE_SSL
+       ssl_done_socket(SESSION(session)->sock);
+#endif
        sock_close(SESSION(session)->sock);
        SESSION(session)->sock = NULL;
 
@@ -419,6 +474,8 @@ gchar *imap_fetch_msg(Folder *folder, FolderItem *item, gint uid)
        g_return_val_if_fail(item != NULL, NULL);
 
        path = folder_item_get_path(item);
+       if (!is_dir_exist(path))
+               make_dir_hier(path);
        filename = g_strconcat(path, G_DIR_SEPARATOR_S, itos(uid), NULL);
        g_free(path);
  
@@ -678,6 +735,26 @@ gint imap_remove_all_msg(Folder *folder, FolderItem *item)
 
 void imap_scan_folder(Folder *folder, FolderItem *item)
 {
+       IMAPSession *session;
+       gint messages, recent, unseen;
+       guint32 uid_validity;
+       gint ok;
+
+       g_return_if_fail(folder != NULL);
+       g_return_if_fail(item != NULL);
+
+       session = imap_session_get(folder);
+       if (!session) return;
+
+       ok = imap_status(session, IMAP_FOLDER(folder), item->path,
+                        &messages, &recent, &unseen, &uid_validity);
+       statusbar_pop_all();
+       if (ok != IMAP_SUCCESS) return;
+
+       item->new = recent;
+       item->unread = unseen;
+       item->total = messages;
+       /* item->mtime = uid_validity; */
 }
 
 void imap_scan_tree(Folder *folder)
@@ -717,12 +794,16 @@ void imap_scan_tree(Folder *folder)
        folder->node = g_node_new(item);
        g_free(root_folder);
 
-       inbox = folder_item_new("INBOX", "INBOX");
-       inbox->stype = F_INBOX;
-       folder_item_append(item, inbox);
-       folder->inbox = inbox;
-
        imap_scan_tree_recursive(session, item, namespace);
+
+       if (!folder->inbox) {
+               inbox = folder_item_new("INBOX", "INBOX");
+               inbox->stype = F_INBOX;
+               folder_item_append(item, inbox);
+               folder->inbox = inbox;
+       }
+       if (!folder->trash)
+               imap_create_trash(folder);
 }
 
 static void imap_scan_tree_recursive(IMAPSession *session,
@@ -763,7 +844,23 @@ static void imap_scan_tree_recursive(IMAPSession *session,
        item_list = imap_parse_list(session, real_path);
        for (cur = item_list; cur != NULL; cur = cur->next) {
                new_item = cur->data;
+               if (!strcmp(new_item->path, "INBOX")) {
+                       if (!item->folder->inbox) {
+                               new_item->stype = F_INBOX;
+                               item->folder->inbox = new_item;
+                       } else {
+                               folder_item_destroy(new_item);
+                               continue;
+                       }
+               } else if (!item->parent && !item->folder->trash) {
+                       if (!strcasecmp(g_basename(new_item->path), "Trash")) {
+                               new_item->stype = F_TRASH;
+                               item->folder->trash = new_item;
+                       }
+               }
                folder_item_append(item, new_item);
+               if (new_item->no_select == FALSE)
+                       imap_scan_folder(new_item->folder, new_item);
                if (new_item->no_sub == FALSE)
                        imap_scan_tree_recursive(session, new_item, namespace);
        }
@@ -823,7 +920,6 @@ static GSList *imap_parse_list(IMAPSession *session, const gchar *path)
                strtailchomp(buf, separator[0]);
                if (buf[0] == '\0') continue;
                if (!strcmp(buf, path)) continue;
-               if (!strcmp(buf, "INBOX")) continue;
 
                if (separator[0] != '\0')
                        subst_char(buf, separator[0], '/');
@@ -849,25 +945,43 @@ static GSList *imap_parse_list(IMAPSession *session, const gchar *path)
 
 gint imap_create_tree(Folder *folder)
 {
-       IMAPFolder *imapfolder = IMAP_FOLDER(folder);
        FolderItem *item;
-       FolderItem *new_item;
-       gchar *trash_path;
-       gchar *imap_dir = "";
 
        g_return_val_if_fail(folder != NULL, -1);
        g_return_val_if_fail(folder->node != NULL, -1);
        g_return_val_if_fail(folder->node->data != NULL, -1);
        g_return_val_if_fail(folder->account != NULL, -1);
 
-       imap_session_get(folder);
+       imap_scan_tree(folder);
 
        item = FOLDER_ITEM(folder->node->data);
 
-       new_item = folder_item_new("INBOX", "INBOX");
-       new_item->stype = F_INBOX;
-       folder_item_append(item, new_item);
-       folder->inbox = new_item;
+       if (!folder->inbox) {
+               FolderItem *inbox;
+
+               inbox = folder_item_new("INBOX", "INBOX");
+               inbox->stype = F_INBOX;
+               folder_item_append(item, inbox);
+               folder->inbox = inbox;
+       }
+       if (!folder->trash)
+               imap_create_trash(folder);
+
+       return 0;
+}
+
+static gint imap_create_trash(Folder *folder)
+{
+       IMAPFolder *imapfolder = IMAP_FOLDER(folder);
+       FolderItem *item;
+       FolderItem *new_item;
+       gchar *trash_path;
+       gchar *imap_dir = "";
+
+       g_return_val_if_fail(folder != NULL, -1);
+       g_return_val_if_fail(folder->node != NULL, -1);
+       g_return_val_if_fail(folder->node->data != NULL, -1);
+       g_return_val_if_fail(folder->account != NULL, -1);
 
        if (folder->account->imap_dir && *folder->account->imap_dir) {
                gchar *tmpdir;
@@ -887,12 +1001,13 @@ gint imap_create_tree(Folder *folder)
 
                        Xstrdup_a(name, namespace->name, return -1);
                        subst_char(name, namespace->separator, '/');
-                       trash_path = g_strconcat(name, imap_dir, "trash", NULL);
+                       trash_path = g_strconcat(name, imap_dir, "Trash", NULL);
                } else
-                       trash_path = g_strconcat(imap_dir, "trash", NULL);
+                       trash_path = g_strconcat(imap_dir, "Trash", NULL);
        } else
-               trash_path = g_strconcat(imap_dir, "trash", NULL);
+               trash_path = g_strconcat(imap_dir, "Trash", NULL);
 
+       item = FOLDER_ITEM(folder->node->data);
        new_item = imap_create_folder(folder, item, trash_path);
 
        if (!new_item) {
@@ -913,6 +1028,7 @@ gint imap_create_tree(Folder *folder)
        folder->trash = new_item;
 
        g_free(trash_path);
+
        return 0;
 }
 
@@ -1012,6 +1128,8 @@ gint imap_remove_folder(Folder *folder, FolderItem *item)
        gint ok;
        IMAPSession *session;
        gchar *path;
+       gint exists, recent, unseen;
+       guint32 uid_validity;
 
        g_return_val_if_fail(folder != NULL, -1);
        g_return_val_if_fail(item != NULL, -1);
@@ -1021,6 +1139,15 @@ gint imap_remove_folder(Folder *folder, FolderItem *item)
        if (!session) return -1;
 
        path = imap_get_real_path(IMAP_FOLDER(folder), item->path);
+
+       ok = imap_cmd_examine(SESSION(session)->sock, "INBOX",
+                             &exists, &recent, &unseen, &uid_validity);
+       statusbar_pop_all();
+       if (ok != IMAP_SUCCESS) {
+               g_free(path);
+               return -1;
+       }
+
        ok = imap_cmd_delete(SESSION(session)->sock, path);
        statusbar_pop_all();
        if (ok != IMAP_SUCCESS) {
@@ -1148,7 +1275,11 @@ static void imap_delete_all_cached_messages(FolderItem *item)
        debug_print(_("done.\n"));
 }
 
+#if !USE_SSL
 static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf)
+#else
+static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf, gboolean use_ssl)
+#endif
 {
        SockInfo *sock;
 
@@ -1158,6 +1289,13 @@ static SockInfo *imap_open(const gchar *server, gushort port, gchar *buf)
                return NULL;
        }
 
+#if USE_SSL
+       if(use_ssl && !ssl_init_socket(sock)) {
+               sock_close(sock);
+               return NULL;
+       }
+#endif
+
        imap_cmd_count = 0;
 
        if (imap_cmd_noop(sock) != IMAP_SUCCESS) {
@@ -1414,20 +1552,20 @@ static MsgFlags imap_parse_flags(const gchar *flag_str)
        const gchar *p = flag_str;
        MsgFlags flags;
 
-       flags = 0;
-       MSG_SET_FLAGS(flags, MSG_UNREAD|MSG_IMAP);
+       flags.perm_flags = MSG_UNREAD;
+       flags.tmp_flags  = MSG_IMAP;
 
        while ((p = strchr(p, '\\')) != NULL) {
                p++;
 
                if (g_strncasecmp(p, "Recent", 6) == 0) {
-                       MSG_SET_FLAGS(flags, MSG_NEW|MSG_UNREAD);
+                       MSG_SET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
                } else if (g_strncasecmp(p, "Seen", 4) == 0) {
-                       MSG_UNSET_FLAGS(flags, MSG_NEW|MSG_UNREAD);
+                       MSG_UNSET_PERM_FLAGS(flags, MSG_NEW|MSG_UNREAD);
                } else if (g_strncasecmp(p, "Deleted", 7) == 0) {
-                       MSG_SET_FLAGS(flags, MSG_DELETED);
+                       MSG_SET_PERM_FLAGS(flags, MSG_DELETED);
                } else if (g_strncasecmp(p, "Flagged", 7) == 0) {
-                       MSG_SET_FLAGS(flags, MSG_MARKED);
+                       MSG_SET_PERM_FLAGS(flags, MSG_MARKED);
                }
        }
 
@@ -1454,8 +1592,7 @@ static MsgInfo *imap_parse_envelope(SockInfo *sock, GString *line_str)
        gchar *to = NULL;
        gchar *inreplyto = NULL;
        gchar *msgid = NULL;
-       MsgFlags flags = 0;
-       gint n_element = 0;
+       MsgFlags flags = {0, 0};
 
        g_return_val_if_fail(line_str != NULL, NULL);
        g_return_val_if_fail(line_str->str[0] == '*' &&
@@ -1478,7 +1615,7 @@ static MsgInfo *imap_parse_envelope(SockInfo *sock, GString *line_str)
        g_return_val_if_fail(*cur_pos == '(', NULL);
        cur_pos++;
 
-       while (n_element < 4) {
+       while (*cur_pos != '\0' && *cur_pos != ')') {
                while (*cur_pos == ' ') cur_pos++;
 
                if (!strncmp(cur_pos, "UID ", 4)) {
@@ -1569,9 +1706,6 @@ static MsgInfo *imap_parse_envelope(SockInfo *sock, GString *line_str)
                        g_warning("invalid FETCH response: %s\n", cur_pos);
                        break;
                }
-
-               if (*cur_pos == '\0' || *cur_pos == ')') break;
-               n_element++;
        }
 
        msginfo = g_new0(MsgInfo, 1);
@@ -1669,26 +1803,69 @@ catch:
        return ok;
 }
 
-#undef THROW
-
-#if 0
-static gint imap_status_uidnext(IMAPSession *session, IMAPFolder *folder,
-                               const gchar *path, guint32 *uid_next)
+static gint imap_status(IMAPSession *session, IMAPFolder *folder,
+                       const gchar *path,
+                       gint *messages, gint *recent, gint *unseen,
+                       guint32 *uid_validity)
 {
        gchar *real_path;
        gint ok;
+       GPtrArray *argbuf;
+       gchar *str;
+
+       *messages = *recent = *unseen = *uid_validity = 0;
+
+       argbuf = g_ptr_array_new();
 
        real_path = imap_get_real_path(folder, path);
-       ok = imap_cmd_status(SESSION(session)->sock, real_path,
-                            "UIDNEXT", uid_next);
-       if (ok != IMAP_SUCCESS)
-               log_warning(_("can't get the next uid of folder: %s\n"),
-                           real_path);
+       if (strchr(real_path, ' ') != NULL)
+               imap_cmd_gen_send(SESSION(session)->sock, "STATUS \"%s\" "
+                                       "(MESSAGES RECENT UNSEEN UIDVALIDITY)",
+                                 real_path);
+       else
+               imap_cmd_gen_send(SESSION(session)->sock, "STATUS %s "
+                                       "(MESSAGES RECENT UNSEEN UIDVALIDITY)",
+                                 real_path);
+
+       ok = imap_cmd_ok(SESSION(session)->sock, argbuf);
+       if (ok != IMAP_SUCCESS) THROW(ok);
+
+       str = search_array_contain_str(argbuf, "STATUS");
+       if (!str) THROW(IMAP_ERROR);
+
+       str = strchr(str, '(');
+       if (!str) THROW(IMAP_ERROR);
+       str++;
+       while (*str != '\0' && *str != ')') {
+               while (*str == ' ') str++;
+
+               if (!strncmp(str, "MESSAGES ", 9)) {
+                       str += 9;
+                       *messages = strtol(str, &str, 10);
+               } else if (!strncmp(str, "RECENT ", 7)) {
+                       str += 7;
+                       *recent = strtol(str, &str, 10);
+               } else if (!strncmp(str, "UNSEEN ", 7)) {
+                       str += 7;
+                       *unseen = strtol(str, &str, 10);
+               } else if (!strncmp(str, "UIDVALIDITY ", 12)) {
+                       str += 12;
+                       *uid_validity = strtoul(str, &str, 10);
+               } else {
+                       g_warning("invalid STATUS response: %s\n", str);
+                       break;
+               }
+       }
+
+catch:
        g_free(real_path);
+       ptr_array_free_strings(argbuf);
+       g_ptr_array_free(argbuf, TRUE);
 
        return ok;
 }
-#endif
+
+#undef THROW
 
 
 /* low-level IMAP4rev1 commands */
@@ -1777,21 +1954,28 @@ static gint imap_cmd_list(SockInfo *sock, const gchar *ref,
 
 #define THROW goto catch
 
-static gint imap_cmd_select(SockInfo *sock, const gchar *folder,
-                           gint *exists, gint *recent, gint *unseen,
-                           guint32 *uid_validity)
+static gint imap_cmd_do_select(SockInfo *sock, const gchar *folder,
+                              gboolean examine,
+                              gint *exists, gint *recent, gint *unseen,
+                              guint32 *uid_validity)
 {
        gint ok;
        gchar *resp_str;
        GPtrArray *argbuf;
+       gchar *select_cmd;
 
        *exists = *recent = *unseen = *uid_validity = 0;
        argbuf = g_ptr_array_new();
 
+       if (examine)
+               select_cmd = "EXAMINE";
+       else
+               select_cmd = "SELECT";
+
        if (strchr(folder, ' ') != NULL)
-               imap_cmd_gen_send(sock, "SELECT \"%s\"", folder);
+               imap_cmd_gen_send(sock, "%s \"%s\"", select_cmd, folder);
        else
-               imap_cmd_gen_send(sock, "SELECT %s", folder);
+               imap_cmd_gen_send(sock, "%s %s", select_cmd, folder);
 
        if ((ok = imap_cmd_ok(sock, argbuf)) != IMAP_SUCCESS) THROW;
 
@@ -1835,42 +2019,21 @@ catch:
        return ok;
 }
 
-#if 0
-static gint imap_cmd_status(SockInfo *sock, const gchar *folder,
-                           const gchar *status, gint *value)
+static gint imap_cmd_select(SockInfo *sock, const gchar *folder,
+                           gint *exists, gint *recent, gint *unseen,
+                           guint32 *uid_validity)
 {
-       gint ok;
-       GPtrArray *argbuf;
-       gchar *str;
-
-       if (value) *value = 0;
-       argbuf = g_ptr_array_new();
-
-       if (strchr(folder, ' ') != NULL)
-               imap_cmd_gen_send(sock, "STATUS \"%s\" (%s)", folder, status);
-       else
-               imap_cmd_gen_send(sock, "STATUS %s (%s)", folder, status);
-
-       if ((ok = imap_cmd_ok(sock, argbuf)) != IMAP_SUCCESS) THROW;
-
-       str = search_array_contain_str(argbuf, "STATUS");
-       if (!str) THROW;
-       str = strchr(str, '(');
-       if (!str) THROW;
-       str++;
-       if (strncmp(str, status, strlen(status)) != 0) THROW;
-       str += strlen(status);
-       if (*str != ' ') THROW;
-       str++;
-       if (value) *value = atoi(str);
-
-catch:
-       ptr_array_free_strings(argbuf);
-       g_ptr_array_free(argbuf, TRUE);
+       return imap_cmd_do_select(sock, folder, FALSE,
+                                 exists, recent, unseen, uid_validity);
+}
 
-       return ok;
+static gint imap_cmd_examine(SockInfo *sock, const gchar *folder,
+                            gint *exists, gint *recent, gint *unseen,
+                            guint32 *uid_validity)
+{
+       return imap_cmd_do_select(sock, folder, TRUE,
+                                 exists, recent, unseen, uid_validity);
 }
-#endif
 
 #undef THROW