pykolab can't handle undelete mailbox in murder topology to a different root folder
Open, 60Public

Description

My task is undelete user mailbox (mailboxes) to a shared folder. This used to work before, but in pykolab-0.8.7-1.1.el7.kolab_16.noarch version it does not anymore.

kolab undelete-mailbox DELETED/the.user/58C8DC6D@domain.lt shared/undeleted-mailboxes@domain.lt

Expected result is that DELETED/the.user/58C8DC6D@domain.lt mailbox and all deleted submailboxes become submailboxes of shared/undeleted-mailboxes/the.user@domain.lt.
My Cyrus runs in murder topology with 2 backend servers, 2 frontend and 1 mupdate.
The above undelete mailbox command fails in such a situation, because pykolab/imap/cyrus.py line 184:

# TODO: Workaround for undelete
if len(self.lm(mailfolder)) < 1:
     return self.server

The mailfolder variable is constructed few lines above and has value user/the.user/@domain.lt. The workaround does not work in Murder topology, because the mailbox is deleted and the self.server returned is address of frontend server, not the backend server. Further code path determines that frontend != backend (the shared mailbox server is resolved correctly) and tries to first XFER deleted mailbox to backend of shared folder and then RENAME. The XFER command fails, because deleted mailbox is already on that server (and I'm not sure, but XFER command should be issued directly to the backend server holding the mailbox, not to frontend). So XFER fails and undelete-mailbox command fails.
Here is the proposed ugly patch to resolve the issue, actually to do a better workaround:

--- cyrus.py.bak        2017-03-15 11:40:07.304067656 +0200
+++ cyrus.py.bak.2      2017-03-15 11:48:08.577058153 +0200
@@ -173,8 +173,8 @@ class Cyrus(cyruslib.CYRUS):
 
         _mailfolder = self.parse_mailfolder(mailfolder)
 
-        prefix = _mailfolder['path_parts'].pop(0)
-        mbox = _mailfolder['path_parts'].pop(0)
+        prefix = _mailfolder['path_parts'][0]
+        mbox = _mailfolder['path_parts'][1]
         if _mailfolder['domain'] is not None:
             mailfolder = "%s%s%s@%s" % (
                     prefix,
@@ -184,8 +184,14 @@ class Cyrus(cyruslib.CYRUS):
                 )
 
         # TODO: Workaround for undelete
-        if len(self.lm(mailfolder)) < 1:
-            return self.server
+        if len(self.lm(mailfolder)) < 1 and _mailfolder['hex_timestamp']:
+            mailfolder = self.folder_utf7("DELETED/%s%s%s@%s" % (
+                    self.separator.join(_mailfolder['path_parts']),
+                    self.separator,
+                    _mailfolder['hex_timestamp'],
+                    _mailfolder['domain'])
+               )
+
 
         # TODO: Murder capabilities may have been suppressed using Cyrus IMAP
         # configuration.
@@ -224,7 +230,7 @@ class Cyrus(cyruslib.CYRUS):
         while 1:
             num_try += 1
             annotations = self._getannotation(
-                    mailfolder,
+                    '"%s"' % (mailfolder),
                     annotation_path
                 )

With this patch code actually tries to construct a deleted mailbox name back if there is a hex_timestamp key present in parsed mailfolder. This way we can find annotations for deleted mailbox, but that also brings issues that mailbox can have spaces and characters that have to be converted to imap UTF7.