Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • gajim/gajim-plugins
  • lovetox/gajim-plugins
  • ag/gajim-plugins
  • FlorianMuenchbach/gajim-plugins
  • rom1dep/gajim-plugins
  • pitchum/gajim-plugins
  • wurstsalat/gajim-plugins
  • Dicson/gajim-plugins
  • andre/gajim-plugins
  • link2xt/gajim-plugins
  • marmistrz/gajim-plugins
  • Jens/gajim-plugins
  • muelli/gajim-plugins
  • asterix/gajim-plugins
  • orhideous/gajim-plugins
  • ngvelprz/gajim-plugins
  • appleorange1/gajim-plugins
  • Martin/gajim-plugins
  • maltel/gajim-plugins
  • Seve/gajim-plugins
  • evert-mouw/gajim-plugins
  • Yuki/gajim-plugins
  • mxre/gajim-plugins
  • ValdikSS/gajim-plugins
  • SaltyBones/gajim-plugins
  • comradekingu/gajim-plugins
  • ritzmann/gajim-plugins
  • genofire/gajim-plugins
  • jjrh/gajim-plugins
  • yarmak/gajim-plugins
  • PapaTutuWawa/gajim-plugins
  • weblate/gajim-plugins
  • XutaxKamay/gajim-plugins
  • nekk/gajim-plugins
  • principis/gajim-plugins
  • cbix/gajim-plugins
  • bodqhrohro/gajim-plugins
  • airtower-luna/gajim-plugins
  • toms/gajim-plugins
  • mesonium/gajim-plugins
  • lissine/gajim-plugins
  • anviar/gajim-plugins
42 results
Show changes
Showing
with 672 additions and 307 deletions
File added
[info]
name: Emoticons pack
short_name: emoticons_pack
version: 0.1.5
description: Install, update and view detailed legend of emoticons
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/
min_gajim_version: 0.16
max_gajim_version: 0.16.9
import sqlite3
from common import gajim
import sys
import os
'''
TODO:
1-) Modify the database class to use models instead of method arguments
2-) Normalize the database. Don't save dirs and files in the same table
'''
class FilesharingDatabase:
def __init__(self, plugin):
self.plugin = plugin
path_l = os.path.split(plugin.config.FILE_PATH)
def __init__(self, FILE_PATH=None):
if not FILE_PATH:
return
path_l = os.path.split(FILE_PATH)
path = os.path.join(path_l[0], 'shared_files.db')
db_exist = os.path.exists(path)
self.conn = sqlite3.connect(path)
......@@ -31,13 +38,13 @@ class FilesharingDatabase:
self.conn.commit()
c.close()
def get_toplevel_files(self, account, requester):
def get_toplevel_dirs(self, account, requester):
c = self.conn.cursor()
data = (account, requester)
c.execute("SELECT relative_path, hash_sha1, size, description, " +
"mod_date, is_dir FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?" +
" AND relative_path NOT LIKE '%/%'", data)
" AND is_dir=1 AND relative_path NOT LIKE '%/%'", data)
result = c.fetchall()
c.close()
return result
......@@ -88,16 +95,15 @@ class FilesharingDatabase:
else:
data = (account, requester, name)
sql = "SELECT relative_path, hash_sha1, size, description, " + \
"mod_date, file_path FROM (files JOIN permissions ON" + \
"mod_date, file_path, is_dir FROM (files JOIN permissions ON" + \
" files.fid=permissions.fid) WHERE account=? AND requester=?" +\
" AND relative_path=?"
c.execute(sql, data)
result = c.fetchall()
c.close()
if result == []:
return None
else:
if result != []:
return result[0]
return result
def get_files_name(self, account, requester):
result = self.get_files(account, requester)
......@@ -115,7 +121,8 @@ class FilesharingDatabase:
>>> _delete_file(1)
"""
self._check_duplicate(account, requester, file_)
requester = gajim.get_jid_without_resource(requester)
if requester.find('/') != -1:
raise Exception('The requester must be given without a resource attached')
c = self.conn.cursor()
c.execute("INSERT INTO files (file_path, " +
"relative_path, hash_sha1, size, description, mod_date, " +
......@@ -138,7 +145,7 @@ class FilesharingDatabase:
data = (account, requester, file_[2])
c.execute("SELECT * FROM (files JOIN permissions ON" +
" files.fid=permissions.fid) WHERE account=? AND requester=?" +
" AND hash_sha1=?)", data)
" AND hash_sha1=?", data)
result.extend(c.fetchall())
if len(result) > 0:
raise Exception('Duplicated entry')
......
......@@ -14,7 +14,7 @@ from fileshare_window import FileShareWindow
import fshare_protocol
from common import ged
from common import caps_cache
from common import xmpp
from common import nbxmpp
from plugins.gui import GajimPluginConfigDialog
......@@ -26,17 +26,15 @@ class FileSharePlugin(GajimPlugin):
@log_calls('FileSharePlugin')
def init(self):
self.activated = False
self.description = _('This plugin allows you to share folders'
' with a peer using jingle file transfer.')
self.config_dialog = FileSharePluginConfigDialog(self)
home_path = os.path.expanduser('~/')
self.config_default_values = {'incoming_dir': (home_path, '')}
self.database = database.FilesharingDatabase(self)
self.database = database.FilesharingDatabase(self.config.FILE_PATH)
# Create one protocol handler per account
accounts = gajim.contacts.get_accounts()
for account in gajim.contacts.get_accounts():
FileSharePlugin.prohandler[account] = \
fshare_protocol.Protocol(account, self)
fshare_protocol.ProtocolDispatcher(account, self)
self.events_handlers = {
'raw-iq-received': (ged.CORE, self._nec_raw_iq)
}
......@@ -81,12 +79,13 @@ class FileSharePlugin(GajimPlugin):
gajim.connections[a].status)
def _nec_raw_iq(self, obj):
if obj.stanza.getTag('match',
if obj.stanza.getTag('query',
namespace=fshare_protocol.NS_FILE_SHARING) and self.activated:
account = obj.conn.name
pro = FileSharePlugin.prohandler[account]
pro.handler(obj.stanza)
raise xmpp.NodeProcessed
peerjid = obj.stanza.getFrom()
pro.handler(obj.stanza, str(peerjid))
raise nbxmpp.NodeProcessed
def __get_contact_menu(self, contact, account):
raise NotImplementedError
......@@ -176,7 +175,7 @@ class FileSharePluginConfigDialog(GajimPluginConfigDialog):
def on_run(self):
widget = self.xml.get_object('dl_folder')
widget.set_text(str(self.plugin.config['incoming_dir']))
def on_hide(self, widget):
widget = self.xml.get_object('dl_folder')
self.plugin.config['incoming_dir'] = widget.get_text()
from common import xmpp
from common import helpers
from common import gajim
from common import XMPPDispatcher
from common.xmpp import Hashes
import nbxmpp
from nbxmpp import Hashes
try:
from common import helpers
from common import gajim
except ImportError:
print "Import Error: Ignore if we are testing"
# Namespace for file sharing
NS_FILE_SHARING = 'http://gajim.org/protocol/filesharing'
NS_FILE_SHARING = 'urn:xmpp:fis:0'
class Protocol():
'''
Creates and extracts information from stanzas
'''
def __init__(self, ourjid):
# set our jid with resource
self.ourjid = ourjid
def convert_dbformat(self, records):
# Converts db output format to the one expected by the Protocol methods
formatted = []
for record in records:
r = {'name' : record[0]}
if record[-1] == 0:
r['type'] = 'file'
if record[1] != None or record[1] != '':
r['hash'] = record[1]
if record[2] != None or record[2] != '':
r['size'] = record[2]
if record[3] != None or record[3] != '':
r['desc'] = record[3]
if record[4] != None or record[4] != '':
r['date'] = record[4]
else:
r['type'] = 'directory'
formatted.append(r)
return formatted
def request(self, contact, path=None):
iq = nbxmpp.Iq(typ='get', to=contact, frm=self.ourjid)
query = iq.setQuery()
query.setNamespace(NS_FILE_SHARING)
if path:
query.setAttr('node', path)
return iq
def buildFileNode(self, file_info):
node = nbxmpp.Node(tag='file')
node.setNamespace(nbxmpp.NS_JINGLE_FILE_TRANSFER)
if not 'name' in file_info:
raise Exception("Child name is required.")
node.addChild(name='name').setData(file_info['name'])
if 'date' in file_info:
node.addChild(name='date').setData(file_info['date'])
if 'desc' in file_info:
node.addChild(name='desc').setData(file_info['desc'])
if 'size' in file_info:
node.addChild(name='size').setData(file_info['size'])
if 'hash' in file_info:
h = Hashes()
h.addHash(file_info['hash'], 'sha-1')
node.addChild(node=h)
return node
def offer(self, id_, contact, node, items):
iq = nbxmpp.Iq(typ='result', to=contact, frm=self.ourjid,
attrs={'id': id_})
query = iq.setQuery()
query.setNamespace(NS_FILE_SHARING)
if node:
query.setAttr('node', node)
for item in items:
if item['type'] == 'file':
fn = self.buildFileNode(item)
query.addChild(node=fn)
elif item['type'] == 'directory':
query.addChild(name='directory', attrs={'name': item['name']})
else:
raise Exception("Unexpected Type")
return iq
class ProtocolDispatcher(Protocol):
'''
Sends and receives stanzas
'''
def __init__(self, account, plugin):
self.account = account
self.plugin = plugin
self.conn = gajim.connections[self.account]
# get our jid with resource
self.ourjid = gajim.get_jid_from_account(self.account)
ourjid = gajim.get_jid_from_account(self.account)
Protocol.__init__(self, ourjid)
self.fsw = None
def set_window(self, window):
self.fsw = window
def request(self, contact, name=None, isFile=False):
iq = xmpp.Iq(typ='get', to=contact, frm=self.ourjid)
match = iq.addChild(name='match', namespace=NS_FILE_SHARING)
request = match.addChild(name='request')
if not isFile and name is None:
request.addChild(name='directory')
elif not isFile and name is not None:
dir_ = request.addChild(name='directory')
dir_.addChild(name='name').addData('/' + name)
elif isFile:
pass
return iq
def __buildReply(self, typ, stanza):
iq = xmpp.Iq(typ, to=stanza.getFrom(), frm=stanza.getTo(),
attrs={'id': stanza.getID()})
iq.addChild(name='match', namespace=NS_FILE_SHARING)
return iq
def set_window(self, fsw):
self.fsw = fsw
def on_request(self, stanza):
try:
fjid = helpers.get_full_jid_from_iq(stanza)
except helpers.InvalidFormat:
# A message from a non-valid JID arrived, it has been ignored.
return
if stanza.getTag('error'):
# TODO: better handle this
return
jid = gajim.get_jid_without_resource(fjid)
req = stanza.getTag('match').getTag('request')
if req.getTag('directory') and not \
req.getTag('directory').getChildren():
# We just received a toplevel directory request
files = self.plugin.database.get_toplevel_files(self.account, jid)
response = self.offer(stanza.getID(), fjid, files)
self.conn.connection.send(response)
elif req.getTag('directory') and req.getTag('directory').getTag('name'):
dir_ = req.getTag('directory').getTag('name').getData()[1:]
files = self.plugin.database.get_files_from_dir(self.account, jid, dir_)
response = self.offer(stanza.getID(), fjid, files)
self.conn.connection.send(response)
def on_offer(self, stanza):
# We just got a stanza offering files
fjid = helpers.get_full_jid_from_iq(stanza)
info = get_files_info(stanza)
if fjid not in self.fsw.browse_jid or not info:
# We weren't expecting anything from this contact, do nothing
# Or we didn't receive any offering files
return
flist = []
for f in info[0]:
flist.append(f['name'])
flist.extend(info[1])
self.fsw.browse_fref = self.fsw.add_file_list(flist, self.fsw.ts_search,
self.fsw.browse_fref,
self.fsw.browse_jid[fjid]
)
for f in info[0]:
iter_ = self.fsw.browse_fref[f['name']]
path = self.fsw.ts_search.get_path(iter_)
self.fsw.brw_file_info[path] = (f['name'], f['date'], f['size'],
f['hash'], f['desc'])
# TODO: add tooltip
'''
for f in info[0]:
r = self.fsw.browse_fref[f['name']]
path = self.fsw.ts_search.get_path(r)
# AM HERE WORKING ON THE TOOLTIP
tooltip.set_text('noooo')
self.fsw.tv_search.set_tooltip_row(tooltip, path)
'''
for dir_ in info[1]:
if dir_ not in self.fsw.empty_row_child:
parent = self.fsw.browse_fref[dir_]
row = self.fsw.ts_search.append(parent, ('',))
self.fsw.empty_row_child[dir_] = row
def handler(self, stanza):
# handles incoming match stanza
if stanza.getTag('match').getTag('offer'):
self.on_offer(stanza)
elif stanza.getTag('match').getTag('request'):
self.on_request(stanza)
def handler(self, stanza, fjid):
# handles incoming stanza
# TODO: Stanza checking
if stanza.getType() == 'get':
offer = self.on_request(stanza, fjid)
self.conn.connection.send(offer)
elif stanza.getType() == 'result':
return self.on_offer(stanza, fjid)
else:
# TODO: reply with malformed stanza error
pass
def offer(self, id_, contact, items):
iq = xmpp.Iq(typ='result', to=contact, frm=self.ourjid,
attrs={'id': id_})
match = iq.addChild(name='match', namespace=NS_FILE_SHARING)
offer = match.addChild(name='offer')
if len(items) == 0:
offer.addChild(name='directory')
def on_toplevel_request(self, stanza, fjid):
jid = get_jid_without_resource(fjid)
roots = self.plugin.database.get_toplevel_dirs(self.account, jid)
items = []
for root in roots:
items.append({'type' : 'directory',
'name' : root[0]
})
return self.offer(stanza.getID(), fjid, None, items)
def on_dir_request(self, stanza, fjid, jid, dir_):
result = self.plugin.database.get_files_from_dir(self.account, jid, dir_)
result = self.convert_dbformat(result)
return self.offer(stanza.getID(), fjid, dir_, result)
def on_request(self, stanza, fjid):
jid = get_jid_without_resource(fjid)
if stanza.getTag('error'):
# TODO: better handle this
return -1
node = stanza.getQuery().getAttr('node')
if node is None:
return self.on_toplevel_request(stanza, fjid)
result = self.plugin.database.get_file(self.account, jid, None, node)
if result == []:
return self.offer(stanza.getID(), fjid, node, result)
if result[-1] == 1:
# The peer asked for the content of a dir
reply = self.on_dir_request(self, stanza, fjid)
else:
for i in items:
# if it is a directory
if i[5] == True:
item = offer.addChild(name='directory')
name = item.addChild('name')
name.setData('/' + i[0])
else:
item = offer.addChild(name='file')
item.addChild('name').setData('/' + i[0])
if i[1] != '':
h = Hashes()
h.addHash(i[1], 'sha-1')
item.addChild(node=h)
item.addChild('size').setData(i[2])
item.addChild('desc').setData(i[3])
item.addChild('date').setData(i[4])
return iq
# The peer asked for more information on a file
# TODO: Refactor, make method to convert db format to expect file
# format
file_ = [{'type' : 'file',
'name' : result[0].split('/')[-1],
'hash' : result[1],
'size' : result[2],
'desc' : result[3],
'date' : result[4],
}]
reply = self.offer(stanza.getID(), fjid, node, file_)
return reply
def set_window(self, fsw):
self.fsw = fsw
def on_offer(self, stanza, fjid):
offered = []
query = stanza.getQuery()
for child in query.getChildren():
if child.getName() == 'directory':
offered.append({'name' : child.getAttr('name'),
'type' : 'directory'})
elif child.getName() == 'file':
attrs = {'type' : 'file'}
grandchildren = child.getChildren()
for grandchild in grandchildren:
attrs[grandchild.getName()] = grandchild.getData()
offered.append(attrs)
else:
print 'File sharing. Cant handle unknown type: ' + str(child)
return offered
def get_files_info(stanza):
# Crawls the stanza in search for file and dir structure.
files = []
dirs = []
children = stanza.getTag('match').getTag('offer').getChildren()
for c in children:
if c.getName() == 'file':
f = {'name' : \
c.getTag('name').getData()[1:] if c.getTag('name') else '',
'size' : c.getTag('size').getData() if c.getTag('size') else '',
'date' : c.getTag('date').getData() if c.getTag('date') else '',
'desc' : c.getTag('desc').getData() if c.getTag('desc') else '',
# TODO: handle different hash algo
'hash' : c.getTag('hash').getData() if c.getTag('hash') else '',
}
files.append(f)
else:
dirname = c.getTag('name')
if dirname is None:
return None
dirs.append(dirname.getData()[1:])
return (files, dirs)
def get_jid_without_resource(jid):
return jid.split('/')[0]
[info]
name: File Sharing
short_name: fshare
#version: 0.1
#version: 0.1.1
description: This plugin allows you to share folders with your peers using jingle file transfer.
authors: Jefry Lagrange <jefry.reyes@gmail.com>
homepage: www.google.com
homepage: www.gajim.org
max_gajim_version: 0.16.9
#/usr/bin/python
import unittest
from mock import Mock
import sys, os
sys.path.append(os.path.abspath(sys.path[0]) + '/../')
import fshare_protocol
import nbxmpp
class TestProtocol(unittest.TestCase):
def setUp(self):
self.protocol = fshare_protocol.Protocol('test@gajim.org/test')
def test_request(self):
iq = self.protocol.request('peer@gajim.org/test', 'documents/test2.txt')
self.assertEqual(iq.getType(), 'get')
self.assertEqual(iq.getQuery().getName(), 'query')
self.assertEqual(iq.getQuery().getNamespace(), fshare_protocol.NS_FILE_SHARING)
self.assertEqual(iq.getQuery().getAttr('node'), 'documents/test2.txt')
def test_convert_dbformat(self):
file_ = [(u'relative_path', u'hash', 999, u'description',
u'date', u'file_path', 0)]
formatted = self.protocol.convert_dbformat(file_)
self.assertNotEqual(len(formatted), 0)
for item in formatted:
self.assertEqual(type(item), type({}))
self.assertEqual(formatted[0]['type'], 'file')
def test_buildFileNode(self):
file_info = {'name' : 'test2.text',
'desc' : 'test',
'hash' : '00000',
'size' : '00000',
'type' : 'file'
}
node = self.protocol.buildFileNode(file_info)
self.assertEqual(node.getName(), 'file')
self.assertEqual(node.getNamespace(), nbxmpp.NS_JINGLE_FILE_TRANSFER)
self.assertEqual(len(node.getChildren()), 4)
def test_offer(self):
items = [ {'name' : 'test2.text',
'desc' : 'test',
'hash' : '00000',
'size' : '00000',
'type' : 'file'
},
{
'name' : 'secret docs',
'type' : 'directory'
}
]
iq = self.protocol.offer('1234', 'peer@gajim.org/test',
'documents', items)
self.assertEqual(iq.getType(), 'result')
self.assertNotEqual(iq.getID(), None)
self.assertEqual(iq.getQuery().getName(), 'query')
self.assertEqual(iq.getQuery().getNamespace(), fshare_protocol.NS_FILE_SHARING)
self.assertEqual(iq.getQuery().getAttr('node'), 'documents')
node = iq.getQuery()
self.assertEqual(len(node.getChildren()), 2)
# Mock modules
gajim = Mock()
attr = {'get_jid_from_account.return_value': 'test@gajim.org/test'}
gajim.configure_mock(**attr)
fshare_protocol.gajim = gajim
fshare_protocol.helpers = Mock()
class TestProtocolDispatcher(unittest.TestCase):
def setUp(self):
self.account = 'test@gajim.org'
self.protocol = fshare_protocol.Protocol(self.account)
testc = {self.account : Mock()}
fshare_protocol.gajim.connections = testc
database = Mock()
top_dirs = [(u'relative_path1', None, None, None, None, 1),
(u'relative_path2', None, None, None, None, 1)]
file_ = (u'file1', u'hash', 999, u'description',
u'date', u'file_path', 0)
attr = {'get_toplevel_dirs.return_value': top_dirs,
'get_file.return_value': file_,
'get_files_from_dir.return_value' : [file_, top_dirs[0]]
}
database.configure_mock(**attr)
plugin = Mock()
plugin.database = database
self.dispatcher = fshare_protocol.ProtocolDispatcher(
self.account, plugin)
def test_handler(self):
iq = self.protocol.request('peer@gajim.org/test',
'documents/test2.txt')
#offer = self.dispatcher.on_offer
request = self.dispatcher.on_request
#self.dispatcher.on_offer = Mock()
self.dispatcher.on_request = Mock()
self.dispatcher.handler(iq, 'peer@gajim.org/test')
assert(self.dispatcher.on_request.called)
self.dispatcher.on_request = request
def test_on_offer(self):
items = [ {'name' : 'test2.text',
'type' : 'file'
},
{
'name' : 'secret docs',
'type' : 'directory'
}
]
iq = self.protocol.offer('1234', 'peer@gajim.org/test',
'documents', items)
offered_files = self.dispatcher.on_offer(iq, 'peer@gajim.org/test')
self.assertEqual(len(offered_files), 2)
def test_on_dir_request(self):
iq = self.protocol.request('peer@gajim.org/test', 'documents')
response = self.dispatcher.on_dir_request(iq, 'peer@gajim.org/test',
'peer@gajim.org', 'documents')
self.assertEqual(response.getType(), 'result')
self.assertEqual(response.getQuery().getName(), 'query')
self.assertEqual(response.getQuery().getNamespace(), fshare_protocol.NS_FILE_SHARING)
self.assertEqual(response.getQuery().getAttr('node'), 'documents')
node = response.getQuery()
self.assertEqual(len(node.getChildren()), 2)
def test_on_request(self):
iq = self.protocol.request('peer@gajim.org/test','documents/file1.txt')
response = self.dispatcher.on_request(iq, 'peer@gajim.org/test')
self.assertEqual(response.getType(), 'result')
self.assertEqual(response.getQuery().getName(), 'query')
self.assertEqual(response.getQuery().getNamespace(), fshare_protocol.NS_FILE_SHARING)
self.assertEqual(response.getQuery().getAttr('node'), 'documents/file1.txt')
node = response.getQuery()
self.assertEqual(len(node.getChildren()), 1)
def test_on_toplevel_request(self):
iq = self.protocol.request('peer@gajim.org/test')
response = self.dispatcher.on_toplevel_request(iq, 'peer@gajim.org')
self.assertEqual(response.getType(), 'result')
self.assertEqual(response.getQuery().getName(), 'query')
self.assertEqual(response.getQuery().getNamespace(), fshare_protocol.NS_FILE_SHARING)
node = response.getQuery()
self.assertEqual(len(node.getChildren()), 2)
if __name__ == '__main__':
unittest.main()
......@@ -7,7 +7,7 @@
<object class="GtkTable" id="config_table">
<property name="visible">True</property>
<property name="border_width">6</property>
<property name="n_rows">2</property>
<property name="n_rows">3</property>
<property name="n_columns">2</property>
<property name="column_spacing">7</property>
<property name="row_spacing">5</property>
......@@ -45,7 +45,7 @@
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
......@@ -59,9 +59,28 @@
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
<property name="y_options"></property>
<property name="y_options"/>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="flash_cb">
<property name="label" translatable="yes">Do not flash the LED, only switch it on.</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
<property name="y_options"/>
</packing>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
......
......@@ -13,11 +13,12 @@ from plugins.gui import GajimPluginConfigDialog
class FlashingKeyboard(GajimPlugin):
@log_calls('FlashingKeyboard')
def init(self):
self.description = _('Flashing keyboard led when there are unread messages.')
self.config_dialog = FlashingKeyboardPluginConfigDialog(self)
self.config_default_values = {
'command1': ("xset led named 'Scroll Lock'", ''),
'command2': ("xset -led named 'Scroll Lock'", '')}
'command1': ("xset led named 'Scroll Lock'", ''),
'command2': ("xset -led named 'Scroll Lock'", ''),
'flash': (True, ''),
}
self.is_active = None
self.timeout = 500
......@@ -35,15 +36,22 @@ class FlashingKeyboard(GajimPlugin):
if gajim.events.get_nb_systray_events():
if self.id_0:
return
self.id_0 = gobject.timeout_add(self.timeout, self.led_on)
if self.config['flash']:
self.id_0 = gobject.timeout_add(self.timeout, self.led_on)
else:
self.led_on()
self.id_0 = True
else:
if self.id_0:
gobject.source_remove(self.id_0)
if self.config['flash']:
gobject.source_remove(self.id_0)
self.id_0 = None
self.led_off()
def led_on(self):
subprocess.Popen('%s' % self.config['command1'], shell=True).wait()
gobject.timeout_add(self.timeout_off, self.led_off)
if self.config['flash']:
gobject.timeout_add(self.timeout_off, self.led_off)
return True
def led_off(self):
......@@ -54,7 +62,11 @@ class FlashingKeyboard(GajimPlugin):
gajim.events.event_added_subscribe(self.on_event_added)
gajim.events.event_removed_subscribe(self.on_event_removed)
if gajim.events.get_nb_systray_events():
self.id_0 = gobject.timeout_add(self.timeout, self.led_on)
if self.config['flash']:
self.id_0 = gobject.timeout_add(self.timeout, self.led_on)
else:
self.led_on()
self.id_0 = True
@log_calls('FlashingKeyboard')
def deactivate(self):
......@@ -62,6 +74,7 @@ class FlashingKeyboard(GajimPlugin):
gajim.events.event_removed_unsubscribe(self.on_event_removed)
if self.id_0:
gobject.source_remove(self.id_0)
self.led_off()
class FlashingKeyboardPluginConfigDialog(GajimPluginConfigDialog):
......@@ -81,14 +94,19 @@ class FlashingKeyboardPluginConfigDialog(GajimPluginConfigDialog):
self.isactive = self.plugin.active
if self.plugin.active:
gajim.plugin_manager.deactivate_plugin(self.plugin)
for name in self.plugin.config_default_values:
for name in ('command1', 'command2'):
widget = self.xml.get_object(name)
widget.set_text(self.plugin.config[name])
widget = self.xml.get_object('flash_cb')
widget.set_active(not self.plugin.config['flash'])
def on_close_button_clicked(self, widget):
widget = self.xml.get_object('command1')
self.plugin.config['command1'] = widget.get_text()
widget = self.xml.get_object('command2')
self.plugin.config['command2'] = widget.get_text()
widget = self.xml.get_object('flash_cb')
self.plugin.config['flash'] = not widget.get_active()
if self.isactive:
gajim.plugin_manager.activate_plugin(self.plugin)
self.hide()
[info]
name: Flashing Keyboard
short_name: flashing_keyboard
version: 0.1.3
version: 0.1.5
description: Flashing keyboard led when there are unread messages.
authors: Denis Fomin <fominde@gmail.com>
homepage: http://trac-plugins.gajim.org/wiki/FlashingKeyboardPlugin
max_gajim_version: 0.16.9
[info]
name: GNOME SessionManager
short_name: gnome_session_manager
version: 0.1.2
version: 0.1.3
description: Set and react on GNOME Session presence settings
authors: Philippe Normand <phil@base-art.net>
homepage: http://base-art.net
max_gajim_version: 0.16.9
......@@ -32,7 +32,6 @@ class GnomeSessionManagerPlugin(GajimPlugin):
@log_calls('GnomeSessionManagerPlugin')
def init(self):
self.description = _('Set and react on GNOME Session presence settings')
self.config_dialog = None
self.events_handlers = {}
......
[info]
name: Google Translation
short_name: google_translation
version: 0.3.1
version: 0.3.2
description: Translates (currently only incoming) messages using Google Translate.
authors = Mateusz Biliński <mateusz@bilinski.it>
mrDoctorWho <mrdoctorwho@gmail.com>
homepage = http://trac-plugins.gajim.org/wiki/GoogleTranslationPlugin
max_gajim_version: 0.16.9
......@@ -109,8 +109,6 @@ class GoogleTranslationPlugin(GajimPlugin):
@log_calls('GoogleTranslationPlugin')
def init(self):
self.description = _('Translates (currently only incoming)'
'messages using Google Translate.')
self.config_dialog = None
self.config_default_values = {
......
[info]
name: Off-The-Record Encryption
short_name: gotr
version: 1.7.1
description: Provide OTR encryption
version: 1.9.6
description: Provide OTR encryption. Read <a href="https://github.com/python-otr/gajim-otr/wiki">https://github.com/python-otr/gajim-otr/wiki</a> before use.
authors: Kjell Braden <afflux.gajim@pentabarf.de>
homepage: http://gajim-otr.pentabarf.de
max_gajim_version: 0.16.9
......@@ -46,6 +46,8 @@ DEFAULTFLAGS = {
MMS = 1024
PROTOCOL = 'xmpp'
MINVERSION_OUTGOING_MSG_STAZA = "0.16.4"
enc_tip = 'A private chat session <i>is established</i> to this contact ' \
'with this fingerprint'
unused_tip = 'A private chat session is established to this contact using ' \
......@@ -53,14 +55,19 @@ unused_tip = 'A private chat session is established to this contact using ' \
ended_tip = 'The private chat session to this contact has <i>ended</i>'
inactive_tip = 'Communication to this contact is currently ' \
'<i>unencrypted</i>'
msg_not_send = _('Your message was not send. Either end '
'your private conversation, or restart it')
import logging
import nbxmpp
import os
import pickle
import time
import sys
from pprint import pformat
from distutils.version import LooseVersion
import common.xmpp
from common import gajim
from common import ged
from common.connection_handlers_events import MessageOutgoingEvent
......@@ -75,6 +82,7 @@ sys.path.insert(0, os.path.dirname(ui.__file__))
from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
name2codepoint['apos'] = 0x0027
HAS_CRYPTO = True
try:
......@@ -85,6 +93,8 @@ try:
except ImportError:
HAS_CRYPTO = False
import nbxmpp
HAS_POTR = True
try:
import potr
......@@ -101,7 +111,7 @@ try:
potrrootlog.addHandler(h)
def get_jid_from_fjid(fjid):
return gajim.get_room_and_nick_from_fjid(fjid)[0]
return gajim.get_room_and_nick_from_fjid(str(fjid))[0]
class GajimContext(potr.context.Context):
# self.peer is fjid
......@@ -119,11 +129,12 @@ try:
msg = unicode(msg)
account = self.user.accountname
stanza = common.xmpp.Message(to=self.peer, body=msg, typ='chat')
stanza = nbxmpp.Message(to=self.peer, body=msg, typ='chat')
if appdata is not None:
session = appdata.get('session', None)
if session is not None:
stanza.setThread(session.thread_id)
add_message_processing_hints(stanza)
gajim.connections[account].connection.send(stanza, now=True)
def setState(self, newstate):
......@@ -244,11 +255,11 @@ except ImportError:
def otr_dialog_destroy(widget, *args, **kwargs):
widget.destroy()
class OtrPlugin(GajimPlugin):
otr = None
def init(self):
self.description = _('See http://www.cypherpunks.ca/otr/')
self.us = {}
......@@ -268,8 +279,14 @@ class OtrPlugin(GajimPlugin):
self.events_handlers = {}
self.events_handlers['message-received'] = (ged.PRECORE,
self.handle_incoming_msg)
self.events_handlers['message-outgoing'] = (ged.OUT_PRECORE,
self.handle_outgoing_msg)
self.events_handlers['before-change-show'] = (ged.PRECORE,
self.handle_change_show)
if LooseVersion(gajim.config.get('version')) < LooseVersion(MINVERSION_OUTGOING_MSG_STAZA):
self.events_handlers['message-outgoing'] = (ged.OUT_PRECORE,
self.handle_outgoing_msg)
else:
self.events_handlers['stanza-message-outgoing'] = (ged.OUT_PRECORE,
self.handle_outgoing_msg_stanza)
self.gui_extension_points = {
'chat_control' : (self.cc_connect, self.cc_disconnect)
......@@ -471,13 +488,14 @@ class OtrPlugin(GajimPlugin):
if ctrl:
ctrl.print_conversation_line(u'[OTR] %s' % msg, 'status',
'', None)
id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
# gajim.logger.write() only marks a message as unread (and so
# only returns an id) when fjid is a real contact (NOT if it's a
# GC private chat)
if id:
gajim.logger.set_read_messages([id])
if gajim.config.should_log(account, jid):
id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
# gajim.logger.write() only marks a message as unread (and so
# only returns an id) when fjid is a real contact (NOT if it's a
# GC private chat)
if id:
gajim.logger.set_read_messages([id])
else:
session = gajim.connections[account].get_or_create_session(fjid,
thread_id)
......@@ -491,10 +509,11 @@ class OtrPlugin(GajimPlugin):
session.control = ctrl
session.control.set_session(session)
msg_id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
session.roster_message(jid, msg, tim=tim, msg_id=msg_id,
msg_type='chat', resource=resource)
if gajim.config.should_log(account, jid):
msg_id = gajim.logger.write('chat_msg_recv', fjid,
message=u'[OTR: %s]' % msg, tim=tim)
session.roster_message(jid, msg, tim=tim, msg_id=msg_id,
msg_type='chat', resource=resource)
@classmethod
def update_otr(cls, user, acc, print_status=False):
......@@ -517,18 +536,30 @@ class OtrPlugin(GajimPlugin):
if ctrl and ctrl.TYPE_ID == TYPE_CHAT:
return ctrl
def handle_change_show(self, event):
account = event.conn.name
if event.show == 'offline':
for us in self.us.itervalues():
for fjid, ctx in us.ctxs.iteritems():
if ctx.state == potr.context.STATE_ENCRYPTED:
self.us[account].getContext(fjid).disconnect()
return PASS
def handle_incoming_msg(self, event):
ctx = None
account = event.conn.name
accjid = gajim.get_jid_from_account(account)
if event.encrypted is not False or not event.stanza.getTag('body') \
or not isinstance(event.stanza.getBody(), unicode):
or not isinstance(event.stanza.getBody(), unicode) \
or event.mtype != 'chat':
return PASS
try:
ctx = self.us[account].getContext(event.fjid)
msgtxt, tlvs = ctx.receiveMessage(event.msgtxt,
msgtxt, tlvs = ctx.receiveMessage(event.msgtxt.encode('utf8'),
appdata={'session':event.session})
except potr.context.NotOTRMessage, e:
# received message was not OTR - pass it on
......@@ -574,52 +605,94 @@ class OtrPlugin(GajimPlugin):
ctx.smpWindow.handle_tlv(tlvs)
stripper = HTMLStripper()
stripper.feed(unicode(msgtxt or ''))
stripper.feed((msgtxt or '').decode('utf8'))
event.msgtxt = stripper.stripped_data
event.stanza.setBody(event.msgtxt)
event.stanza.setXHTML(msgtxt)
event.stanza.setXHTML((msgtxt or '').decode('utf8'))
return PASS
def handle_outgoing_msg(self, event):
if hasattr(event, 'otrmessage'):
return PASS
xep_200 = bool(event.session) and event.session.enable_encryption
if xep_200 or not event.message:
return PASS
if event.session:
fjid = event.session.get_to()
else:
fjid = event.jid
if event.resource:
fjid += '/' + event.resource
message = event.xhtml or escape(event.message)
def handle_outgoing_msg_stanza(self, event):
xhtml = event.msg_iq.getXHTML()
body = event.msg_iq.getBody()
encrypted = False
try:
newmsg = self.us[event.account].getContext(fjid).sendMessage(
potr.context.FRAGMENT_SEND_ALL_BUT_LAST, message,
appdata={'session':event.session})
if xhtml:
xhtml = xhtml.encode('utf8')
encrypted_msg = self.us[event.conn.name].\
getContext(event.msg_iq.getTo()).\
sendMessage(potr.context.FRAGMENT_SEND_ALL_BUT_LAST, xhtml)
if xhtml != encrypted_msg.strip(): #.strip() because sendMessage() adds whitespaces
encrypted = True
event.msg_iq.delChild('html')
event.msg_iq.setBody(encrypted_msg)
elif body:
body = escape(body).encode('utf8')
encrypted_msg = self.us[event.conn.name].\
getContext(event.msg_iq.getTo()).\
sendMessage(potr.context.FRAGMENT_SEND_ALL_BUT_LAST, body)
if body != encrypted_msg.strip():
encrypted = True
event.msg_iq.setBody(encrypted_msg)
except potr.context.NotEncryptedError, e:
if e.args[0] == potr.context.EXC_FINISHED:
self.gajim_log(_('Your message was not send. Either end '
'your private conversation, or restart it'), event.account,
fjid)
self.gajim_log(msg_not_send, event.conn.name, event.msg_iq.getTo())
return IGNORE
else:
raise e
if event.xhtml: # if we had html before, replace with new content
event.xhtml = newmsg
stripper = HTMLStripper()
stripper.feed(unicode(newmsg or ''))
event.message = stripper.stripped_data
if encrypted:
add_message_processing_hints(event.msg_iq)
return PASS
def handle_outgoing_msg(self, event):
try:
if hasattr(event, 'otrmessage'):
return PASS
xep_200 = bool(event.session) and event.session.enable_encryption
potrrootlog.debug('got event {0} xep_200={1}'.format(pformat(event.__dict__), xep_200))
if xep_200 or not event.message:
return PASS
if event.session:
fjid = event.session.get_to()
else:
fjid = event.jid
if event.resource:
fjid += '/' + event.resource
message = event.xhtml or escape(event.message)
message = message.encode('utf8')
potrrootlog.debug('processing message={0!r} from fjid={1!r}'.format(message, fjid))
try:
newmsg = self.us[event.account].getContext(fjid).sendMessage(
potr.context.FRAGMENT_SEND_ALL_BUT_LAST, message,
appdata={'session':event.session})
potrrootlog.debug('processed message={0!r}'.format(newmsg))
except potr.context.NotEncryptedError, e:
if e.args[0] == potr.context.EXC_FINISHED:
self.gajim_log(msg_not_send, event.account, fjid)
return IGNORE
else:
raise e
if event.xhtml: # if we had html before, replace with new content
event.xhtml = newmsg
stripper = HTMLStripper()
stripper.feed((newmsg or '').decode('utf8'))
event.message = stripper.stripped_data
return PASS
except:
potrrootlog.exception('exception in outgoing message handler, message (hopefully) discarded')
return IGNORE
class HTMLStripper(HTMLParser):
def reset(self):
......@@ -634,8 +707,12 @@ class HTMLStripper(HTMLParser):
self.stripped_data += '\n'
def handle_entityref(self, name):
c = unichr(name2codepoint[name])
try:
c = unichr(name2codepoint[name])
except KeyError:
c = '&{};'.format(name)
self.stripped_data += c
def handle_charref(self, name):
if name.startswith('x'):
c = unichr(int(name[1:], 16))
......@@ -661,5 +738,7 @@ def escape(s):
s = s.replace("\n", "<br/>")
return s
## TODO:
## - disconnect ctxs on disconnect
def add_message_processing_hints(stanza):
stanza.addChild(name='private', namespace=nbxmpp.NS_CARBONS)
stanza.addChild(name='no-permanent-store', namespace=nbxmpp.NS_MSG_HINTS)
stanza.addChild(name='no-copy', namespace=nbxmpp.NS_MSG_HINTS)
......@@ -24,4 +24,4 @@ from potr.utils import human_hash
''' version is: (major, minor, patch, sub) with sub being one of 'alpha',
'beta', 'final' '''
VERSION = (1, 0, 0, 'beta5')
VERSION = (1, 0, 0, 'beta7')
......@@ -26,8 +26,8 @@ from potr.utils import human_hash, bytes_to_long, unpack, pack_mpi
DEFAULT_KEYTYPE = 0x0000
pkTypes = {}
def registerkeytype(cls):
if not hasattr(cls, 'parsePayload'):
raise TypeError('registered key types need parsePayload()')
if cls.keyType is None:
raise TypeError('registered key class needs a type value')
pkTypes[cls.keyType] = cls
return cls
......@@ -35,12 +35,16 @@ def generateDefaultKey():
return pkTypes[DEFAULT_KEYTYPE].generate()
class PK(object):
__slots__ = []
keyType = None
@classmethod
def generate(cls):
raise NotImplementedError
@classmethod
def parsePayload(cls, data, private=False):
raise NotImplementedError
def sign(self, data):
raise NotImplementedError
def verify(self, data):
......@@ -80,13 +84,13 @@ class PK(object):
@classmethod
def parsePrivateKey(cls, data):
implCls, data = cls.getImplementation(data)
logging.debug('Got privkey of type %r' % implCls)
logging.debug('Got privkey of type %r', implCls)
return implCls.parsePayload(data, private=True)
@classmethod
def parsePublicKey(cls, data):
implCls, data = cls.getImplementation(data)
logging.debug('Got pubkey of type %r' % implCls)
logging.debug('Got pubkey of type %r', implCls)
return implCls.parsePayload(data)
def __str__(self):
......
......@@ -15,18 +15,16 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this library. If not, see <http://www.gnu.org/licenses/>.
from Crypto import Cipher, Random
from Crypto import Cipher
from Crypto.Hash import SHA256 as _SHA256
from Crypto.Hash import SHA as _SHA1
from Crypto.Hash import SHA as _SHA1
from Crypto.Hash import HMAC as _HMAC
from Crypto.PublicKey import DSA
from Crypto.Random import random
from numbers import Number
from potr.compatcrypto import common
from potr.utils import pack_mpi, read_mpi, bytes_to_long, long_to_bytes
# XXX atfork?
RNG = Random.new()
from potr.utils import read_mpi, bytes_to_long, long_to_bytes
def SHA256(data):
return _SHA256.new(data).digest()
......@@ -54,7 +52,6 @@ def AESCTR(key, counter=0):
return Cipher.AES.new(key, Cipher.AES.MODE_CTR, counter=counter)
class Counter(object):
__slots__ = ['prefix', 'val']
def __init__(self, prefix):
self.prefix = prefix
self.val = 0
......@@ -72,17 +69,15 @@ class Counter(object):
return '<Counter(p={p!r},v={v!r})>'.format(p=self.prefix, v=self.val)
def byteprefix(self):
return long_to_bytes(self.prefix).rjust(8, b'\0')
return long_to_bytes(self.prefix, 8)
def __call__(self):
val = long_to_bytes(self.val)
prefix = long_to_bytes(self.prefix)
bytesuffix = long_to_bytes(self.val, 8)
self.val += 1
return self.byteprefix() + val.rjust(8, b'\0')
return self.byteprefix() + bytesuffix
@common.registerkeytype
class DSAKey(common.PK):
__slots__ = ['priv', 'pub']
keyType = 0x0000
def __init__(self, key=None, private=False):
......@@ -111,10 +106,10 @@ class DSAKey(common.PK):
return SHA1(self.getSerializedPublicPayload())
def sign(self, data):
# 2 <= K <= q = 160bit = 20 byte
K = bytes_to_long(RNG.read(19)) + 2
# 2 <= K <= q
K = random.randrange(2, self.priv.q)
r, s = self.priv.sign(data, K)
return long_to_bytes(r) + long_to_bytes(s)
return long_to_bytes(r, 20) + long_to_bytes(s, 20)
def verify(self, data, sig):
r, s = bytes_to_long(sig[:20]), bytes_to_long(sig[20:])
......
......@@ -19,7 +19,7 @@
from __future__ import unicode_literals
try:
basestring = basestring
type(basestring)
except NameError:
# all strings are unicode in python3k
basestring = str
......@@ -27,7 +27,7 @@ except NameError:
# callable is not available in python 3.0 and 3.1
try:
callable = callable
type(callable)
except NameError:
from collections import Callable
def callable(x):
......@@ -42,6 +42,7 @@ logger = logging.getLogger(__name__)
from potr import crypt
from potr import proto
from potr import compatcrypto
from time import time
......@@ -62,16 +63,11 @@ OFFER_REJECTED = 2
OFFER_ACCEPTED = 3
class Context(object):
__slots__ = ['user', 'policy', 'crypto', 'tagOffer', 'lastSend',
'lastMessage', 'mayRetransmit', 'fragment', 'fragmentInfo', 'state',
'inject', 'trust', 'peer', 'trustName']
def __init__(self, account, peername):
self.user = account
self.peer = peername
self.policy = {}
self.crypto = crypt.CryptEngine(self)
self.discardFragment()
self.tagOffer = OFFER_NOTSENT
self.mayRetransmit = 0
self.lastSend = 0
......@@ -79,6 +75,10 @@ class Context(object):
self.state = STATE_PLAINTEXT
self.trustName = self.peer
self.fragmentInfo = None
self.fragment = None
self.discardFragment()
def getPolicy(self, key):
raise NotImplementedError
......@@ -100,13 +100,19 @@ class Context(object):
params = message.split(b',')
if len(params) < 5 or not params[1].isdigit() or not params[2].isdigit():
logger.warning('invalid formed fragmented message: %r', params)
return None
self.discardFragment()
return message
K, N = self.fragmentInfo
try:
k = int(params[1])
n = int(params[2])
except ValueError:
logger.warning('invalid formed fragmented message: %r', params)
self.discardFragment()
return message
k = int(params[1])
n = int(params[2])
fragData = params[3]
logger.debug(params)
......@@ -114,17 +120,17 @@ class Context(object):
if n >= k == 1:
# first fragment
self.discardFragment()
self.fragmentInfo = (k,n)
self.fragmentInfo = (k, n)
self.fragment.append(fragData)
elif N == n >= k > 1 and k == K+1:
# accumulate
self.fragmentInfo = (k,n)
self.fragmentInfo = (k, n)
self.fragment.append(fragData)
else:
# bad, discard
self.discardFragment()
logger.warning('invalid fragmented message: %r', params)
return None
return message
if n == k > 0:
assembled = b''.join(self.fragment)
......@@ -210,7 +216,7 @@ class Context(object):
if self.state != STATE_ENCRYPTED:
self.sendInternal(proto.Error(
'You sent encrypted to {user}, who wasn\'t expecting it.'
.format(user=self.user.name)), appdata=appdata)
.format(user=self.user.name).encode('utf-8')), appdata=appdata)
if ignore:
return IGN
raise NotEncryptedError(EXC_UNREADABLE_MESSAGE)
......@@ -263,12 +269,13 @@ class Context(object):
return msg
def processOutgoingMessage(self, msg, flags, tlvs=[]):
if isinstance(self.parse(msg), proto.Query):
isQuery = self.parseExplicitQuery(msg) is not None
if isQuery:
return self.user.getDefaultQueryMessage(self.getPolicy)
if self.state == STATE_PLAINTEXT:
if self.getPolicy('REQUIRE_ENCRYPTION'):
if not isinstance(self.parse(msg), proto.Query):
if not isQuery:
self.lastMessage = msg
self.lastSend = time()
self.mayRetransmit = 2
......@@ -277,8 +284,12 @@ class Context(object):
return msg
if self.getPolicy('SEND_TAG') and self.tagOffer != OFFER_REJECTED:
self.tagOffer = OFFER_SENT
return proto.TaggedPlaintext(msg, self.getPolicy('ALLOW_V1'),
self.getPolicy('ALLOW_V2'))
versions = set()
if self.getPolicy('ALLOW_V1'):
versions.add(1)
if self.getPolicy('ALLOW_V2'):
versions.add(2)
return proto.TaggedPlaintext(msg, versions)
return msg
if self.state == STATE_ENCRYPTED:
msg = self.crypto.createDataMessage(msg, flags, tlvs)
......@@ -304,9 +315,9 @@ class Context(object):
def sendFragmented(self, msg, policy=FRAGMENT_SEND_ALL, appdata=None):
mms = self.maxMessageSize(appdata)
msgLen = len(msg)
if mms != 0 and len(msg) > mms:
if mms != 0 and msgLen > mms:
fms = mms - 19
fragments = [ msg[i:i+fms] for i in range(0, len(msg), fms) ]
fragments = [ msg[i:i+fms] for i in range(0, msgLen, fms) ]
fc = len(fragments)
......@@ -375,9 +386,9 @@ class Context(object):
self.crypto.smpSecret(secret, question=question, appdata=appdata)
def handleQuery(self, message, appdata=None):
if message.v2 and self.getPolicy('ALLOW_V2'):
if 2 in message.versions and self.getPolicy('ALLOW_V2'):
self.authStartV2(appdata=appdata)
elif message.v1 and self.getPolicy('ALLOW_V1'):
elif 1 in message.versions and self.getPolicy('ALLOW_V1'):
self.authStartV1(appdata=appdata)
def authStartV1(self, appdata=None):
......@@ -386,7 +397,33 @@ class Context(object):
def authStartV2(self, appdata=None):
self.crypto.startAKE(appdata=appdata)
def parse(self, message):
def parseExplicitQuery(self, message):
otrTagPos = message.find(proto.OTRTAG)
if otrTagPos == -1:
return None
indexBase = otrTagPos + len(proto.OTRTAG)
if len(message) <= indexBase:
return None
compare = message[indexBase]
hasq = compare == b'?'[0]
hasv = compare == b'v'[0]
if not hasq and not hasv:
return None
hasv |= len(message) > indexBase+1 and message[indexBase+1] == b'v'[0]
if hasv:
end = message.find(b'?', indexBase+1)
else:
end = indexBase+1
return message[indexBase:end]
def parse(self, message, nofragment=False):
otrTagPos = message.find(proto.OTRTAG)
if otrTagPos == -1:
if proto.MESSAGE_TAG_BASE in message:
......@@ -395,38 +432,40 @@ class Context(object):
return message
indexBase = otrTagPos + len(proto.OTRTAG)
if len(message) <= indexBase:
return message
compare = message[indexBase]
if compare == b','[0]:
if nofragment is False and compare == b','[0]:
message = self.fragmentAccumulate(message[indexBase:])
if message is None:
return None
else:
return self.parse(message)
return self.parse(message, nofragment=True)
else:
self.discardFragment()
hasq = compare == b'?'[0]
hasv = compare == b'v'[0]
if hasq or hasv:
hasv |= len(message) > indexBase+1 and \
message[indexBase+1] == b'v'[0]
if hasv:
end = message.find(b'?', indexBase+1)
else:
end = indexBase+1
payload = message[indexBase:end]
return proto.Query.parse(payload)
queryPayload = self.parseExplicitQuery(message)
if queryPayload is not None:
return proto.Query.parse(queryPayload)
if compare == b':'[0] and len(message) > indexBase + 4:
infoTag = base64.b64decode(message[indexBase+1:indexBase+5])
classInfo = struct.unpack(b'!HB', infoTag)
cls = proto.messageClasses.get(classInfo, None)
if cls is None:
try:
infoTag = base64.b64decode(message[indexBase+1:indexBase+5])
classInfo = struct.unpack(b'!HB', infoTag)
cls = proto.messageClasses.get(classInfo, None)
if cls is None:
return message
logger.debug('{user} got msg {typ!r}' \
.format(user=self.user.name, typ=cls))
return cls.parsePayload(message[indexBase+5:])
except (TypeError, struct.error):
logger.exception('could not parse OTR message %s', message)
return message
logger.debug('{user} got msg {typ!r}' \
.format(user=self.user.name, typ=cls))
return cls.parsePayload(message[indexBase+5:])
if message[indexBase:indexBase+7] == b' Error:':
return proto.Error(message[indexBase+7:])
......@@ -437,6 +476,22 @@ class Context(object):
"""Return the max message size for this context."""
return self.user.maxMessageSize
def getExtraKey(self, extraKeyAppId=None, extraKeyAppData=None, appdata=None):
""" retrieves the generated extra symmetric key.
if extraKeyAppId is set, notifies the chat partner about intended
usage (additional application specific information can be supplied in
extraKeyAppData).
returns the 256 bit symmetric key """
if self.state != STATE_ENCRYPTED:
raise NotEncryptedError
if extraKeyAppId is not None:
tlvs = [proto.ExtraKeyTLV(extraKeyAppId, extraKeyAppData)]
self.sendInternal(b'', tlvs=tlvs, appdata=appdata)
return self.crypto.extraKey
class Account(object):
contextclass = Context
def __init__(self, name, protocol, maxMessageSize, privkey=None):
......@@ -447,10 +502,10 @@ class Account(object):
self.ctxs = {}
self.trusts = {}
self.maxMessageSize = maxMessageSize
self.defaultQuery = b'?OTRv{versions}?\n{accountname} has requested ' \
b'an Off-the-Record private conversation. However, you ' \
b'do not have a plugin to support that.\nSee '\
b'http://otr.cypherpunks.ca/ for more information.';
self.defaultQuery = '?OTRv{versions}?\n{accountname} has requested ' \
'an Off-the-Record private conversation. However, you ' \
'do not have a plugin to support that.\nSee '\
'http://otr.cypherpunks.ca/ for more information.'
def __repr__(self):
return '<{cls}(name={name!r})>'.format(cls=self.__class__.__name__,
......@@ -461,7 +516,7 @@ class Account(object):
self.privkey = self.loadPrivkey()
if self.privkey is None:
if autogen is True:
self.privkey = crypt.generateDefaultKey()
self.privkey = compatcrypto.generateDefaultKey()
self.savePrivkey()
else:
raise LookupError
......@@ -484,8 +539,9 @@ class Account(object):
return self.ctxs[uid]
def getDefaultQueryMessage(self, policy):
v = b'2' if policy('ALLOW_V2') else b''
return self.defaultQuery.format(accountname=self.name, versions=v)
v = '2' if policy('ALLOW_V2') else ''
msg = self.defaultQuery.format(accountname=self.name, versions=v)
return msg.encode('ascii')
def setTrust(self, key, fingerprint, trustLevel):
if key not in self.trusts:
......