Exemple #1
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self._oauth2_request_url = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        # Only set the newmail_hook in an IMAP repository.
        if self.config.has_option(self.getsection(), 'newmail_hook'):
            self.newmail_hook = self.localeval.eval(
                self.getconf('newmail_hook'))

        if self.getconf('sep', None):
            self.ui.info("The 'sep' setting is being ignored for IMAP "
                         "repository '%s' (it's autodetected)" % self)

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
                                         name="Keep alive " + self.getname(),
                                         args=(keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29 * 60
        else:
            return num

    def getsep(self):
        """Return the folder separator for the IMAP repository

        This requires that self.imapserver has been initialized with an
        acquireconnection() or it will still be `None`"""
        assert self.imapserver.delim != None, "'%s' " \
            "repository called getsep() before the folder separator was " \
            "queried from the server"% self
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception as e:
                raise OfflineImapError("remotehosteval option for repository "
                    "'%s' failed:\n%s"% (self, e), OfflineImapError.ERROR.REPO), \
                    None, exc_info()[2]
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError(
            "No remote host for repository "
            "'%s' specified." % self, OfflineImapError.ERROR.REPO)

    def get_remote_identity(self):
        """Remote identity is used for certain SASL mechanisms
        (currently -- PLAIN) to inform server about the ID
        we want to authorize as instead of our login name."""

        return self.getconf('remote_identity', default=None)

    def get_auth_mechanisms(self):
        supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
        # Mechanisms are ranged from the strongest to the
        # weakest ones.
        # TODO: we need DIGEST-MD5, it must come before CRAM-MD5
        # TODO: due to the chosen-plaintext resistance.
        default = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]

        mechs = self.getconflist('auth_mechanisms', r',\s*', default)

        for m in mechs:
            if m not in supported:
                raise OfflineImapError("Repository %s: "% self + \
                  "unknown authentication mechanism '%s'"% m,
                  OfflineImapError.ERROR.REPO)

        self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
        return mechs

    def getuser(self):
        user = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
        if user != None:
            return localeval.eval(user)

        if self.config.has_option(self.getsection(), 'remoteuser'):
            user = self.getconf('remoteuser')
        if user != None:
            return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                return netrcentry[0]

        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(
                self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                return netrcentry[0]

    def getport(self):
        port = None

        if self.config.has_option(self.getsection(), 'remoteporteval'):
            port = self.getconf('remoteporteval')
        if port != None:
            return self.localeval.eval(port)

        return self.getconfint('remoteport', None)

    def getssl(self):
        return self.getconfboolean('ssl', 0)

    def getsslclientcert(self):
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        return self.getconf_xform('sslclientcert', xforms, None)

    def getsslclientkey(self):
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        return self.getconf_xform('sslclientkey', xforms, None)

    def getsslcacertfile(self):
        """Determines CA bundle.
        
        Returns path to the CA bundle.  It is either explicitely specified
        or requested via "OS-DEFAULT" value (and we will search known
        locations for the current OS and distribution).

        If search via "OS-DEFAULT" route yields nothing, we will throw an
        exception to make our callers distinguish between not specified
        value and non-existent default CA bundle.

        It is also an error to specify non-existent file via configuration:
        it will error out later, but, perhaps, with less verbose explanation,
        so we will also throw an exception.  It is consistent with
        the above behaviour, so any explicitely-requested configuration
        that doesn't result in an existing file will give an exception.
        """

        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        cacertfile = self.getconf_xform('sslcacertfile', xforms, None)
        if self.getconf('sslcacertfile', None) == "OS-DEFAULT":
            cacertfile = get_os_sslcertfile()
            if cacertfile == None:
                searchpath = get_os_sslcertfile_searchpath()
                if searchpath:
                    reason = "Default CA bundle was requested, "\
                             "but no existing locations available.  "\
                             "Tried %s." % (", ".join(searchpath))
                else:
                    reason = "Default CA bundle was requested, "\
                             "but OfflineIMAP doesn't know any for your "\
                             "current operating system."
                raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
        if cacertfile is None:
            return None
        if not os.path.isfile(cacertfile):
            reason = "CA certfile for repository '%s' couldn't be found.  "\
                     "No such file: '%s'" % (self.name, cacertfile)
            raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
        return cacertfile

    def gettlslevel(self):
        return self.getconf('tls_level', 'tls_compat')

    def getsslversion(self):
        return self.getconf('ssl_version', None)

    def get_ssl_fingerprint(self):
        """Return array of possible certificate fingerprints.

        Configuration item cert_fingerprint can contain multiple
        comma-separated fingerprints in hex form."""

        value = self.getconf('cert_fingerprint', "")
        return [f.strip().lower() for f in value.split(',') if f]

    def getoauth2_request_url(self):
        if self._oauth2_request_url:  # Use cached value if possible.
            return self._oauth2_request_url

        oauth2_request_url = self.getconf('oauth2_request_url', None)
        if oauth2_request_url != None:
            self._oauth2_request_url = oauth2_request_url
            return self._oauth2_request_url

        #raise OfflineImapError("No remote oauth2_request_url for repository "
        #"'%s' specified."% self, OfflineImapError.ERROR.REPO)

    def getoauth2_refresh_token(self):
        return self.getconf('oauth2_refresh_token', None)

    def getoauth2_client_id(self):
        return self.getconf('oauth2_client_id', None)

    def getoauth2_client_secret(self):
        return self.getconf('oauth2_client_secret', None)

    def getpreauthtunnel(self):
        return self.getconf('preauthtunnel', None)

    def gettransporttunnel(self):
        return self.getconf('transporttunnel', None)

    def getreference(self):
        return self.getconf('reference', '')

    def getdecodefoldernames(self):
        return self.getconfboolean('decodefoldernames', 0)

    def getidlefolders(self):
        localeval = self.localeval
        return localeval.eval(self.getconf('idlefolders', '[]'))

    def getmaxconnections(self):
        num1 = len(self.getidlefolders())
        num2 = self.getconfint('maxconnections', 1)
        return max(num1, num2)

    def getexpunge(self):
        return self.getconfboolean('expunge', 1)

    def getpassword(self):
        """Return the IMAP password for this repository.

        It tries to get passwords in the following order:

        1. evaluate Repository 'remotepasseval'
        2. read password from Repository 'remotepass'
        3. read password from file specified in Repository 'remotepassfile'
        4. read password from ~/.netrc
        5. read password from /etc/netrc

        On success we return the password.
        If all strategies fail we return None."""

        # 1. evaluate Repository 'remotepasseval'
        passwd = self.getconf('remotepasseval', None)
        if passwd != None:
            return self.localeval.eval(passwd)
        # 2. read password from Repository 'remotepass'
        password = self.getconf('remotepass', None)
        if password != None:
            return password
        # 3. read password from file specified in Repository 'remotepassfile'
        passfile = self.getconf('remotepassfile', None)
        if passfile != None:
            fd = open(os.path.expanduser(passfile))
            password = fd.readline().strip()
            fd.close()
            return password
        # 4. read password from ~/.netrc
        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                user = self.getuser()
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # 5. read password from /etc/netrc
        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(
                self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                user = self.getuser()
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # no strategy yielded a password!
        return None

    def getfolder(self, foldername):
        """Return instance of OfflineIMAP representative folder."""

        return self.getfoldertype()(self.imapserver, foldername, self)

    def getfoldertype(self):
        return folder.IMAP.IMAPFolder

    def connect(self):
        imapobj = self.imapserver.acquireconnection()
        self.imapserver.releaseconnection(imapobj)

    def forgetfolders(self):
        self.folders = None

    def getfolders(self):
        """Return a list of instances of OfflineIMAP representative folder."""

        if self.folders != None:
            return self.folders
        retval = []
        imapobj = self.imapserver.acquireconnection()
        # check whether to list all folders, or subscribed only
        listfunction = imapobj.list
        if self.getconfboolean('subscribedonly', False):
            listfunction = imapobj.lsub
        try:
            listresult = listfunction(directory=self.imapserver.reference)[1]
        finally:
            self.imapserver.releaseconnection(imapobj)
        for s in listresult:
            if s == None or \
                   (isinstance(s, basestring) and s == ''):
                # Bug in imaplib: empty strings in results from
                # literals. TODO: still relevant?
                continue
            flags, delim, name = imaputil.imapsplit(s)
            flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
            if '\\noselect' in flaglist:
                continue
            foldername = imaputil.dequote(name)
            retval.append(self.getfoldertype()(self.imapserver, foldername,
                                               self))
        # Add all folderincludes
        if len(self.folderincludes):
            imapobj = self.imapserver.acquireconnection()
            try:
                for foldername in self.folderincludes:
                    try:
                        imapobj.select(foldername, readonly=True)
                    except OfflineImapError as e:
                        # couldn't select this folderinclude, so ignore folder.
                        if e.severity > OfflineImapError.ERROR.FOLDER:
                            raise
                        self.ui.error(e,
                                      exc_info()[2], 'Invalid folderinclude:')
                        continue
                    retval.append(self.getfoldertype()(self.imapserver,
                                                       foldername, self))
            finally:
                self.imapserver.releaseconnection(imapobj)

        if self.foldersort is None:
            # default sorting by case insensitive transposed name
            retval.sort(key=lambda x: str.lower(x.getvisiblename()))
        else:
            # do foldersort in a python3-compatible way
            # http://bytes.com/topic/python/answers/844614-python-3-sorting-comparison-function
            def cmp2key(mycmp):
                """Converts a cmp= function into a key= function
                We need to keep cmp functions for backward compatibility"""
                class K:
                    def __init__(self, obj, *args):
                        self.obj = obj

                    def __cmp__(self, other):
                        return mycmp(self.obj.getvisiblename(),
                                     other.obj.getvisiblename())

                return K

            retval.sort(key=cmp2key(self.foldersort))

        self.folders = retval
        return self.folders

    def makefolder(self, foldername):
        """Create a folder on the IMAP server

        This will not update the list cached in :meth:`getfolders`. You
        will need to invoke :meth:`forgetfolders` to force new caching
        when you are done creating folders yourself.

        :param foldername: Full path of the folder to be created."""

        if self.getreference():
            foldername = self.getreference() + self.getsep() + foldername
        if not foldername:  # Create top level folder as folder separator
            foldername = self.getsep()
        self.ui.makefolder(self, foldername)
        if self.account.dryrun:
            return
        imapobj = self.imapserver.acquireconnection()
        try:
            result = imapobj.create(foldername)
            if result[0] != 'OK':
                raise OfflineImapError(
                    "Folder '%s'[%s] could not be created. "
                    "Server responded: %s" % (foldername, self, str(result)),
                    OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)
Exemple #2
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        if self.getconf('sep', None):
            self.ui.info("The 'sep' setting is being ignored for IMAP "
                         "repository '%s' (it's autodetected)" % self)

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
                                         name = "Keep alive " + self.getname(),
                                         args = (keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29*60
        else:
            return num

    def getsep(self):
        """Return the folder separator for the IMAP repository

        This requires that self.imapserver has been initialized with an
        acquireconnection() or it will still be `None`"""
        assert self.imapserver.delim != None, "'%s' " \
            "repository called getsep() before the folder separator was " \
            "queried from the server" % self
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception as e:
                raise OfflineImapError("remotehosteval option for repository "\
                                       "'%s' failed:\n%s" % (self, e),
                                       OfflineImapError.ERROR.REPO)
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError("No remote host for repository "\
                                   "'%s' specified." % self,
                               OfflineImapError.ERROR.REPO)


    def get_remote_identity(self):
        """
        Remote identity is used for certain SASL mechanisms
        (currently -- PLAIN) to inform server about the ID
        we want to authorize as instead of our login name.

        """

        return self.getconf('remote_identity', default=None)

    def get_auth_mechanisms(self):
        supported = ["GSSAPI", "CRAM-MD5", "PLAIN", "LOGIN"]
        # Mechanisms are ranged from the strongest to the
        # weakest ones.
        # TODO: we need DIGEST-MD5, it must come before CRAM-MD5
        # TODO: due to the chosen-plaintext resistance.
        default = ["GSSAPI", "CRAM-MD5", "PLAIN", "LOGIN"]

        mechs = self.getconflist('auth_mechanisms', r',\s*',
          default)

        for m in mechs:
            if m not in supported:
                raise OfflineImapError("Repository %s: " % self + \
                  "unknown authentication mechanism '%s'" % m,
                  OfflineImapError.ERROR.REPO)

        self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
        return mechs


    def getuser(self):
        user = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
        if user != None:
            return localeval.eval(user)

        user = self.getconf('remoteuser')
        if user != None:
            return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                return netrcentry[0]

        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                return netrcentry[0]


    def getport(self):
        port = None

        if self.config.has_option(self.getsection(), 'remoteporteval'):
            port = self.getconf('remoteporteval')
        if port != None:
            return self.localeval.eval(port)

        return self.getconfint('remoteport', None)

    def getssl(self):
        return self.getconfboolean('ssl', 0)

    def getsslclientcert(self):
        return self.getconf('sslclientcert', None)

    def getsslclientkey(self):
        return self.getconf('sslclientkey', None)

    def getsslcacertfile(self):
        """Return the absolute path of the CA certfile to use, if any"""
        cacertfile = self.getconf('sslcacertfile', get_os_sslcertfile())
        if cacertfile is None:
            return None
        cacertfile = os.path.expanduser(cacertfile)
        cacertfile = os.path.abspath(cacertfile)
        if not os.path.isfile(cacertfile):
            raise SyntaxWarning("CA certfile for repository '%s' could "
                                "not be found. No such file: '%s'" \
                                % (self.name, cacertfile))
        return cacertfile

    def getsslversion(self):
        return self.getconf('ssl_version', None)

    def get_ssl_fingerprint(self):
        """
        Return array of possible certificate fingerprints.

        Configuration item cert_fingerprint can contain multiple
        comma-separated fingerprints in hex form.

        """

        value = self.getconf('cert_fingerprint', "")
        return [f.strip().lower() for f in value.split(',') if f]

    def getpreauthtunnel(self):
        return self.getconf('preauthtunnel', None)

    def gettransporttunnel(self):
        return self.getconf('transporttunnel', None)

    def getreference(self):
        return self.getconf('reference', '')

    def getidlefolders(self):
        localeval = self.localeval
        return localeval.eval(self.getconf('idlefolders', '[]'))

    def getmaxconnections(self):
        num1 = len(self.getidlefolders())
        num2 = self.getconfint('maxconnections', 1)
        return max(num1, num2)

    def getexpunge(self):
        return self.getconfboolean('expunge', 1)

    def getpassword(self):
        """Return the IMAP password for this repository.

        It tries to get passwords in the following order:

        1. evaluate Repository 'remotepasseval'
        2. read password from Repository 'remotepass'
        3. read password from file specified in Repository 'remotepassfile'
        4. read password from ~/.netrc
        5. read password from /etc/netrc

        On success we return the password.
        If all strategies fail we return None.
        """
        # 1. evaluate Repository 'remotepasseval'
        passwd = self.getconf('remotepasseval', None)
        if passwd != None:
            return self.localeval.eval(passwd)
        # 2. read password from Repository 'remotepass'
        password = self.getconf('remotepass', None)
        if password != None:
            return password
        # 3. read password from file specified in Repository 'remotepassfile'
        passfile = self.getconf('remotepassfile', None)
        if passfile != None:
            fd = open(os.path.expanduser(passfile))
            password = fd.readline().strip()
            fd.close()
            return password
        # 4. read password from ~/.netrc
        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                user = self.getconf('remoteuser')
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # 5. read password from /etc/netrc
        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                user = self.getconf('remoteuser')
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # no strategy yielded a password!
        return None


    def getfolder(self, foldername):
        return self.getfoldertype()(self.imapserver, foldername, self)

    def getfoldertype(self):
        return folder.IMAP.IMAPFolder

    def connect(self):
        imapobj = self.imapserver.acquireconnection()
        self.imapserver.releaseconnection(imapobj)

    def forgetfolders(self):
        self.folders = None

    def getfolders(self):
        if self.folders != None:
            return self.folders
        retval = []
        imapobj = self.imapserver.acquireconnection()
        # check whether to list all folders, or subscribed only
        listfunction = imapobj.list
        if self.getconfboolean('subscribedonly', False):
            listfunction = imapobj.lsub
        try:
            listresult = listfunction(directory = self.imapserver.reference)[1]
        finally:
            self.imapserver.releaseconnection(imapobj)
        for string in listresult:
            if string == None or \
                   (isinstance(string, basestring) and string == ''):
                # Bug in imaplib: empty strings in results from
                # literals. TODO: still relevant?
                continue
            flags, delim, name = imaputil.imapsplit(string)
            flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
            if '\\noselect' in flaglist:
                continue
            foldername = imaputil.dequote(name)
            retval.append(self.getfoldertype()(self.imapserver, foldername,
                                               self))
        # Add all folderincludes
        if len(self.folderincludes):
            imapobj = self.imapserver.acquireconnection()
            try:
                for foldername in self.folderincludes:
                    try:
                        imapobj.select(foldername, readonly = True)
                    except OfflineImapError as e:
                        # couldn't select this folderinclude, so ignore folder.
                        if e.severity > OfflineImapError.ERROR.FOLDER:
                            raise
                        self.ui.error(e, exc_info()[2],
                                      'Invalid folderinclude:')
                        continue
                    retval.append(self.getfoldertype()(self.imapserver,
                                                       foldername,
                                                       self))
            finally:
                self.imapserver.releaseconnection(imapobj)

        if self.foldersort is None:
            # default sorting by case insensitive transposed name
            retval.sort(key=lambda x: str.lower(x.getvisiblename()))
        else:
            # do foldersort in a python3-compatible way
            # http://bytes.com/topic/python/answers/844614-python-3-sorting-comparison-function
            def cmp2key(mycmp):
                """Converts a cmp= function into a key= function
                We need to keep cmp functions for backward compatibility"""
                class K:
                    def __init__(self, obj, *args):
                        self.obj = obj
                    def __cmp__(self, other):
                        return mycmp(self.obj.getvisiblename(), other.obj.getvisiblename())
                return K
            retval.sort(key=cmp2key(self.foldersort))

        self.folders = retval
        return self.folders

    def makefolder(self, foldername):
        """Create a folder on the IMAP server

        This will not update the list cached in :meth:`getfolders`. You
        will need to invoke :meth:`forgetfolders` to force new caching
        when you are done creating folders yourself.

        :param foldername: Full path of the folder to be created."""
        if self.getreference():
            foldername = self.getreference() + self.getsep() + foldername
        if not foldername: # Create top level folder as folder separator
            foldername = self.getsep()
        self.ui.makefolder(self, foldername)
        if self.account.dryrun:
            return
        imapobj = self.imapserver.acquireconnection()
        try:
            result = imapobj.create(foldername)
            if result[0] != 'OK':
                raise OfflineImapError("Folder '%s'[%s] could not be created. "
                                       "Server responded: %s" % \
                                           (foldername, self, str(result)),
                                       OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)
Exemple #3
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        self.imapserver = imapserver.ConfigedIMAPServer(self)
        self.folders = None
        self.nametrans = lambda foldername: foldername
        self.folderfilter = lambda foldername: 1
        self.folderincludes = []
        self.foldersort = cmp
        localeval = self.localeval
        if self.config.has_option(self.getsection(), 'nametrans'):
            self.nametrans = localeval.eval(self.getconf('nametrans'),
                                            {'re': re})
        if self.config.has_option(self.getsection(), 'folderfilter'):
            self.folderfilter = localeval.eval(self.getconf('folderfilter'),
                                               {'re': re})
        if self.config.has_option(self.getsection(), 'folderincludes'):
            self.folderincludes = localeval.eval(self.getconf('folderincludes'),
                                                 {'re': re})
        if self.config.has_option(self.getsection(), 'foldersort'):
            self.foldersort = localeval.eval(self.getconf('foldersort'),
                                             {'re': re})

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
                                         name = "Keep alive " + self.getname(),
                                         args = (keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        return self.getconfint("keepalive", 0)

    def getsep(self):
        return self.imapserver.delim

    def gethost(self):
        host = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
        if host != None:
            return localeval.eval(host)

        host = self.getconf('remotehost')
        if host != None:
            return host

    def getuser(self):
        user = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
        if user != None:
            return localeval.eval(user)

        user = self.getconf('remoteuser')
        if user != None:
            return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError, inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
Exemple #4
0
class IMAPRepository(BaseRepository):
    """
    IMAP Repository Class, children of BaseRepository
    """
    def __init__(self, reposname, account):
        self.idlefolders = None
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        # Must be set before calling imapserver.IMAPServer(self)
        self.oauth2_request_url = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        self.copy_ignore_eval = None
        # Keep alive.
        self.kaevent = None
        self.kathread = None

        # Only set the newmail_hook in an IMAP repository.
        if self.config.has_option(self.getsection(), 'newmail_hook'):
            self.newmail_hook = self.localeval.eval(
                self.getconf('newmail_hook'))

        if self.getconf('sep', None):
            self.ui.info("The 'sep' setting is being ignored for IMAP "
                         "repository '%s' (it's autodetected)" % self)

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime:
            return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
                                         name="Keep alive " + self.getname(),
                                         args=(keepalivetime, self.kaevent))
        self.kathread.setDaemon(True)
        self.kathread.start()

    def stopkeepalive(self):
        if self.kaevent is None:
            return  # Keepalive is not active.

        self.kaevent.set()
        self.kathread = None
        self.kaevent = None

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def get_copy_ignore_UIDs(self, foldername):
        """Return a list of UIDs to not copy for this foldername."""

        if self.copy_ignore_eval is None:
            if self.config.has_option(self.getsection(),
                                      'copy_ignore_eval'):
                self.copy_ignore_eval = self.localeval.eval(
                    self.getconf('copy_ignore_eval'))
            else:
                self.copy_ignore_eval = lambda x: None

        return self.copy_ignore_eval(foldername)

    def getholdconnectionopen(self):
        """
        Value of holdconnectionopen or False if it is not set

        Returns: Value of holdconnectionopen or False if it is not set

        """
        if self.getidlefolders():
            return True
        return self.getconfboolean("holdconnectionopen", False)

    def getkeepalive(self):
        """
        This function returns the keepalive value. If it is not set, then
        check if the getidlefolders is set. If getidlefolders is set, then
        returns 29 * 60

        Returns: keepalive value

        """
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29 * 60
        return num

    def getsep(self):
        """Return the folder separator for the IMAP repository

        This requires that self.imapserver has been initialized with an
        acquireconnection() or it will still be `None`"""
        assert self.imapserver.delim is not None, \
            "'%s' repository called getsep() before the folder separator was " \
            "queried from the server" % self
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # Use cached value if possible.
            return self._host

        # 1) Check for remotehosteval setting.
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception as exc:
                raise OfflineImapError(
                    "remotehosteval option for repository "
                    "'%s' failed:\n%s" % (self, exc),
                    OfflineImapError.ERROR.REPO,
                    exc_info()[2]) from exc
            if host:
                self._host = host
                return self._host
        # 2) Check for plain remotehost setting.
        host = self.getconf('remotehost', None)
        if host is not None:
            self._host = host
            return self._host

        # No success.
        raise OfflineImapError("No remote host for repository "
                               "'%s' specified." % self,
                               OfflineImapError.ERROR.REPO)

    def get_remote_identity(self):
        """Remote identity is used for certain SASL mechanisms
        (currently -- PLAIN) to inform server about the ID
        we want to authorize as instead of our login name."""

        identity = self.getconf('remote_identity', default=None)
        if identity is not None:
            identity = identity.encode('UTF-8')
        return identity

    def get_auth_mechanisms(self):
        """
        Get the AUTH mechanisms. We have (ranged from the strongest to weakest)
        these methods: "GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"

        Returns: The supported AUTH Methods

        """
        supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
        # Mechanisms are ranged from the strongest to the
        # weakest ones.
        # TODO: we need DIGEST-MD5, it must come before CRAM-MD5
        # due to the chosen-plaintext resistance.
        default = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]

        mechs = self.getconflist('auth_mechanisms', r',\s*',
                                 default)

        for mech in mechs:
            if mech not in supported:
                raise OfflineImapError("Repository %s: " % self +
                                       "unknown authentication mechanism '%s'"
                                       % mech, OfflineImapError.ERROR.REPO)

        self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
        return mechs

    def getuser(self):
        """
        Returns the remoteusereval or remoteuser  or netrc user value.

        Returns: Returns the remoteusereval or remoteuser or netrc user value.

        """
        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
            if user is not None:
                l_user = self.localeval.eval(user)

                # We need a str username
                if isinstance(l_user, bytes):
                    return l_user.decode(encoding='utf-8')
                elif isinstance(l_user, str):
                    return l_user

                # If is not bytes or str, we have a problem
                raise OfflineImapError("Could not get a right username format for"
                                       " repository %s. Type found: %s. "
                                       "Please, open a bug." %
                                       (self.name, type(l_user)),
                                       OfflineImapError.ERROR.FOLDER)

        if self.config.has_option(self.getsection(), 'remoteuser'):
            # Assume the configuration file to be UTF-8 encoded so we must not
            # encode this string again.
            user = self.getconf('remoteuser')
            if user is not None:
                return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                return netrcentry[0]

        try:
            netrcentry = netrc.netrc('/etc/netrc')\
                .authenticators(self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                return netrcentry[0]

    def getport(self):
        """
        Returns remoteporteval value or None if not found.

        Returns: Returns remoteporteval int value or None if not found.

        """
        port = None

        if self.config.has_option(self.getsection(), 'remoteporteval'):
            port = self.getconf('remoteporteval')
        if port is not None:
            return self.localeval.eval(port)

        return self.getconfint('remoteport', None)

    def getipv6(self):
        """
        Returns if IPv6 is set. If not set, then return None

        Returns: Boolean flag if IPv6 is set.

        """
        return self.getconfboolean('ipv6', None)

    def getssl(self):
        """
        Get the boolean SSL value. Default is True, used if not found.

        Returns: Get the boolean SSL value. Default is True

        """
        return self.getconfboolean('ssl', True)

    def getsslclientcert(self):
        """
        Return the SSL client cert (sslclientcert) or None if not found

        Returns: SSL client key (sslclientcert) or None if not found

        """
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        return self.getconf_xform('sslclientcert', xforms, None)

    def getsslclientkey(self):
        """
        Return the SSL client key (sslclientkey) or None if not found

        Returns: SSL client key (sslclientkey) or None if not found

        """
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        return self.getconf_xform('sslclientkey', xforms, None)

    def getsslcacertfile(self):
        """Determines CA bundle.

        Returns path to the CA bundle.  It is explicitely specified or
        requested via "OS-DEFAULT" value (and we will search known
        locations for the current OS and distribution). If it is not
        specified, we will search it in the known locations.

        If search route, via "OS-DEFAULT" or because is not specified,
        yields nothing, we will throw an exception to make our callers
        distinguish between not specified value and non-existent
        default CA bundle.

        It is also an error to specify non-existent file via configuration:
        it will error out later, but, perhaps, with less verbose explanation,
        so we will also throw an exception.  It is consistent with
        the above behaviour, so any explicitely-requested configuration
        that doesn't result in an existing file will give an exception.
        """
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        cacertfile = self.getconf_xform('sslcacertfile', xforms, None)
        # Can't use above cacertfile because of abspath.
        conf_sslacertfile = self.getconf('sslcacertfile', None)
        if conf_sslacertfile == "OS-DEFAULT" or \
                conf_sslacertfile is None or \
                conf_sslacertfile == '':
            cacertfile = get_os_sslcertfile()
            if cacertfile is None:
                searchpath = get_os_sslcertfile_searchpath()
                if searchpath:
                    reason = "Default CA bundle was requested, " \
                             "but no existing locations available.  " \
                             "Tried %s." % (", ".join(searchpath))
                else:
                    reason = "Default CA bundle was requested, " \
                             "but OfflineIMAP doesn't know any for your " \
                             "current operating system."
                raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
        if cacertfile is None:
            return None
        if not os.path.isfile(cacertfile):
            reason = "CA certfile for repository '%s' couldn't be found.  " \
                     "No such file: '%s'" % (self.name, cacertfile)
            raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
        return cacertfile

    def gettlslevel(self):
        """
        Returns the TLS level (tls_level). If not set, returns 'tls_compat'

        Returns: TLS level (tls_level). If not set, returns 'tls_compat'

        """
        return self.getconf('tls_level', 'tls_compat')

    def getsslversion(self):
        """
        Returns the SSL version. If not set, returns None.

        Returns: SSL version. If not set, returns None.

        """
        return self.getconf('ssl_version', None)

    def getstarttls(self):
        """
        Get the value of starttls. If not set, returns True

        Returns: Value of starttls. If not set, returns True

        """
        return self.getconfboolean('starttls', True)

    def get_ssl_fingerprint(self):
        """Return array of possible certificate fingerprints.

        Configuration item cert_fingerprint can contain multiple
        comma-separated fingerprints in hex form."""

        value = self.getconf('cert_fingerprint', "")
        return [f.strip().lower().replace(":", "")
                for f in value.split(',') if f]

    def setoauth2_request_url(self, url):
        """
        Set the OAUTH2 URL request.

        Args:
            url: OAUTH2 URL request

        Returns: None

        """
        self.oauth2_request_url = url

    def getoauth2_request_url(self):
        """
        Returns the OAUTH2 URL request from configuration (oauth2_request_url).
        If it is not found, then returns None

        Returns: OAUTH2 URL request (oauth2_request_url)

        """
        if self.oauth2_request_url is not None:  # Use cached value if possible.
            return self.oauth2_request_url

        self.setoauth2_request_url(self.getconf('oauth2_request_url', None))
        return self.oauth2_request_url

    def getoauth2_refresh_token(self):
        """
        Get the OAUTH2 refresh token from the configuration
        (oauth2_refresh_token)
        If the access token is not found, then returns None.

        Returns: OAUTH2 refresh token (oauth2_refresh_token)

        """
        refresh_token = self.getconf('oauth2_refresh_token', None)
        if refresh_token is None:
            refresh_token = self.localeval.eval(
                self.getconf('oauth2_refresh_token_eval', "None")
            )
            if refresh_token is not None:
                refresh_token = refresh_token.strip("\n")
        return refresh_token

    def getoauth2_access_token(self):
        """
        Get the OAUTH2 access token from the configuration (oauth2_access_token)
        If the access token is not found, then returns None.

        Returns: OAUTH2 access token (oauth2_access_token)

        """
        access_token = self.getconf('oauth2_access_token', None)
        if access_token is None:
            access_token = self.localeval.eval(
                self.getconf('oauth2_access_token_eval', "None")
            )
            if access_token is not None:
                access_token = access_token.strip("\n")
        return access_token

    def getoauth2_client_id(self):
        """
        Get the OAUTH2 client id (oauth2_client_id) from the configuration.
        If not found, returns None

        Returns: OAUTH2 client id (oauth2_client_id)

        """
        client_id = self.getconf('oauth2_client_id', None)
        if client_id is None:
            client_id = self.localeval.eval(
                self.getconf('oauth2_client_id_eval', "None")
            )
            if client_id is not None:
                client_id = client_id.strip("\n")
        return client_id

    def getoauth2_client_secret(self):
        """
        Get the OAUTH2 client secret (oauth2_client_secret) from the
        configuration. If it is not found, then returns None.

        Returns: OAUTH2 client secret

        """
        client_secret = self.getconf('oauth2_client_secret', None)
        if client_secret is None:
            client_secret = self.localeval.eval(
                self.getconf('oauth2_client_secret_eval', "None")
            )
            if client_secret is not None:
                client_secret = client_secret.strip("\n")
        return client_secret

    def getpreauthtunnel(self):
        """
        Get the value of preauthtunnel. If not found, then returns None.

        Returns: Returns preauthtunnel value. If not found, returns None.

        """
        return self.getconf('preauthtunnel', None)

    def gettransporttunnel(self):
        """
        Get the value of transporttunnel. If not found, then returns None.

        Returns: Returns transporttunnel value. If not found, returns None.

        """
        return self.getconf('transporttunnel', None)

    def getreference(self):
        """
        Get the reference value in the configuration. If the value is not found
        then returns a double quote ("") as string.

        Returns: The reference variable. If not set, then returns '""'

        """
        return self.getconf('reference', '""')

    def getdecodefoldernames(self):
        """
        Get the boolean value of decodefoldernames configuration variable,
        if the value is not found, returns False.

        Returns: Boolean value of decodefoldernames, else False

        """
        return self.getconfboolean('decodefoldernames', False)

    def getidlefolders(self):
        """
        Get the list of idlefolders from configuration. If the value is not
        found, returns an empty list.

        Returns: A list of idle folders

        """
        if self.idlefolders is None:
            self.idlefolders = self.localeval.eval(
                self.getconf('idlefolders', '[]')
            )
        return self.idlefolders

    def getmaxconnections(self):
        """
        Get the maxconnections configuration value from configuration.
        If the value is not set, returns 1 connection

        Returns: Integer value of maxconnections configuration variable, else 1

        """
        num1 = len(self.getidlefolders())
        num2 = self.getconfint('maxconnections', 1)
        return max(num1, num2)

    def getexpunge(self):
        """
        Get the expunge configuration value from configuration.
        If the value is not set in the configuration, then returns True

        Returns: Boolean value of expunge configuration variable

        """
        return self.getconfboolean('expunge', True)

    def getpassword(self):
        """Return the IMAP password for this repository.

        It tries to get passwords in the following order:

        1. evaluate Repository 'remotepasseval'
        2. read password from Repository 'remotepass'
        3. read password from file specified in Repository 'remotepassfile'
        4. read password from ~/.netrc
        5. read password from /etc/netrc

        On success we return the password.
        If all strategies fail we return None."""

        # 1. Evaluate Repository 'remotepasseval'.
        passwd = self.getconf('remotepasseval', None)
        if passwd is not None:
            l_pass = self.localeval.eval(passwd)

            # We need a str password
            if isinstance(l_pass, bytes):
                return l_pass.decode(encoding='utf-8')
            elif isinstance(l_pass, str):
                return l_pass

            # If is not bytes or str, we have a problem
            raise OfflineImapError("Could not get a right password format for"
                                   " repository %s. Type found: %s. "
                                   "Please, open a bug." %
                                   (self.name, type(l_pass)),
                                   OfflineImapError.ERROR.FOLDER)

        # 2. Read password from Repository 'remotepass'.
        password = self.getconf('remotepass', None)
        if password is not None:
            # Assume the configuration file to be UTF-8 encoded so we must not
            # encode this string again.
            return password
        # 3. Read password from file specified in Repository 'remotepassfile'.
        passfile = self.getconf('remotepassfile', None)
        if passfile is not None:
            file_desc = open(os.path.expanduser(passfile), 'r',
                             encoding='utf-8')
            password = file_desc.readline().strip()
            file_desc.close()

            # We need a str password
            if isinstance(password, bytes):
                return password.decode(encoding='utf-8')
            elif isinstance(password, str):
                return password

            # If is not bytes or str, we have a problem
            raise OfflineImapError("Could not get a right password format for"
                                   " repository %s. Type found: %s. "
                                   "Please, open a bug." %
                                   (self.name, type(password)),
                                   OfflineImapError.ERROR.FOLDER)

        # 4. Read password from ~/.netrc.
        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                user = self.getuser()
                if user is None or user == netrcentry[0]:
                    return netrcentry[2]
        # 5. Read password from /etc/netrc.
        try:
            netrcentry = netrc.netrc('/etc/netrc')\
                .authenticators(self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                user = self.getuser()
                if user is None or user == netrcentry[0]:
                    return netrcentry[2]
        # No strategy yielded a password!
        return None

    def getfolder(self, foldername, decode=True):
        """Return instance of OfflineIMAP representative folder."""

        return self.getfoldertype()(self.imapserver, foldername, self, decode)

    def getfoldertype(self):
        """
        This function returns the folder type, in this case
        folder.IMAP.IMAPFolder

        Returns: folder.IMAP.IMAPFolder

        """
        return folder.IMAP.IMAPFolder

    def connect(self):
        imapobj = self.imapserver.acquireconnection()
        self.imapserver.releaseconnection(imapobj)

    def forgetfolders(self):
        self.folders = None

    def getfolders(self):
        """Return a list of instances of OfflineIMAP representative folder."""

        if self.folders is not None:
            return self.folders
        retval = []
        imapobj = self.imapserver.acquireconnection()
        # check whether to list all folders, or subscribed only
        listfunction = imapobj.list
        if self.getconfboolean('subscribedonly', False):
            listfunction = imapobj.lsub

        try:
            result, listresult = \
                listfunction(directory=self.imapserver.reference, pattern='"*"')
            if result != 'OK':
                raise OfflineImapError("Could not list the folders for"
                                       " repository %s. Server responded: %s" %
                                       (self.name, str(listresult)),
                                       OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)

        for fldr in listresult:
            if fldr is None or (isinstance(fldr, str) and fldr == ''):
                # Bug in imaplib: empty strings in results from
                # literals. TODO: still relevant?
                continue
            try:
                flags, delim, name = imaputil.imapsplit(fldr)
            except ValueError:
                self.ui.error(
                    "could not correctly parse server response; got: %s" % fldr)
                raise
            flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
            if '\\noselect' in flaglist:
                continue
            retval.append(self.getfoldertype()(self.imapserver, name,
                                               self))
        # Add all folderincludes
        if len(self.folderincludes):
            imapobj = self.imapserver.acquireconnection()
            try:
                for foldername in self.folderincludes:
                    try:
                        imapobj.select(imaputil.utf8_IMAP(foldername),
                                       readonly=True)
                    except OfflineImapError as exc:
                        # couldn't select this folderinclude, so ignore folder.
                        if exc.severity > OfflineImapError.ERROR.FOLDER:
                            raise
                        self.ui.error(exc, exc_info()[2],
                                      'Invalid folderinclude:')
                        continue
                    retval.append(self.getfoldertype()(
                        self.imapserver, foldername, self, decode=False))
            finally:
                self.imapserver.releaseconnection(imapobj)

        if self.foldersort is None:
            # default sorting by case insensitive transposed name
            retval.sort(key=lambda x: str.lower(x.getvisiblename()))
        else:
            # do foldersort in a python3-compatible way
            # http://bytes.com/topic/python/answers/ \
            # 844614-python-3-sorting-comparison-function
            def cmp2key(mycmp):
                """Converts a cmp= function into a key= function
                We need to keep cmp functions for backward compatibility"""

                class K:
                    """
                    Class to compare getvisiblename() between two objects.
                    """
                    def __init__(self, obj, *args):
                        self.obj = obj

                    def __cmp__(self, other):
                        return mycmp(self.obj.getvisiblename(),
                                     other.obj.getvisiblename())

                    def __lt__(self, other):
                        return self.__cmp__(other) < 0

                    def __le__(self, other):
                        return self.__cmp__(other) <= 0

                    def __gt__(self, other):
                        return self.__cmp__(other) > 0

                    def __ge__(self, other):
                        return self.__cmp__(other) >= 0

                    def __eq__(self, other):
                        return self.__cmp__(other) == 0

                    def __ne__(self, other):
                        return self.__cmp__(other) != 0

                return K

            retval.sort(key=cmp2key(self.foldersort))

        self.folders = retval
        return self.folders

    def deletefolder(self, foldername):
        """Delete a folder on the IMAP server."""

        if self.account.utf_8_support:
            foldername = imaputil.utf8_IMAP(foldername)
        imapobj = self.imapserver.acquireconnection()
        try:
            result = imapobj.delete(foldername)
            if result[0] != 'OK':
                msg = "Folder '%s'[%s] could not be deleted. "\
                      "Server responded: %s" % (foldername, self, str(result))
                raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)

    def makefolder(self, foldername):
        """
        Create a folder on the IMAP server

        This will not update the list cached in :meth:`getfolders`. You
        will need to invoke :meth:`forgetfolders` to force new caching
        when you are done creating folders yourself.

        Args:
            foldername: Full path of the folder to be created

        Returns: None

        """
        if foldername == '':
            return

        if self.getreference() != '""':
            foldername = self.getreference() + self.getsep() + foldername
        if not foldername:  # Create top level folder as folder separator.
            foldername = self.getsep()
            self.makefolder_single(foldername)
            return

        parts = foldername.split(self.getsep())
        folder_paths = [self.getsep().join(parts[:n + 1])
                        for n in range(len(parts))]
        for folder_path in folder_paths:
            try:
                self.makefolder_single(folder_path)
            except OfflineImapError as exc:
                if '[ALREADYEXISTS]' not in exc.reason:
                    raise

    def makefolder_single(self, foldername):
        """
        Create a IMAP folder.

        Args:
            foldername: Folder's name to create

        Returns: None

        """
        self.ui.makefolder(self, foldername)
        if self.account.dryrun:
            return
        imapobj = self.imapserver.acquireconnection()
        try:
            if self.account.utf_8_support:
                foldername = imaputil.utf8_IMAP(foldername)

            result = imapobj.create(foldername)
            if result[0] != 'OK':
                msg = "Folder '%s'[%s] could not be created. "\
                      "Server responded: %s" % (foldername, self, str(result))
                raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)
	def __init__(self, srcusername, srcpasswd, dstusername, dstpasswd):

		try:
			import fcntl
			hasfcntl = 1
		except:
			hasfcntl = 0
	
		lockfd = None

		def lock(config, ui):
			if not hasfcntl:
				return
			lockfd = open(self.configuration.getmetadatadir() + "/lock", "w")
			try:
				fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
			except IOError:
				ui.locked()
				ui.terminate(1)

		self.configuration = CustomConfigParser()
		
		self.configuration.add_section('general')
		self.configuration.set('general','accounts', dstusername)
		self.configuration.add_section('Account ' + dstusername)

		self.configuration.set('Account ' + dstusername, 'localrepository', dstusername+'_local')
		self.configuration.set('Account ' + dstusername, 'remoterepository', dstusername+'_remote')
		
		self.configuration.add_section('Repository ' + dstusername + '_local')
		self.configuration.add_section('Repository ' + dstusername + '_remote')

		self.configuration.set('Repository ' + dstusername + '_local', 'type', 'IMAP')
		self.configuration.set('Repository ' + dstusername + '_local', 'remotehost', newserveraddr)
		self.configuration.set('Repository ' + dstusername + '_local', 'remoteuser', dstusername)
		self.configuration.set('Repository ' + dstusername + '_local', 'remotepass', dstpasswd)

		self.configuration.set('Repository ' + dstusername + '_remote', 'type', 'IMAP')
		self.configuration.set('Repository ' + dstusername + '_remote', 'remotehost', server)
		self.configuration.set('Repository ' + dstusername + '_remote', 'remoteuser', srcusername)
		self.configuration.set('Repository ' + dstusername + '_remote', 'remotepass', srcpasswd)
		
		self.monothread = 0
		# Setup a interface

		ui = offlineimap.ui.detector.findUI(self.configuration, 'TTY.TTYUI')
		UIBase.setglobalui(ui)
#		ui.add_debug('imap')
#		ui.add_debug('thread')
#		imaplib.Debug = 5
#		threading._VERBOSE = 1

		lock(self.configuration, ui)
	
		def sigterm_handler(signum, frame):
			# die immediately
			ui.terminate(errormsg="terminating...")
		signal.signal(signal.SIGTERM,sigterm_handler)

		try:
			pidfd = open(config.getmetadatadir() + "/pid", "w")
			pidfd.write(str(os.getpid()) + "\n")
			pidfd.close()
		except:
			pass
		
		try:
			activeaccounts = self.configuration.get("general", "accounts")
			activeaccounts = activeaccounts.replace(" ", "")
			activeaccounts = activeaccounts.split(",")
			allaccounts = accounts.AccountHashGenerator(self.configuration)

			if self.monothread:
				threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
			else:
				threadutil.initInstanceLimit("ACCOUNTLIMIT", self.configuration.getdefaultint("general", "maxsyncaccounts", 1))

			for reposname in self.configuration.getsectionlist('Repository'):
				for instancename in ["FOLDER_" + reposname, "MSGCOPY_" + reposname]:
					if self.monothread:
						threadutil.initInstanceLimit(instancename, 1)
					else:
						threadutil.initInstanceLimit(instancename, self.configuration.getdefaultint('Repository ' + reposname, "maxconnections", 1))
	
			syncaccounts = []
			for account in activeaccounts:
				if account not in syncaccounts:
					syncaccounts.append(account)
	
			siglisteners = []
			def sig_handler(signum, frame):
				if signum == signal.SIGUSR1:
					# tell each account to do a full sync asap
					signum = (1,)
				elif signum == signal.SIGHUP:
					# tell each account to die asap
					signum = (2,)
				elif signum == signal.SIGUSR2:
					# tell each account to do a full sync asap, then die
					signum = (1, 2)
				# one listener per account thread (up to maxsyncaccounts)
				for listener in siglisteners:
					for sig in signum:
						listener.put_nowait(sig)
			signal.signal(signal.SIGHUP,sig_handler)
			signal.signal(signal.SIGUSR1,sig_handler)
			signal.signal(signal.SIGUSR2,sig_handler)
	
			threadutil.initexitnotify()
			t = ExitNotifyThread(target=syncmaster.syncitall,
						name='Sync Runner',
						kwargs = {'accounts': syncaccounts,
						'config': self.configuration,
						'siglisteners': siglisteners})
			t.setDaemon(1)
			t.start()
		
		except:
			ui.mainException()

		try:
			threadutil.exitnotifymonitorloop(threadutil.threadexited)
		except SystemExit:
			raise
		except:
			ui.mainException()				  # Also expected to terminate.

		rmtree(self.configuration.getmetadatadir())
Exemple #6
0
def startup(versionno):
    assert versionno == version.versionstr, "Revision of main program (%s) does not match that of library (%s).  Please double-check your PYTHONPATH and installation locations." % (versionno, version.versionstr)
    options = {}
    if '--help' in sys.argv[1:]:
        sys.stdout.write(version.getcmdhelp() + "\n")
        sys.exit(0)

    for optlist in getopt(sys.argv[1:], 'P:1oqa:c:d:l:u:hk:f:')[0]:
        options[optlist[0]] = optlist[1]

    if options.has_key('-h'):
        sys.stdout.write(version.getcmdhelp())
        sys.stdout.write("\n")
        sys.exit(0)
    configfilename = os.path.expanduser("~/.offlineimaprc")
    if options.has_key('-c'):
        configfilename = options['-c']
    if options.has_key('-P'):
        if not options.has_key('-1'):
            sys.stderr.write("FATAL: profile mode REQUIRES -1\n")
            sys.exit(100)
        profiledir = options['-P']
        os.mkdir(profiledir)
        threadutil.setprofiledir(profiledir)
        sys.stderr.write("WARNING: profile mode engaged;\nPotentially large data will be created in " + profiledir + "\n")

    config = CustomConfigParser()
    if not os.path.exists(configfilename):
        sys.stderr.write(" *** Config file %s does not exist; aborting!\n" % configfilename)
        sys.exit(1)

    config.read(configfilename)

    # override config values with option '-k'
    for option in options.keys():
        if option == '-k':
            (key, value) = options['-k'].split('=', 1)
            if ':' in key:
                (secname, key) = key.split(':', 1)
                section = secname.replace("_", " ")
            else:
                section = "general"
            config.set(section, key, value)

    ui = offlineimap.ui.detector.findUI(config, options.get('-u'))
    UIBase.setglobalui(ui)

    if options.has_key('-l'):
        ui.setlogfd(open(options['-l'], 'wt'))

    ui.init_banner()

    if options.has_key('-d'):
        for debugtype in options['-d'].split(','):
            ui.add_debug(debugtype.strip())
            if debugtype == 'imap':
                imaplib.Debug = 5
            if debugtype == 'thread':
                threading._VERBOSE = 1

    if options.has_key('-o'):
        # FIXME: maybe need a better
        for section in accounts.getaccountlist(config):
            config.remove_option('Account ' + section, "autorefresh")

    if options.has_key('-q'):
        for section in accounts.getaccountlist(config):
            config.set('Account ' + section, "quick", '-1')

    if options.has_key('-f'):
        foldernames = options['-f'].replace(" ", "").split(",")
        folderfilter = "lambda f: f in %s" % foldernames
        folderincludes = "[]"
        for accountname in accounts.getaccountlist(config):
            account_section = 'Account ' + accountname
            remote_repo_section = 'Repository ' + \
                                  config.get(account_section, 'remoterepository')
            local_repo_section = 'Repository ' + \
                                 config.get(account_section, 'localrepository')
            for section in [remote_repo_section, local_repo_section]:
                config.set(section, "folderfilter", folderfilter)
                config.set(section, "folderincludes", folderincludes)

    lock(config, ui)

    def sigterm_handler(signum, frame):
        # die immediately
        ui.terminate(errormsg="terminating...")
    signal.signal(signal.SIGTERM,sigterm_handler)

    try:
        pidfd = open(config.getmetadatadir() + "/pid", "w")
        pidfd.write(str(os.getpid()) + "\n")
        pidfd.close()
    except:
        pass

    try:
        if options.has_key('-l'):
            sys.stderr = ui.logfile

        socktimeout = config.getdefaultint("general", "socktimeout", 0)
        if socktimeout > 0:
            socket.setdefaulttimeout(socktimeout)

        activeaccounts = config.get("general", "accounts")
        if options.has_key('-a'):
            activeaccounts = options['-a']
        activeaccounts = activeaccounts.replace(" ", "")
        activeaccounts = activeaccounts.split(",")
        allaccounts = accounts.AccountHashGenerator(config)

        syncaccounts = []
        for account in activeaccounts:
            if account not in allaccounts:
                if len(allaccounts) == 0:
                    errormsg = 'The account "%s" does not exist because no accounts are defined!'%account
                else:
                    errormsg = 'The account "%s" does not exist.  Valid accounts are:'%account
                    for name in allaccounts.keys():
                        errormsg += '\n%s'%name
                ui.terminate(1, errortitle = 'Unknown Account "%s"'%account, errormsg = errormsg)
            if account not in syncaccounts:
                syncaccounts.append(account)

        server = None
        remoterepos = None
        localrepos = None

        if options.has_key('-1'):
            threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
        else:
            threadutil.initInstanceLimit("ACCOUNTLIMIT",
                                         config.getdefaultint("general", "maxsyncaccounts", 1))

        for reposname in config.getsectionlist('Repository'):
            for instancename in ["FOLDER_" + reposname,
                                 "MSGCOPY_" + reposname]:
                if options.has_key('-1'):
                    threadutil.initInstanceLimit(instancename, 1)
                else:
                    threadutil.initInstanceLimit(instancename,
                                                 config.getdefaultint('Repository ' + reposname, "maxconnections", 1))
        siglisteners = []
        def sig_handler(signum, frame):
            if signum == signal.SIGUSR1:
                # tell each account to do a full sync asap
                signum = (1,)
            elif signum == signal.SIGHUP:
                # tell each account to die asap
                signum = (2,)
            elif signum == signal.SIGUSR2:
                # tell each account to do a full sync asap, then die
                signum = (1, 2)
            # one listener per account thread (up to maxsyncaccounts)
            for listener in siglisteners:
                for sig in signum:
                    listener.put_nowait(sig)
        signal.signal(signal.SIGHUP,sig_handler)
        signal.signal(signal.SIGUSR1,sig_handler)
        signal.signal(signal.SIGUSR2,sig_handler)

        threadutil.initexitnotify()
        t = ExitNotifyThread(target=syncmaster.syncitall,
                             name='Sync Runner',
                             kwargs = {'accounts': syncaccounts,
                                       'config': config,
                                       'siglisteners': siglisteners})
        t.setDaemon(1)
        t.start()
    except:
        ui.mainException()

    try:
        threadutil.exitnotifymonitorloop(threadutil.threadexited)
    except SystemExit:
        raise
    except:
        ui.mainException()                  # Also expected to terminate.
Exemple #7
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        if self.getconf('sep', None):
            self.ui.info("The 'sep' setting is being ignored for IMAP "
                         "repository '%s' (it's autodetected)" % self)

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
                                         name="Keep alive " + self.getname(),
                                         args=(keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29 * 60
        else:
            return num

    def getsep(self):
        """Return the folder separator for the IMAP repository

        This requires that self.imapserver has been initialized with an
        acquireconnection() or it will still be `None`"""
        assert self.imapserver.delim != None, "'%s' " \
            "repository called getsep() before the folder separator was " \
            "queried from the server" % self
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception as e:
                raise OfflineImapError("remotehosteval option for repository "\
                                       "'%s' failed:\n%s" % (self, e),
                                       OfflineImapError.ERROR.REPO)
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError("No remote host for repository "\
                                   "'%s' specified." % self,
                               OfflineImapError.ERROR.REPO)

    def getuser(self):
        user = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
        if user != None:
            return localeval.eval(user)

        user = self.getconf('remoteuser')
        if user != None:
            return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                return netrcentry[0]

        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(
                self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                return netrcentry[0]

    def getport(self):
        return self.getconfint('remoteport', None)

    def getssl(self):
        return self.getconfboolean('ssl', 0)

    def getsslclientcert(self):
        return self.getconf('sslclientcert', None)

    def getsslclientkey(self):
        return self.getconf('sslclientkey', None)

    def getsslcacertfile(self):
        """Return the absolute path of the CA certfile to use, if any"""
        cacertfile = self.getconf('sslcacertfile', None)
        if cacertfile is None:
            return None
        cacertfile = os.path.expanduser(cacertfile)
        cacertfile = os.path.abspath(cacertfile)
        if not os.path.isfile(cacertfile):
            raise SyntaxWarning("CA certfile for repository '%s' could "
                                "not be found. No such file: '%s'" \
                                % (self.name, cacertfile))
        return cacertfile

    def get_ssl_fingerprint(self):
        return self.getconf('cert_fingerprint', None)

    def getpreauthtunnel(self):
        return self.getconf('preauthtunnel', None)

    def getreference(self):
        return self.getconf('reference', '')

    def getidlefolders(self):
        localeval = self.localeval
        return localeval.eval(self.getconf('idlefolders', '[]'))

    def getmaxconnections(self):
        num1 = len(self.getidlefolders())
        num2 = self.getconfint('maxconnections', 1)
        return max(num1, num2)

    def getexpunge(self):
        return self.getconfboolean('expunge', 1)

    def getpassword(self):
        """Return the IMAP password for this repository.

        It tries to get passwords in the following order:

        1. evaluate Repository 'remotepasseval'
        2. read password from Repository 'remotepass'
        3. read password from file specified in Repository 'remotepassfile'
        4. read password from ~/.netrc
        5. read password from /etc/netrc

        On success we return the password.
        If all strategies fail we return None.
        """
        # 1. evaluate Repository 'remotepasseval'
        passwd = self.getconf('remotepasseval', None)
        if passwd != None:
            return self.localeval.eval(passwd)
        # 2. read password from Repository 'remotepass'
        password = self.getconf('remotepass', None)
        if password != None:
            return password
        # 3. read password from file specified in Repository 'remotepassfile'
        passfile = self.getconf('remotepassfile', None)
        if passfile != None:
            fd = open(os.path.expanduser(passfile))
            password = fd.readline().strip()
            fd.close()
            return password
        # 4. read password from ~/.netrc
        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                user = self.getconf('remoteuser')
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # 5. read password from /etc/netrc
        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(
                self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                user = self.getconf('remoteuser')
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # no strategy yielded a password!
        return None

    def getfolder(self, foldername):
        return self.getfoldertype()(self.imapserver, foldername, self)

    def getfoldertype(self):
        return folder.IMAP.IMAPFolder

    def connect(self):
        imapobj = self.imapserver.acquireconnection()
        self.imapserver.releaseconnection(imapobj)

    def forgetfolders(self):
        self.folders = None

    def getfolders(self):
        if self.folders != None:
            return self.folders
        retval = []
        imapobj = self.imapserver.acquireconnection()
        # check whether to list all folders, or subscribed only
        listfunction = imapobj.list
        if self.getconfboolean('subscribedonly', False):
            listfunction = imapobj.lsub
        try:
            listresult = listfunction(directory=self.imapserver.reference)[1]
        finally:
            self.imapserver.releaseconnection(imapobj)
        for string in listresult:
            if string == None or \
                   (isinstance(string, basestring) and string == ''):
                # Bug in imaplib: empty strings in results from
                # literals. TODO: still relevant?
                continue
            flags, delim, name = imaputil.imapsplit(string)
            flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
            if '\\noselect' in flaglist:
                continue
            foldername = imaputil.dequote(name)
            retval.append(self.getfoldertype()(self.imapserver, foldername,
                                               self))
        # Add all folderincludes
        if len(self.folderincludes):
            imapobj = self.imapserver.acquireconnection()
            try:
                for foldername in self.folderincludes:
                    try:
                        imapobj.select(foldername, readonly=True)
                    except OfflineImapError as e:
                        # couldn't select this folderinclude, so ignore folder.
                        if e.severity > OfflineImapError.ERROR.FOLDER:
                            raise
                        self.ui.error(e,
                                      exc_info()[2], 'Invalid folderinclude:')
                        continue
                    retval.append(self.getfoldertype()(self.imapserver,
                                                       foldername, self))
            finally:
                self.imapserver.releaseconnection(imapobj)

        if self.foldersort is None:
            # default sorting by case insensitive transposed name
            retval.sort(key=lambda x: str.lower(x.getvisiblename()))
        else:
            # do foldersort in a python3-compatible way
            # http://bytes.com/topic/python/answers/844614-python-3-sorting-comparison-function
            def cmp2key(mycmp):
                """Converts a cmp= function into a key= function
                We need to keep cmp functions for backward compatibility"""
                class K:
                    def __init__(self, obj, *args):
                        self.obj = obj

                    def __cmp__(self, other):
                        return mycmp(self.obj, other.obj)

                return K

            retval.sort(key=cmp2key(self.foldersort))

        self.folders = retval
        return self.folders

    def makefolder(self, foldername):
        """Create a folder on the IMAP server

        This will not update the list cached in :meth:`getfolders`. You
        will need to invoke :meth:`forgetfolders` to force new caching
        when you are done creating folders yourself.

        :param foldername: Full path of the folder to be created."""
        if self.getreference():
            foldername = self.getreference() + self.getsep() + foldername
        if not foldername:  # Create top level folder as folder separator
            foldername = self.getsep()
        self.ui.makefolder(self, foldername)
        if self.account.dryrun:
            return
        imapobj = self.imapserver.acquireconnection()
        try:
            result = imapobj.create(foldername)
            if result[0] != 'OK':
                raise OfflineImapError("Folder '%s'[%s] could not be created. "
                                       "Server responded: %s" % \
                                           (foldername, self, str(result)),
                                       OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)
Exemple #8
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        self.imapserver = imapserver.ConfigedIMAPServer(self)
        self.folders = None
        self.nametrans = lambda foldername: foldername
        self.folderfilter = lambda foldername: 1
        self.folderincludes = []
        self.foldersort = cmp
        localeval = self.localeval
        if self.config.has_option(self.getsection(), 'nametrans'):
            self.nametrans = localeval.eval(self.getconf('nametrans'),
                                            {'re': re})
        if self.config.has_option(self.getsection(), 'folderfilter'):
            self.folderfilter = localeval.eval(self.getconf('folderfilter'),
                                               {'re': re})
        if self.config.has_option(self.getsection(), 'folderincludes'):
            self.folderincludes = localeval.eval(
                self.getconf('folderincludes'), {'re': re})
        if self.config.has_option(self.getsection(), 'foldersort'):
            self.foldersort = localeval.eval(self.getconf('foldersort'),
                                             {'re': re})

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
                                         name="Keep alive " + self.getname(),
                                         args=(keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        return self.getconfint("keepalive", 0)

    def getsep(self):
        return self.imapserver.delim

    def gethost(self):
        host = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
        if host != None:
            return localeval.eval(host)

        host = self.getconf('remotehost')
        if host != None:
            return host

    def getuser(self):
        user = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
        if user != None:
            return localeval.eval(user)

        user = self.getconf('remoteuser')
        if user != None:
            return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError, inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
Exemple #9
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        self.nametrans = lambda foldername: foldername
        self.folderfilter = lambda foldername: 1
        self.folderincludes = []
        self.foldersort = cmp
        localeval = self.localeval
        if self.config.has_option(self.getsection(), 'nametrans'):
            self.nametrans = localeval.eval(self.getconf('nametrans'),
                                            {'re': re})
        if self.config.has_option(self.getsection(), 'folderfilter'):
            self.folderfilter = localeval.eval(self.getconf('folderfilter'),
                                               {'re': re})
        if self.config.has_option(self.getsection(), 'folderincludes'):
            self.folderincludes = localeval.eval(self.getconf('folderincludes'),
                                                 {'re': re})
        if self.config.has_option(self.getsection(), 'foldersort'):
            self.foldersort = localeval.eval(self.getconf('foldersort'),
                                             {'re': re})

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
                                         name = "Keep alive " + self.getname(),
                                         args = (keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29*60
        else:
            return num

    def getsep(self):
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception, e:
                raise OfflineImapError("remotehosteval option for repository "\
                                       "'%s' failed:\n%s" % (self, e),
                                       OfflineImapError.ERROR.REPO)
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError("No remote host for repository "\
                                   "'%s' specified." % self,
                               OfflineImapError.ERROR.REPO)
Exemple #10
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
                                         name="Keep alive " + self.getname(),
                                         args=(keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29 * 60
        else:
            return num

    def getsep(self):
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception, e:
                raise OfflineImapError("remotehosteval option for repository "\
                                       "'%s' failed:\n%s" % (self, e),
                                       OfflineImapError.ERROR.REPO)
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError("No remote host for repository "\
                                   "'%s' specified." % self,
                               OfflineImapError.ERROR.REPO)
Exemple #11
0
    def __init__(self, srcusername, srcpasswd, dstusername, dstpasswd):

        try:
            import fcntl
            hasfcntl = 1
        except:
            hasfcntl = 0

        lockfd = None

        def lock(config, ui):
            if not hasfcntl:
                return
            lockfd = open(self.configuration.getmetadatadir() + "/lock", "w")
            try:
                fcntl.flock(lockfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
            except IOError:
                ui.locked()
                ui.terminate(1)

        self.configuration = CustomConfigParser()

        self.configuration.add_section('general')
        self.configuration.set('general', 'accounts', dstusername)
        self.configuration.add_section('Account ' + dstusername)

        self.configuration.set('Account ' + dstusername, 'localrepository',
                               dstusername + '_local')
        self.configuration.set('Account ' + dstusername, 'remoterepository',
                               dstusername + '_remote')

        self.configuration.add_section('Repository ' + dstusername + '_local')
        self.configuration.add_section('Repository ' + dstusername + '_remote')

        self.configuration.set('Repository ' + dstusername + '_local', 'type',
                               'IMAP')
        self.configuration.set('Repository ' + dstusername + '_local',
                               'remotehost', newserveraddr)
        self.configuration.set('Repository ' + dstusername + '_local',
                               'remoteuser', dstusername)
        self.configuration.set('Repository ' + dstusername + '_local',
                               'remotepass', dstpasswd)

        self.configuration.set('Repository ' + dstusername + '_remote', 'type',
                               'IMAP')
        self.configuration.set('Repository ' + dstusername + '_remote',
                               'remotehost', server)
        self.configuration.set('Repository ' + dstusername + '_remote',
                               'remoteuser', srcusername)
        self.configuration.set('Repository ' + dstusername + '_remote',
                               'remotepass', srcpasswd)

        self.monothread = 0
        # Setup a interface

        ui = offlineimap.ui.detector.findUI(self.configuration, 'TTY.TTYUI')
        UIBase.setglobalui(ui)
        #		ui.add_debug('imap')
        #		ui.add_debug('thread')
        #		imaplib.Debug = 5
        #		threading._VERBOSE = 1

        lock(self.configuration, ui)

        def sigterm_handler(signum, frame):
            # die immediately
            ui.terminate(errormsg="terminating...")

        signal.signal(signal.SIGTERM, sigterm_handler)

        try:
            pidfd = open(config.getmetadatadir() + "/pid", "w")
            pidfd.write(str(os.getpid()) + "\n")
            pidfd.close()
        except:
            pass

        try:
            activeaccounts = self.configuration.get("general", "accounts")
            activeaccounts = activeaccounts.replace(" ", "")
            activeaccounts = activeaccounts.split(",")
            allaccounts = accounts.AccountHashGenerator(self.configuration)

            if self.monothread:
                threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
            else:
                threadutil.initInstanceLimit(
                    "ACCOUNTLIMIT",
                    self.configuration.getdefaultint("general",
                                                     "maxsyncaccounts", 1))

            for reposname in self.configuration.getsectionlist('Repository'):
                for instancename in [
                        "FOLDER_" + reposname, "MSGCOPY_" + reposname
                ]:
                    if self.monothread:
                        threadutil.initInstanceLimit(instancename, 1)
                    else:
                        threadutil.initInstanceLimit(
                            instancename,
                            self.configuration.getdefaultint(
                                'Repository ' + reposname, "maxconnections",
                                1))

            syncaccounts = []
            for account in activeaccounts:
                if account not in syncaccounts:
                    syncaccounts.append(account)

            siglisteners = []

            def sig_handler(signum, frame):
                if signum == signal.SIGUSR1:
                    # tell each account to do a full sync asap
                    signum = (1, )
                elif signum == signal.SIGHUP:
                    # tell each account to die asap
                    signum = (2, )
                elif signum == signal.SIGUSR2:
                    # tell each account to do a full sync asap, then die
                    signum = (1, 2)
                # one listener per account thread (up to maxsyncaccounts)
                for listener in siglisteners:
                    for sig in signum:
                        listener.put_nowait(sig)

            signal.signal(signal.SIGHUP, sig_handler)
            signal.signal(signal.SIGUSR1, sig_handler)
            signal.signal(signal.SIGUSR2, sig_handler)

            threadutil.initexitnotify()
            t = ExitNotifyThread(target=syncmaster.syncitall,
                                 name='Sync Runner',
                                 kwargs={
                                     'accounts': syncaccounts,
                                     'config': self.configuration,
                                     'siglisteners': siglisteners
                                 })
            t.setDaemon(1)
            t.start()

        except:
            ui.mainException()

        try:
            threadutil.exitnotifymonitorloop(threadutil.threadexited)
        except SystemExit:
            raise
        except:
            ui.mainException()  # Also expected to terminate.

        rmtree(self.configuration.getmetadatadir())
Exemple #12
0
def startup(versionno):
    assert versionno == version.versionstr, "Revision of main program (%s) does not match that of library (%s).  Please double-check your PYTHONPATH and installation locations." % (
        versionno, version.versionstr)
    options = {}
    if '--help' in sys.argv[1:]:
        sys.stdout.write(version.getcmdhelp() + "\n")
        sys.exit(0)

    for optlist in getopt(sys.argv[1:], 'P:1oqa:c:d:l:u:hk:f:')[0]:
        options[optlist[0]] = optlist[1]

    if options.has_key('-h'):
        sys.stdout.write(version.getcmdhelp())
        sys.stdout.write("\n")
        sys.exit(0)
    configfilename = os.path.expanduser("~/.offlineimaprc")
    if options.has_key('-c'):
        configfilename = options['-c']
    if options.has_key('-P'):
        if not options.has_key('-1'):
            sys.stderr.write("FATAL: profile mode REQUIRES -1\n")
            sys.exit(100)
        profiledir = options['-P']
        os.mkdir(profiledir)
        threadutil.setprofiledir(profiledir)
        sys.stderr.write(
            "WARNING: profile mode engaged;\nPotentially large data will be created in "
            + profiledir + "\n")

    config = CustomConfigParser()
    if not os.path.exists(configfilename):
        sys.stderr.write(" *** Config file %s does not exist; aborting!\n" %
                         configfilename)
        sys.exit(1)

    config.read(configfilename)

    # override config values with option '-k'
    for option in options.keys():
        if option == '-k':
            (key, value) = options['-k'].split('=', 1)
            if ':' in key:
                (secname, key) = key.split(':', 1)
                section = secname.replace("_", " ")
            else:
                section = "general"
            config.set(section, key, value)

    ui = offlineimap.ui.detector.findUI(config, options.get('-u'))
    UIBase.setglobalui(ui)

    if options.has_key('-l'):
        ui.setlogfd(open(options['-l'], 'wt'))

    ui.init_banner()

    if options.has_key('-d'):
        for debugtype in options['-d'].split(','):
            ui.add_debug(debugtype.strip())
            if debugtype == 'imap':
                imaplib.Debug = 5
            if debugtype == 'thread':
                threading._VERBOSE = 1

    if options.has_key('-o'):
        # FIXME: maybe need a better
        for section in accounts.getaccountlist(config):
            config.remove_option('Account ' + section, "autorefresh")

    if options.has_key('-q'):
        for section in accounts.getaccountlist(config):
            config.set('Account ' + section, "quick", '-1')

    if options.has_key('-f'):
        foldernames = options['-f'].replace(" ", "").split(",")
        folderfilter = "lambda f: f in %s" % foldernames
        folderincludes = "[]"
        for accountname in accounts.getaccountlist(config):
            account_section = 'Account ' + accountname
            remote_repo_section = 'Repository ' + \
                                  config.get(account_section, 'remoterepository')
            local_repo_section = 'Repository ' + \
                                 config.get(account_section, 'localrepository')
            for section in [remote_repo_section, local_repo_section]:
                config.set(section, "folderfilter", folderfilter)
                config.set(section, "folderincludes", folderincludes)

    lock(config, ui)

    def sigterm_handler(signum, frame):
        # die immediately
        ui.terminate(errormsg="terminating...")

    signal.signal(signal.SIGTERM, sigterm_handler)

    try:
        pidfd = open(config.getmetadatadir() + "/pid", "w")
        pidfd.write(str(os.getpid()) + "\n")
        pidfd.close()
    except:
        pass

    try:
        if options.has_key('-l'):
            sys.stderr = ui.logfile

        socktimeout = config.getdefaultint("general", "socktimeout", 0)
        if socktimeout > 0:
            socket.setdefaulttimeout(socktimeout)

        activeaccounts = config.get("general", "accounts")
        if options.has_key('-a'):
            activeaccounts = options['-a']
        activeaccounts = activeaccounts.replace(" ", "")
        activeaccounts = activeaccounts.split(",")
        allaccounts = accounts.AccountHashGenerator(config)

        syncaccounts = []
        for account in activeaccounts:
            if account not in allaccounts:
                if len(allaccounts) == 0:
                    errormsg = 'The account "%s" does not exist because no accounts are defined!' % account
                else:
                    errormsg = 'The account "%s" does not exist.  Valid accounts are:' % account
                    for name in allaccounts.keys():
                        errormsg += '\n%s' % name
                ui.terminate(1,
                             errortitle='Unknown Account "%s"' % account,
                             errormsg=errormsg)
            if account not in syncaccounts:
                syncaccounts.append(account)

        server = None
        remoterepos = None
        localrepos = None

        if options.has_key('-1'):
            threadutil.initInstanceLimit("ACCOUNTLIMIT", 1)
        else:
            threadutil.initInstanceLimit(
                "ACCOUNTLIMIT",
                config.getdefaultint("general", "maxsyncaccounts", 1))

        for reposname in config.getsectionlist('Repository'):
            for instancename in [
                    "FOLDER_" + reposname, "MSGCOPY_" + reposname
            ]:
                if options.has_key('-1'):
                    threadutil.initInstanceLimit(instancename, 1)
                else:
                    threadutil.initInstanceLimit(
                        instancename,
                        config.getdefaultint('Repository ' + reposname,
                                             "maxconnections", 1))
        siglisteners = []

        def sig_handler(signum, frame):
            if signum == signal.SIGUSR1:
                # tell each account to do a full sync asap
                signum = (1, )
            elif signum == signal.SIGHUP:
                # tell each account to die asap
                signum = (2, )
            elif signum == signal.SIGUSR2:
                # tell each account to do a full sync asap, then die
                signum = (1, 2)
            # one listener per account thread (up to maxsyncaccounts)
            for listener in siglisteners:
                for sig in signum:
                    listener.put_nowait(sig)

        signal.signal(signal.SIGHUP, sig_handler)
        signal.signal(signal.SIGUSR1, sig_handler)
        signal.signal(signal.SIGUSR2, sig_handler)

        threadutil.initexitnotify()
        t = ExitNotifyThread(target=syncmaster.syncitall,
                             name='Sync Runner',
                             kwargs={
                                 'accounts': syncaccounts,
                                 'config': config,
                                 'siglisteners': siglisteners
                             })
        t.setDaemon(1)
        t.start()
    except:
        ui.mainException()

    try:
        threadutil.exitnotifymonitorloop(threadutil.threadexited)
    except SystemExit:
        raise
    except:
        ui.mainException()  # Also expected to terminate.
Exemple #13
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""

        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self._oauth2_request_url = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        self.copy_ignore_eval = None

        # Only set the newmail_hook in an IMAP repository.
        if self.config.has_option(self.getsection(), 'newmail_hook'):
            self.newmail_hook = self.localeval.eval(
                self.getconf('newmail_hook'))

        if self.getconf('sep', None):
            self.ui.info("The 'sep' setting is being ignored for IMAP "
                         "repository '%s' (it's autodetected)"% self)

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
                                         name = "Keep alive " + self.getname(),
                                         args = (keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def get_copy_ignore_UIDs(self, foldername):
        """Return a list of UIDs to not copy for this foldername."""

        if self.copy_ignore_eval is None:
            if self.config.has_option(self.getsection(),
                                     'copy_ignore_eval'):
                self.copy_ignore_eval = self.localeval.eval(
                        self.getconf('copy_ignore_eval'))
            else:
                self.copy_ignore_eval = lambda x: None

        return self.copy_ignore_eval(foldername)

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", False)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29*60
        else:
            return num

    def getsep(self):
        """Return the folder separator for the IMAP repository

        This requires that self.imapserver has been initialized with an
        acquireconnection() or it will still be `None`"""
        assert self.imapserver.delim != None, "'%s' " \
            "repository called getsep() before the folder separator was " \
            "queried from the server"% self
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception as e:
                six.reraise(OfflineImapError,
                            OfflineImapError(
                                "remotehosteval option for repository "
                                "'%s' failed:\n%s"% (self, e),
                                OfflineImapError.ERROR.REPO),
                            exc_info()[2])
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError("No remote host for repository "
            "'%s' specified."% self, OfflineImapError.ERROR.REPO)

    def get_remote_identity(self):
        """Remote identity is used for certain SASL mechanisms
        (currently -- PLAIN) to inform server about the ID
        we want to authorize as instead of our login name."""

        identity = self.getconf('remote_identity', default=None)
        if identity != None:
            identity = identity.encode('UTF-8')
        return identity

    def get_auth_mechanisms(self):
        supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
        # Mechanisms are ranged from the strongest to the
        # weakest ones.
        # TODO: we need DIGEST-MD5, it must come before CRAM-MD5
        # TODO: due to the chosen-plaintext resistance.
        default = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]

        mechs = self.getconflist('auth_mechanisms', r',\s*',
          default)

        for m in mechs:
            if m not in supported:
                raise OfflineImapError("Repository %s: "% self + \
                  "unknown authentication mechanism '%s'"% m,
                  OfflineImapError.ERROR.REPO)

        self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
        return mechs


    def getuser(self):
        user = None
        localeval = self.localeval

        if self.config.has_option(self.getsection(), 'remoteusereval'):
            user = self.getconf('remoteusereval')
            if user != None:
                return localeval.eval(user).encode('UTF-8')

        if self.config.has_option(self.getsection(), 'remoteuser'):
            # Assume the configuration file to be UTF-8 encoded so we must not
            # encode this string again.
            user = self.getconf('remoteuser')
            if user != None:
                return user

        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                return netrcentry[0]

        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                return netrcentry[0]


    def getport(self):
        port = None

        if self.config.has_option(self.getsection(), 'remoteporteval'):
            port = self.getconf('remoteporteval')
        if port != None:
            return self.localeval.eval(port)

        return self.getconfint('remoteport', None)

    def getipv6(self):
        return self.getconfboolean('ipv6', False)

    def getssl(self):
        return self.getconfboolean('ssl', True)

    def getsslclientcert(self):
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        return self.getconf_xform('sslclientcert', xforms, None)

    def getsslclientkey(self):
        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        return self.getconf_xform('sslclientkey', xforms, None)

    def getsslcacertfile(self):
        """Determines CA bundle.

        Returns path to the CA bundle.  It is either explicitely specified
        or requested via "OS-DEFAULT" value (and we will search known
        locations for the current OS and distribution).

        If search via "OS-DEFAULT" route yields nothing, we will throw an
        exception to make our callers distinguish between not specified
        value and non-existent default CA bundle.

        It is also an error to specify non-existent file via configuration:
        it will error out later, but, perhaps, with less verbose explanation,
        so we will also throw an exception.  It is consistent with
        the above behaviour, so any explicitely-requested configuration
        that doesn't result in an existing file will give an exception.
        """

        xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
        cacertfile = self.getconf_xform('sslcacertfile', xforms, None)
        # Can't use above cacertfile because of abspath.
        if self.getconf('sslcacertfile', None) == "OS-DEFAULT":
            cacertfile = get_os_sslcertfile()
            if cacertfile == None:
                searchpath = get_os_sslcertfile_searchpath()
                if searchpath:
                    reason = "Default CA bundle was requested, "\
                             "but no existing locations available.  "\
                             "Tried %s." % (", ".join(searchpath))
                else:
                    reason = "Default CA bundle was requested, "\
                             "but OfflineIMAP doesn't know any for your "\
                             "current operating system."
                raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
        if cacertfile is None:
            return None
        if not os.path.isfile(cacertfile):
            reason = "CA certfile for repository '%s' couldn't be found.  "\
                     "No such file: '%s'" % (self.name, cacertfile)
            raise OfflineImapError(reason, OfflineImapError.ERROR.REPO)
        return cacertfile

    def gettlslevel(self):
        return self.getconf('tls_level', 'tls_compat')

    def getsslversion(self):
        return self.getconf('ssl_version', None)

    def getstarttls(self):
        return self.getconfboolean('starttls', True)

    def get_ssl_fingerprint(self):
        """Return array of possible certificate fingerprints.

        Configuration item cert_fingerprint can contain multiple
        comma-separated fingerprints in hex form."""

        value = self.getconf('cert_fingerprint', "")
        return [f.strip().lower() for f in value.split(',') if f]

    def getoauth2_request_url(self):
        if self._oauth2_request_url:  # Use cached value if possible.
            return self._oauth2_request_url

        self.oauth2_request_url = self.getconf('oauth2_request_url', None)
        return self._oauth2_request_url

    def getoauth2_refresh_token(self):
        return self.getconf('oauth2_refresh_token', None)

    def getoauth2_access_token(self):
        return self.getconf('oauth2_access_token', None)

    def getoauth2_client_id(self):
        return self.getconf('oauth2_client_id', None)

    def getoauth2_client_secret(self):
        return self.getconf('oauth2_client_secret', None)

    def getpreauthtunnel(self):
        return self.getconf('preauthtunnel', None)

    def gettransporttunnel(self):
        return self.getconf('transporttunnel', None)

    def getreference(self):
        return self.getconf('reference', '')

    def getdecodefoldernames(self):
        return self.getconfboolean('decodefoldernames', False)

    def getidlefolders(self):
        localeval = self.localeval
        return localeval.eval(self.getconf('idlefolders', '[]'))

    def getmaxconnections(self):
        num1 = len(self.getidlefolders())
        num2 = self.getconfint('maxconnections', 1)
        return max(num1, num2)

    def getexpunge(self):
        return self.getconfboolean('expunge', True)

    def getpassword(self):
        """Return the IMAP password for this repository.

        It tries to get passwords in the following order:

        1. evaluate Repository 'remotepasseval'
        2. read password from Repository 'remotepass'
        3. read password from file specified in Repository 'remotepassfile'
        4. read password from ~/.netrc
        5. read password from /etc/netrc

        On success we return the password.
        If all strategies fail we return None."""

        # 1. evaluate Repository 'remotepasseval'
        passwd = self.getconf('remotepasseval', None)
        if passwd != None:
            return self.localeval.eval(passwd).encode('UTF-8')
        # 2. read password from Repository 'remotepass'
        password = self.getconf('remotepass', None)
        if password != None:
            # Assume the configuration file to be UTF-8 encoded so we must not
            # encode this string again.
            return password
        # 3. read password from file specified in Repository 'remotepassfile'
        passfile = self.getconf('remotepassfile', None)
        if passfile != None:
            fd = codecs.open(os.path.expanduser(passfile), 'r', 'UTF-8')
            password = fd.readline().strip()
            fd.close()
            return password.encode('UTF-8')
        # 4. read password from ~/.netrc
        try:
            netrcentry = netrc.netrc().authenticators(self.gethost())
        except IOError as inst:
            if inst.errno != errno.ENOENT:
                raise
        else:
            if netrcentry:
                user = self.getuser()
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # 5. read password from /etc/netrc
        try:
            netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost())
        except IOError as inst:
            if inst.errno not in (errno.ENOENT, errno.EACCES):
                raise
        else:
            if netrcentry:
                user = self.getuser()
                if user == None or user == netrcentry[0]:
                    return netrcentry[2]
        # no strategy yielded a password!
        return None

    def getfolder(self, foldername):
        """Return instance of OfflineIMAP representative folder."""

        return self.getfoldertype()(self.imapserver, foldername, self)

    def getfoldertype(self):
        return folder.IMAP.IMAPFolder

    def connect(self):
        imapobj = self.imapserver.acquireconnection()
        self.imapserver.releaseconnection(imapobj)

    def forgetfolders(self):
        self.folders = None

    def getfolders(self):
        """Return a list of instances of OfflineIMAP representative folder."""

        if self.folders != None:
            return self.folders
        retval = []
        imapobj = self.imapserver.acquireconnection()
        # check whether to list all folders, or subscribed only
        listfunction = imapobj.list
        if self.getconfboolean('subscribedonly', False):
            listfunction = imapobj.lsub
        try:
            listresult = listfunction(directory = self.imapserver.reference)[1]
        finally:
            self.imapserver.releaseconnection(imapobj)
        for s in listresult:
            if s == None or \
                   (isinstance(s, str) and s == ''):
                # Bug in imaplib: empty strings in results from
                # literals. TODO: still relevant?
                continue
            flags, delim, name = imaputil.imapsplit(s)
            flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
            if '\\noselect' in flaglist:
                continue
            foldername = imaputil.dequote(name)
            retval.append(self.getfoldertype()(self.imapserver, foldername,
                                               self))
        # Add all folderincludes
        if len(self.folderincludes):
            imapobj = self.imapserver.acquireconnection()
            try:
                for foldername in self.folderincludes:
                    try:
                        imapobj.select(foldername, readonly = True)
                    except OfflineImapError as e:
                        # couldn't select this folderinclude, so ignore folder.
                        if e.severity > OfflineImapError.ERROR.FOLDER:
                            raise
                        self.ui.error(e, exc_info()[2],
                                      'Invalid folderinclude:')
                        continue
                    retval.append(self.getfoldertype()(
                        self.imapserver, foldername, self))
            finally:
                self.imapserver.releaseconnection(imapobj)

        if self.foldersort is None:
            # default sorting by case insensitive transposed name
            retval.sort(key=lambda x: str.lower(x.getvisiblename()))
        else:
            # do foldersort in a python3-compatible way
            # http://bytes.com/topic/python/answers/844614-python-3-sorting-comparison-function
            def cmp2key(mycmp):
                """Converts a cmp= function into a key= function
                We need to keep cmp functions for backward compatibility"""
                class K:
                    def __init__(self, obj, *args):
                        self.obj = obj
                    def __cmp__(self, other):
                        return mycmp(self.obj.getvisiblename(), other.obj.getvisiblename())
                return K
            retval.sort(key=cmp2key(self.foldersort))

        self.folders = retval
        return self.folders

    def deletefolder(self, foldername):
        """Delete a folder on the IMAP server."""

        imapobj = self.imapserver.acquireconnection()
        try:
            result = imapobj.delete(foldername)
            if result[0] != 'OK':
                raise OfflineImapError("Folder '%s'[%s] could not be deleted. "
                    "Server responded: %s"% (foldername, self, str(result)),
                    OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)

    def makefolder(self, foldername):
        """Create a folder on the IMAP server

        This will not update the list cached in :meth:`getfolders`. You
        will need to invoke :meth:`forgetfolders` to force new caching
        when you are done creating folders yourself.

        :param foldername: Full path of the folder to be created."""

        if foldername is '':
            return

        if self.getreference():
            foldername = self.getreference() + self.getsep() + foldername
        if not foldername: # Create top level folder as folder separator.
            foldername = self.getsep()
        self.ui.makefolder(self, foldername)
        if self.account.dryrun:
            return
        imapobj = self.imapserver.acquireconnection()
        try:
            result = imapobj.create(foldername)
            if result[0] != 'OK':
                raise OfflineImapError("Folder '%s'[%s] could not be created. "
                    "Server responded: %s"% (foldername, self, str(result)),
                    OfflineImapError.ERROR.FOLDER)
        finally:
            self.imapserver.releaseconnection(imapobj)
Exemple #14
0
class IMAPRepository(BaseRepository):
    def __init__(self, reposname, account):
        """Initialize an IMAPRepository object."""
        BaseRepository.__init__(self, reposname, account)
        # self.ui is being set by the BaseRepository
        self._host = None
        self.imapserver = imapserver.IMAPServer(self)
        self.folders = None
        if self.getconf('sep', None):
            self.ui.info("The 'sep' setting is being ignored for IMAP "
                         "repository '%s' (it's autodetected)" % self)

    def startkeepalive(self):
        keepalivetime = self.getkeepalive()
        if not keepalivetime: return
        self.kaevent = Event()
        self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
                                         name = "Keep alive " + self.getname(),
                                         args = (keepalivetime, self.kaevent))
        self.kathread.setDaemon(1)
        self.kathread.start()

    def stopkeepalive(self):
        if not hasattr(self, 'kaevent'):
            # Keepalive is not active.
            return

        self.kaevent.set()
        del self.kathread
        del self.kaevent

    def holdordropconnections(self):
        if not self.getholdconnectionopen():
            self.dropconnections()

    def dropconnections(self):
        self.imapserver.close()

    def getholdconnectionopen(self):
        if self.getidlefolders():
            return 1
        return self.getconfboolean("holdconnectionopen", 0)

    def getkeepalive(self):
        num = self.getconfint("keepalive", 0)
        if num == 0 and self.getidlefolders():
            return 29*60
        else:
            return num

    def getsep(self):
        """Return the folder separator for the IMAP repository

        This requires that self.imapserver has been initialized with an
        acquireconnection() or it will still be `None`"""
        assert self.imapserver.delim != None, "'%s' " \
            "repository called getsep() before the folder separator was " \
            "queried from the server" % self
        return self.imapserver.delim

    def gethost(self):
        """Return the configured hostname to connect to

        :returns: hostname as string or throws Exception"""
        if self._host:  # use cached value if possible
            return self._host

        # 1) check for remotehosteval setting
        if self.config.has_option(self.getsection(), 'remotehosteval'):
            host = self.getconf('remotehosteval')
            try:
                host = self.localeval.eval(host)
            except Exception as e:
                raise OfflineImapError("remotehosteval option for repository "\
                                       "'%s' failed:\n%s" % (self, e),
                                       OfflineImapError.ERROR.REPO)
            if host:
                self._host = host
                return self._host
        # 2) check for plain remotehost setting
        host = self.getconf('remotehost', None)
        if host != None:
            self._host = host
            return self._host

        # no success
        raise OfflineImapError("No remote host for repository "\
                                   "'%s' specified." % self,
                               OfflineImapError.ERROR.REPO)


    def get_xoauth_access_token(self):
        return self.getconf('xoauth_access_token')