demandimport breaks OTR plugin with recent pycrypto versions
Bug description
I've recently updated my gajim repositories to these revisions:
- gajim: hg changeset 14717
- gajim-plugins: hg changeset 674
- pycrypto: git commit f9a0fc77e1c8847c1a17503e5a1b86a409b8cb2d
After upgrading, the OTR plugin fails to work. Each time an OTR session would be initiated, this error occurred:
09:52:38 (E) gajim.c.ged Error while running an even handler: \<bound method OtrPlugin.handle_incoming_msg of \<gotr.otrmodule.OtrPlugin object at 0x37ed590>>
Traceback (most recent call last):
File "/home/leon/gajim/src/common/ged.py", line 93, in raise_event
if handler(*args, **kwargs):
File "../plugins/gotr/otrmodule.py", line 542, in handle_incoming_msg
appdata={'session':event.session})
File "../plugins/gotr/potr/context.py", line 190, in receiveMessage
self.handleQuery(message, appdata=appdata)
File "../plugins/gotr/potr/context.py", line 379, in handleQuery
self.authStartV2(appdata=appdata)
File "../plugins/gotr/potr/context.py", line 387, in authStartV2
self.crypto.startAKE(appdata=appdata)
File "../plugins/gotr/potr/crypt.py", line 258, in startAKE
self.ake = AuthKeyExchange(self.ctx.user.getPrivkey(), self.goEncrypted)
File "../plugins/gotr/potr/crypt.py", line 341, in __init__
self.dh = DH()
File "../plugins/gotr/potr/crypt.py", line 57, in __init__
self.priv = bytes_to_long(RNG.read(40))
File "/usr/lib64/python2.7/site-packages/Crypto/Random/_UserFriendlyRNG.py", line 187, in read
return self._singleton.read(bytes)
File "/usr/lib64/python2.7/site-packages/Crypto/Random/_UserFriendlyRNG.py", line 163, in read
return _UserFriendlyRNG.read(self, bytes)
File "/usr/lib64/python2.7/site-packages/Crypto/Random/_UserFriendlyRNG.py", line 117, in read
retval = self._fa.random_data(N)
File "/usr/lib64/python2.7/site-packages/Crypto/Random/Fortuna/FortunaAccumulator.py", line 116, in random_data
current_time = maybe_monotonic_time()
File "/home/leon/gajim/src/common/demandimport.py", line 82, in __call__
raise TypeError("%s object is not callable" % repr(self))
TypeError: \<unloaded module 'monotonic'> object is not callable
Instead of an OTR session being initiated, only the “//[…] has requested an Off-the-Record private conversation. However, you do not have a plugin to support that.See http://otr.cypherpunks.ca/ for more information.//” message would be displayed in the chat window.
After short investigation, I found that the problem was introduced with commit 22d7760ae7346d80a6c54b1549e9a1d560c239bc
in the pycrypto git repository. After this commit, pycrypto uses this (very pythonic) import pattern to import the time module:
leon@hactar ~/gajim/pycrypto/lib/Crypto % cat Util/_time.py
[…]
try:
from time import monotonic as maybe_monotonic_time
except ImportError:
from time import time as maybe_monotonic_time
This would normally work just fine, however, gajim uses demandimport
which hides the ImportError
and thus prevents this common import pattern from working, as can be easily reproduced:
>>> from time import monotonic as maybe_monotonic_time
Traceback (most recent call last):
File "\<stdin>", line 1, in \<module>
ImportError: cannot import name monotonic
>>> import demandimport
>>> demandimport.enable()
>>> from time import monotonic as maybe_monotonic_time
>>> maybe_monotonic_time()
Traceback (most recent call last):
File "\<stdin>", line 1, in \<module>
File "demandimport.py", line 82, in __call__
raise TypeError("%s object is not callable" % repr(self))
TypeError: \<unloaded module 'monotonic'> object is not callable
I'm not sure what the appropriate fix is here. I don't think pycrypto is at fault, as it's only using a very common python idiom. Although the error occurs in the OTR plugin, it cannot really be blamed either; in the end, this seems to be a fundamental flaw of the demandimport
mechanism. The correct fix would obviously be to patch that module to support the try: import…; except ImportError: …
idiom, but I'm not sure that's possible.
A possible workarounds would be to disable demandimport
before running the OTR plugin, or before the plugin runs pycrypto.