diff --git a/file_sharing/__init__.py b/file_sharing/__init__.py deleted file mode 100644 index e30b2af..0000000 --- a/file_sharing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from fshare import FileSharePlugin diff --git a/file_sharing/config_dialog.ui b/file_sharing/config_dialog.ui deleted file mode 100644 index 77c4cb3..0000000 --- a/file_sharing/config_dialog.ui +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - True - vertical - - - True - - - True - 0 - Incoming folder: - - - False - 0 - - - - - True - True - - - - 1 - - - - - False - 0 - - - - - - diff --git a/file_sharing/database.py b/file_sharing/database.py deleted file mode 100644 index 9becfc6..0000000 --- a/file_sharing/database.py +++ /dev/null @@ -1,199 +0,0 @@ -import sqlite3 -from common import app -import sys -import os - -class FilesharingDatabase: - def __init__(self, plugin): - self.plugin = plugin - path_l = os.path.split(plugin.config.FILE_PATH) - path = os.path.join(path_l[0], 'shared_files.db') - db_exist = os.path.exists(path) - self.conn = sqlite3.connect(path) - # Enable foreign keys contraints - self.conn.cursor().execute("pragma foreign_keys = on") - if not db_exist: - self.create_database() - - # NOTE: Make sure we are getting and setting the requester without its - # resource - def create_database(self): - c = self.conn.cursor() - # Create tables - c.execute("CREATE TABLE permissions" + - "(fid integer REFERENCES files(fid) ON DELETE CASCADE, " + - "account text, requester text)") - c.execute("CREATE TABLE files" + - "(fid INTEGER PRIMARY KEY AUTOINCREMENT," + - " file_path text, relative_path text, hash_sha1 text," + - "size numeric, description text, mod_date text, is_dir boolean)") - # Save (commit) the changes - self.conn.commit() - c.close() - - def get_toplevel_files(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) - result = c.fetchall() - c.close() - return result - - def get_files_from_dir(self, account, requester, dir_): - c = self.conn.cursor() - data = (account, requester, dir_ + '/%') - 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 LIKE ?", data) - result = c.fetchall() - c.close() - fresult = [] - for r in result: - name = r[0][len(dir_) + 1:] - if '/' not in name: - fresult.append(r) - return fresult - - def get_files(self, account, requester): - """ - >>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \ - 'date', False) - >>> foo = add_file('account@gajim', 'requester@jabber', file_) - >>> result = get_files('account@gajim', 'requester@jabber') - >>> len(result) - 1 - >>> _delete_file(1) - """ - 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=?", data) - result = c.fetchall() - c.close() - return result - - def get_file(self, account, requester, hash_, name): - c = self.conn.cursor() - if hash_: - data = (account, requester, hash_) - sql = "SELECT relative_path, hash_sha1, size, description, " + \ - "mod_date, file_path FROM (files JOIN permissions ON" + \ - " files.fid=permissions.fid) WHERE account=? AND requester=?" +\ - " AND hash_sha1=?" - else: - data = (account, requester, name) - sql = "SELECT relative_path, hash_sha1, size, description, " + \ - "mod_date, file_path 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: - return result[0] - - def get_files_name(self, account, requester): - result = self.get_files(account, requester) - flist = [] - for r in result: - flist.append(r[0]) - return flist - - def add_file(self, account, requester, file_): - """ - >>> file_ = ('file_path', 'relative_path', 'hash', 999, 'description', \ - 'date', False) - >>> add_file('account@gajim', 'requester@jabber', file_) - 1 - >>> _delete_file(1) - """ - self._check_duplicate(account, requester, file_) - requester = app.get_jid_without_resource(requester) - c = self.conn.cursor() - c.execute("INSERT INTO files (file_path, " + - "relative_path, hash_sha1, size, description, mod_date, " + - " is_dir) VALUES (?,?,?,?,?,?,?)", file_) - fid = c.lastrowid - permission_data = (fid, account, requester) - c.execute("INSERT INTO permissions VALUES (?,?,?)", permission_data) - self.conn.commit() - c.close() - return fid - - def _check_duplicate(self, account, requester, file_): - c = self.conn.cursor() - data = (account, requester, file_[1]) - c.execute("SELECT * FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=?" + - " AND relative_path=? ", data) - result = c.fetchall() - if file_[2] != '': - 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) - result.extend(c.fetchall()) - if len(result) > 0: - raise Exception('Duplicated entry') - c.close() - - def _delete_file(self, fid): - c = self.conn.cursor() - data = (fid, ) - c.execute("DELETE FROM files WHERE fid=?", data) - self.conn.commit() - c.close() - - def _delete_dir(self, dir_, account, requester): - c = self.conn.cursor() - data = (account, requester, dir_, dir_ + '/%') - sql = "DELETE FROM files WHERE fid IN " + \ - " (SELECT files.fid FROM files, permissions WHERE" + \ - " files.fid=permissions.fid AND account=?"+ \ - " AND requester=? AND (relative_path=? OR relative_path LIKE ?))" - c.execute(sql, data) - self.conn.commit() - c.close() - - def delete(self, account, requester, relative_path): - c = self.conn.cursor() - data = (account, requester, relative_path) - c.execute("SELECT files.fid, is_dir FROM (files JOIN permissions ON" + - " files.fid=permissions.fid) WHERE account=? AND requester=? AND " + - "relative_path=? ", data) - result = c.fetchone() - c.close() - if result[1] == 0: - self._delete_file(result[0]) - else: - self._delete_dir(relative_path, account, requester) - - def delete_all(self, account, requester): - c = self.conn.cursor() - data = (account, requester) - sql = "DELETE FROM files WHERE fid IN (SELECT fid FROM permissions" + \ - " WHERE account=? AND requester=?)" - c.execute(sql, data) - self.conn.commit() - c.close() - - -if __name__ == "__main__": - """ - DELETE DATABASE FILE BEFORE RUNNING TESTS - """ - import doctest - path = sys.path[0] - path = path + '/' + 'shared_files.db' - conn = sqlite3.connect(path) - # Enable foreign keys contraints - conn.cursor().execute("pragma foreign_keys = on") - FilesharingDatabase.create_database() - doctest.testmod() diff --git a/file_sharing/fileshare_window.py b/file_sharing/fileshare_window.py deleted file mode 100644 index e9e19c1..0000000 --- a/file_sharing/fileshare_window.py +++ /dev/null @@ -1,399 +0,0 @@ -import gtk -import gobject -from common import gajim -from common import helpers -from common.file_props import FilesProp -import fshare -import os -import fshare_protocol - -class FileShareWindow(gtk.Window): - - def __init__(self, plugin): - self.plugin = plugin - gtk.Window.__init__(self) - self.set_title('File Share') - self.connect('delete_event', self.delete_event) - self.set_position(gtk.WIN_POS_CENTER) - self.set_default_size(400, 400) - # Children - self.notebook = gtk.Notebook() - # Browse page - self.bt_search = gtk.Button('Search') - self.bt_search.connect('clicked', self.on_bt_search_clicked) - self.entry_search = gtk.Entry(max=0) - self.entry_search.set_size_request(300, -1) - self.exp_advance = gtk.Expander('Advance') - self.ts_search = gtk.TreeStore(gobject.TYPE_STRING) - self.tv_search = gtk.TreeView(self.ts_search) - self.tv_search.connect('row-expanded', self.row_expanded) - self.tv_search.connect('button-press-event', - self.on_treeview_button_press_event) - self.browse_popup = gtk.Menu() - mi_download = gtk.MenuItem('Download') - mi_property = gtk.MenuItem('Property') - mi_download.show() - mi_property.show() - mi_download.connect('activate', self.on_download_clicked) - self.browse_popup.append(mi_download) - self.browse_popup.append(mi_property) - self.browse_sw = gtk.ScrolledWindow() - self.browse_sw.add(self.tv_search) - self.tvcolumn_browse = gtk.TreeViewColumn('') - self.tv_search.append_column(self.tvcolumn_browse) - self.cell_browse = gtk.CellRendererText() - self.tvcolumn_browse.pack_start(self.cell_browse, True) - self.tvcolumn_browse.add_attribute(self.cell_browse, 'text', 0) - self.lbl_browse = gtk.Label('Browse') - self.browse_hbox = gtk.HBox() - self.browse_hbox2 = gtk.HBox() - self.browse_vbox = gtk.VBox() - self.browse_hbox.pack_start(self.entry_search, True, True, 10) - self.browse_hbox.pack_start(self.bt_search, False, False, 10) - self.browse_vbox.pack_start(self.browse_hbox, False, False, 10) - self.browse_vbox.pack_start(self.exp_advance, False, False, 10) - self.browse_vbox.pack_start(self.browse_sw, True, True, 10) - self.notebook.append_page(self.browse_vbox, self.lbl_browse) - # file references for tv_search - self.browse_fref = {} - # contact references for tv_search - self.browse_jid = {} - # Information of the files inserted in the treeview: name, size, etc - self.brw_file_info = {} - # dummy row children so that we can get expanders - self.empty_row_child = {} - # Manage page - self.bt_add_file = gtk.Button('Add file') - self.bt_add_file.connect('clicked', self.add_file) - self.bt_add_dir = gtk.Button('Add directory') - self.bt_add_dir.connect('clicked', self.add_directory) - self.bt_remove = gtk.Button('Remove') - self.bt_remove.connect('clicked', self.remove_file_clicked) - self.bt_remove_all = gtk.Button('Remove all') - self.bt_remove_all.connect('clicked', self.remove_all_clicked) - self.ts_contacts = gtk.TreeStore(gobject.TYPE_STRING) - self.cbb_contacts = gtk.ComboBoxEntry(self.ts_contacts) - self.cbb_contacts.connect('changed', self.__check_combo_edit) - cbb_entry = self.cbb_contacts.child - self.cbb_completion = gtk.EntryCompletion() - cbb_entry.set_completion(self.cbb_completion) - self.cbb_completion.set_model(self.ts_contacts) - self.cbb_completion.set_text_column(0) - self.bt_stophash = gtk.Button('Calculating hash') - self.lbl_manage = gtk.Label('Manage Shared Files') - self.ts_files = gtk.TreeStore(gobject.TYPE_STRING) - self.tv_files = gtk.TreeView(self.ts_files) - self.treeSelection_files = self.tv_files.get_selection() - self.treeSelection_files.connect('changed', self.row_selected) - self.manage_sw = gtk.ScrolledWindow() - self.manage_sw.add(self.tv_files) - self.tvcolumn = gtk.TreeViewColumn('') - self.tv_files.append_column(self.tvcolumn) - self.cell = gtk.CellRendererText() - self.tvcolumn.pack_start(self.cell, True) - self.tvcolumn.add_attribute(self.cell, 'text', 0) - self.pb_filehash = gtk.ProgressBar() - self.manage_hbox = gtk.HBox() - self.manage_hbox2 = gtk.HBox() - self.manage_vbox = gtk.VBox() - self.manage_vbox2 = gtk.VBox() - self.manage_hbox.pack_start(self.bt_stophash , False, False, 10) - self.manage_hbox.pack_start(self.pb_filehash, True, True, 10) - self.manage_hbox.set_sensitive(False) - self.manage_vbox.pack_start(self.cbb_contacts, False, False, 10) - self.manage_vbox.pack_start(self.manage_hbox, False, False, 10) - self.manage_hbox2.pack_start(self.manage_sw, True, True, 10) - self.manage_hbox2.pack_start(self.manage_vbox2, False, False, 10) - self.manage_vbox2.pack_start(self.bt_add_file, False, False, 10) - self.manage_vbox2.pack_start(self.bt_add_dir, False, False, 10) - self.manage_vbox2.pack_start(self.bt_remove, False, False, 10) - self.manage_vbox2.pack_start(self.bt_remove_all, False, False, 10) - self.manage_vbox2.set_sensitive(False) - self.manage_vbox.pack_start(self.manage_hbox2, True, True, 10) - self.notebook.append_page(self.manage_vbox, self.lbl_manage) - # Preferences page - self.lbl_pref = gtk.Label('Preferences') - self.entry_dir_pref = gtk.Entry(max=0) - self.entry_dir_pref.set_text(self.plugin.config['incoming_dir']) - self.bt_sel_dir_pref = gtk.Button('Select dir', gtk.STOCK_OPEN) - self.bt_sel_dir_pref.connect('clicked', self.on_bt_sel_dir_pref_clicked) - self.frm_dir_pref = gtk.Frame('Incoming files directory') - self.frm_dir_pref.set_shadow_type(gtk.SHADOW_IN) - self.pref_hbox = gtk.HBox() - self.pref_vbox = gtk.VBox() - self.pref_hbox.pack_start(self.entry_dir_pref, True, True, 10) - self.pref_hbox.pack_start(self.bt_sel_dir_pref, False, False, 10) - self.frm_dir_pref.add(self.pref_hbox) - self.pref_vbox.pack_start(self.frm_dir_pref, False, False, 10) - self.notebook.append_page(self.pref_vbox , self.lbl_pref) - self.add(self.notebook) - self.show_all() - - def set_account(self, account): - self.account = account - # connect window to protocol - pro = fshare.FileSharePlugin.prohandler[self.account] - pro.set_window(self) - - def add_file(self, widget): - dialog = gtk.FileChooserDialog('Add file to be shared', self, - gtk.FILE_CHOOSER_ACTION_OPEN, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, - gtk.RESPONSE_OK) - ) - dialog.set_select_multiple(True) - response = dialog.run() - if response == gtk.RESPONSE_OK: - file_list = dialog.get_filenames() - self.add_items_tvcontacts(file_list) - dialog.destroy() - - def add_directory(self, widget): - dialog = gtk.FileChooserDialog('Add directory to be shared', self, - gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, - (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, - gtk.RESPONSE_ACCEPT) - ) - response = dialog.run() - if response == gtk.RESPONSE_ACCEPT: - file_list = [] - file_list.append(dialog.get_filename()) - self.add_items_tvcontacts(file_list) - dialog.destroy() - - def add_file_list(self, flist, treestore, fref = {}, root = None): - # file references to their in the treeStore - for f in flist: - # keeps track of the relative dir path - tail = '' - # keeps track of the parent row - parent = None - dirpath = f.split('/') - if len(dirpath) == 1: - # Top level file, it doesnt have parent, add it right away - fref[dirpath[0]] = treestore.insert(root, 0, (dirpath[0],)) - else: - for dir_ in dirpath: - if tail + dir_ not in fref: - fref[tail + dir_] = treestore.append(parent, (dir_,)) - parent = fref[tail + dir_] - tail = tail + dir_ + '/' - return fref - - def __convert_date(self, epoch): - # Converts date-time from seconds from epoch to iso 8601 - import time, datetime - ts = time.gmtime(epoch) - dt = datetime.datetime(ts.tm_year, ts.tm_mon, ts.tm_mday, ts.tm_hour, - ts.tm_min, ts.tm_sec) - return dt.isoformat() - - def add_items_tvcontacts(self, file_list, parentdir = None, parent = None): - # TODO: execute this method inside of a thread - for f in file_list: - # Relative name to be used internally in the shared folders - relative_name = f.split('/')[-1] - if parentdir: - relative_name = parentdir + '/' + relative_name - short_name = relative_name.split('/')[-1] - if short_name[0] == '.': - # Return if it is a hidden file - return - if parent: - row = self.ts_files.append(parent, (short_name,)) - else: - row = self.ts_files.insert(None, 0, (short_name,)) - # File info - size = os.path.getsize(f) - is_dir = os.path.isdir(f) - mod_date = os.path.getmtime(f) - mod_date = self.__convert_date(mod_date) - # TODO: add hash - file_ = (f, relative_name, '', size, '', mod_date, is_dir) - requester = self.cbb_contacts.get_active_text() - try: - fid = self.plugin.database.add_file(self.account, requester, - file_) - except Exception, e: - if e == 'Duplicated entry': - print 'Error: ' + e - continue - else: - raise Exception(e) - if is_dir: - tmpfl = os.listdir(f) - fl = [] - for item in tmpfl: - fl.append(f + '/' + item) - self.add_items_tvcontacts(fl, relative_name, row) - - def add_contact_browse(self, contact): - fjid = contact.get_full_jid() - jid = gajim.get_jid_without_resource(fjid) - contacts = gajim.contacts.get_contacts(self.account, jid) - for con in contacts: - if con.show in ('offline', 'error') and not \ - con.supports(fshare_protocol.NS_FILE_SHARING): - break - cjid = con.get_full_jid() - r = self.ts_search.insert(None, 0, (cjid, )) - self.browse_jid[cjid] = r - pro = fshare.FileSharePlugin.prohandler[self.account] - # Request list of files from peer - stanza = pro.request(cjid) - if pro.conn.connection: - pro.conn.connection.send(stanza) - - def add_contact_manage(self, contact): - self.contacts_rows = [] - self.cbb_contacts.grab_focus() - for c in gajim.contacts.iter_contacts(self.account): - jid = gajim.get_jid_without_resource(c.get_full_jid()) - r = self.ts_contacts.insert(None, len(self.ts_contacts), (jid, )) - if c.get_full_jid() == contact.get_full_jid(): - self.cbb_contacts.set_active_iter(r) - self.contacts_rows.append(r) - self.manage_vbox2.set_sensitive(True) - self.bt_remove.set_sensitive(False) - self.add_file_list(self.plugin.database.get_files_name(self.account, - gajim.get_jid_without_resource(contact.get_full_jid())), - self.ts_files) - - def delete_event(self, widget, data=None): - fshare.FileSharePlugin.filesharewindow = {} - return False - - def __check_combo_edit(self, widget, data=None): - self.ts_files.clear() - entry = self.cbb_contacts.child - contact = entry.get_text() - self.manage_vbox2.set_sensitive(False) - for i in self.contacts_rows: - # If the contact in the comboboxentry is include inside of the - # combobox - if contact == self.ts_contacts.get_value(i, 0): - self.add_file_list(self.plugin.database.get_files_name( - self.account, contact), self.ts_files) - self.manage_vbox2.set_sensitive(True) - self.bt_remove.set_sensitive(False) - break - - def remove_file_clicked(self, widget, data=None): - entry = self.cbb_contacts.child - contact = entry.get_text() - sel = self.treeSelection_files.get_selected() - relative_name = self.ts_files.get_value(sel[1], 0) - self.ts_files.remove(sel[1]) - self.plugin.database.delete(self.account, contact, relative_name) - widget.set_sensitive(False) - - def remove_all_clicked(self, widget, data=None): - entry = self.cbb_contacts.child - contact = entry.get_text() - self.plugin.database.delete_all(self.account, contact) - self.ts_files.clear() - - def row_selected(self, widget, data=None): - # When row is selected in tv_files - sel = self.treeSelection_files.get_selected() - if not sel[1]: - return - depth = self.ts_files.iter_depth(sel[1]) - # Don't remove file and dirs that aren't at the root level - if depth == 0: - self.bt_remove.set_sensitive(True) - else: - self.bt_remove.set_sensitive(False) - - def row_expanded(self, widget, iter_, path, data=None): - name = None - for key in self.empty_row_child: - parent = self.ts_search.iter_parent(self.empty_row_child[key]) - p = self.ts_search.get_path(parent) - if p == path: - name = key - break - if name: - # if we found that the expanded row is the parent of the empty row - # remove it from the treestore and empty_row dictionary. Then ask - # peer for list of files of that directory - i = self.empty_row_child[name] - pro = fshare.FileSharePlugin.prohandler[self.account] - contact = self.get_contact_from_iter(self.ts_search, i) - contact = gajim.contacts.get_contact_with_highest_priority( - self.account, contact) - stanza = pro.request(contact.get_full_jid(), name, isFile=False) - if pro.conn.connection: - pro.conn.connection.send(stanza) - self.ts_search.remove(i) - del self.empty_row_child[name] - - def get_contact_from_iter(self, treestore, iter_): - toplevel = treestore.get_iter_root() - while toplevel: - if treestore.is_ancestor(toplevel, iter_): - return gajim.get_jid_without_resource(treestore.get_value( - toplevel, 0)) - toplevel = treestore.iter_next(toplevel) - - def on_treeview_button_press_event(self, treeview, event): - if event.button == 3: - x = int(event.x) - y = int(event.y) - time = event.time - pthinfo = treeview.get_path_at_pos(x, y) - if pthinfo is not None: - path, col, cellx, celly = pthinfo - treeview.grab_focus() - treeview.set_cursor( path, col, 0) - treestore = treeview.get_model() - it = treestore.get_iter(path) - if treestore.iter_depth(it) != 0: - self.browse_popup.popup(None, None, None, event.button, time) - return True - - def on_bt_search_clicked(self, widget, data=None): - pass - - def on_download_clicked(self, widget, data=None): - tree, row = self.tv_search.get_selection().get_selected() - path = tree.get_path(row) - file_info = self.brw_file_info[path] - fjid = self.get_contact_from_iter(tree, row) - # Request the file - file_path = os.path.join(self.plugin.config['incoming_dir'], - file_info[0]) - sid = helpers.get_random_string_16() - new_file_props = FilesProp.getNewFileProp(self.account, sid) - new_file_props.file_name = file_path - print file_path - new_file_props.name = file_info[0] - new_file_props.desc = file_info[4] - new_file_props.size = file_info[2] - new_file_props.date = file_info[1] - new_file_props.hash_ = None if file_info[3] == '' else file_info[3] - new_file_props.type_ = 'r' - tsid = gajim.connections[self.account].start_file_transfer(fjid, - new_file_props, True) - new_file_props.transport_sid = tsid - ft_window = gajim.interface.instances['file_transfers'] - contact = gajim.contacts.get_contact_from_full_jid(self.account, fjid) - ft_window .add_transfer(self.account, contact, new_file_props) - - def on_bt_sel_dir_pref_clicked(self, widget, data=None): - chooser = gtk.FileChooserDialog(title='Incoming files directory', - action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER, - buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, - gtk.RESPONSE_OK)) - response = chooser.run() - if response == gtk.RESPONSE_OK: - file_name = chooser.get_filename() - self.entry_dir_pref.set_text(file_name) - self.plugin.config['incoming_dir'] = file_name - chooser.destroy() - - -if __name__ == "__main__": - f = FileShareWindow(None) - f.show() - gtk.main() diff --git a/file_sharing/fshare.py b/file_sharing/fshare.py deleted file mode 100644 index 7b490ae..0000000 --- a/file_sharing/fshare.py +++ /dev/null @@ -1,182 +0,0 @@ -# -*- coding: utf-8 -*- -## -import database -import gtk -import os -import base64 -import urllib2 -from plugins import GajimPlugin -from plugins.helpers import log_calls -import gui_menu_builder -import gtkgui_helpers -from common import gajim -from fileshare_window import FileShareWindow -import fshare_protocol -from common import ged -from common import caps_cache -from common import xmpp -from plugins.gui import GajimPluginConfigDialog - - -class FileSharePlugin(GajimPlugin): - - filesharewindow = {} - prohandler = {} - - @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) - # 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) - self.events_handlers = { - 'raw-iq-received': (ged.CORE, self._nec_raw_iq) - } - - def activate(self): - self.activated = True - # Add fs feature - if fshare_protocol.NS_FILE_SHARING not in gajim.gajim_common_features: - gajim.gajim_common_features.append(fshare_protocol.NS_FILE_SHARING) - self._compute_caps_hash() - # Replace the contact menu - self.__get_contact_menu = gui_menu_builder.get_contact_menu - gui_menu_builder.get_contact_menu = self.contact_menu - # Replace get_file_info - for account in gajim.contacts.get_accounts(): - conn = gajim.connections[account] - self._get_file_info = conn.get_file_info - conn.get_file_info = self.get_file_info - - def deactivate(self): - self.activated = False - # Remove fs feature - if fshare_protocol.NS_FILE_SHARING not in gajim.gajim_common_features: - gajim.gajim_common_features.remove(fshare_protocol.NS_FILE_SHARING) - self._compute_caps_hash() - # Restore the contact menu - gui_menu_builder.get_contact_menu = self.__get_contact_menu - # Restore get_file_info - for account in gajim.contacts.get_accounts(): - conn = gajim.connections[account] - conn.get_file_info = self._get_file_info - - def _compute_caps_hash(self): - for a in gajim.connections: - gajim.caps_hash[a] = caps_cache.compute_caps_hash([ - gajim.gajim_identity], gajim.gajim_common_features + \ - gajim.gajim_optional_features[a]) - # re-send presence with new hash - connected = gajim.connections[a].connected - if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': - gajim.connections[a].change_status(gajim.SHOW_LIST[connected], - gajim.connections[a].status) - - def _nec_raw_iq(self, obj): - if obj.stanza.getTag('match', - namespace=fshare_protocol.NS_FILE_SHARING) and self.activated: - account = obj.conn.name - pro = FileSharePlugin.prohandler[account] - pro.handler(obj.stanza) - raise xmpp.NodeProcessed - - def __get_contact_menu(self, contact, account): - raise NotImplementedError - - def contact_menu(self, contact, account): - menu = self.__get_contact_menu(contact, account) - fs = gtk.MenuItem('File sharing') - submenu = gtk.Menu() - fs.set_submenu(submenu) - bf = gtk.MenuItem('Browse files') - bf.connect('activate', self.browse_menu_clicked, account, contact) - msf = gtk.MenuItem('Manage shared files') - msf.connect('activate', self.manage_menu_clicked, account, contact) - enable_fs = gtk.CheckMenuItem('Enable file sharing') - enable_fs.set_active(True) - submenu.attach(bf, 0, 1, 0, 1) - submenu.attach(msf, 0, 1, 1, 2) - submenu.attach(enable_fs, 0, 1, 2, 3) - if gajim.account_is_disconnected(account) or \ - contact.show in ('offline', 'error') or not \ - contact.supports(fshare_protocol.NS_FILE_SHARING): - bf.set_sensitive(False) - submenu.show() - bf.show() - msf.show() - enable_fs.show() - fs.show() - menu.attach(fs, 0, 1, 3, 4) - return menu - - def _get_file_info(self, peerjid, hash_=None, name=None, account=None): - raise NotImplementedError - - def get_file_info(self, peerjid, hash_=None, name=None, account=None): - file_info = self._get_file_info(hash_, name) - if file_info: - return file_info - raw_info = self.database.get_file(account, peerjid, hash_, name) - file_info = {'name': raw_info[0], - 'file-name' : raw_info[5], - 'hash' : raw_info[1], - 'size' : raw_info[2], - 'date' : raw_info[4], - 'peerjid' : peerjid - } - - return file_info - - def __get_fsw_instance(self, account): - # Makes sure we only have one instance of the window per account - if account not in FileSharePlugin.filesharewindow: - FileSharePlugin.filesharewindow[account] = fsw = FileShareWindow( - self) - FileSharePlugin.prohandler[account].set_window(fsw) - return FileSharePlugin.filesharewindow[account] - - def __init_window(self, account, contact): - fsw = self.__get_fsw_instance(account) - fsw.set_account(account) - fsw.contacts_rows = [] - fsw.ts_contacts.clear() - fsw.ts_search.clear() - # Add information to widgets - fsw.add_contact_manage(contact) - fsw.add_contact_browse(contact) - return fsw - - def manage_menu_clicked(self, widget, account, contact): - fsw = self.__init_window(account, contact) - fsw.notebook.set_current_page(1) - - def browse_menu_clicked(self, widget, account, contact): - fsw = self.__init_window(account, contact) - fsw.notebook.set_current_page(0) - -class FileSharePluginConfigDialog(GajimPluginConfigDialog): - def init(self): - self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( - 'config_dialog.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain('gajim_plugins') - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['hbox111']) - hbox = self.xml.get_object('hbox111') - self.child.pack_start(hbox) - self.connect('hide', self.on_hide) - - 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() diff --git a/file_sharing/fshare_protocol.py b/file_sharing/fshare_protocol.py deleted file mode 100644 index 3247b46..0000000 --- a/file_sharing/fshare_protocol.py +++ /dev/null @@ -1,163 +0,0 @@ -from common import xmpp -from common import helpers -from common import gajim -from common import XMPPDispatcher -from common.xmpp import Hashes -# Namespace for file sharing -NS_FILE_SHARING = 'http://gajim.org/protocol/filesharing' - -class Protocol(): - - 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) - 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 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) - 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') - 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 - - def set_window(self, fsw): - self.fsw = fsw - - -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) - diff --git a/file_sharing/manifest.ini b/file_sharing/manifest.ini deleted file mode 100644 index c3297fd..0000000 --- a/file_sharing/manifest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[info] -name: File Sharing -short_name: fshare -#version: 0.1.1 -description: This plugin allows you to share folders with your peers using jingle file transfer. -authors: Jefry Lagrange -homepage: www.google.com - -max_gajim_version: 0.15.9 diff --git a/gnome_session_manager/__init__.py b/gnome_session_manager/__init__.py deleted file mode 100644 index 4f4909c..0000000 --- a/gnome_session_manager/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from plugin import GnomeSessionManagerPlugin diff --git a/gnome_session_manager/gnome_session_manager.png b/gnome_session_manager/gnome_session_manager.png deleted file mode 100644 index 4196d6a..0000000 Binary files a/gnome_session_manager/gnome_session_manager.png and /dev/null differ diff --git a/gnome_session_manager/manifest.ini b/gnome_session_manager/manifest.ini deleted file mode 100644 index 88a204b..0000000 --- a/gnome_session_manager/manifest.ini +++ /dev/null @@ -1,8 +0,0 @@ -[info] -name: GNOME SessionManager -short_name: gnome_session_manager -version: 0.1.3 -description: Set and react on GNOME Session presence settings -authors: Philippe Normand -homepage: http://base-art.net -max_gajim_version: 0.15.9 diff --git a/gnome_session_manager/plugin.py b/gnome_session_manager/plugin.py deleted file mode 100644 index ffb9268..0000000 --- a/gnome_session_manager/plugin.py +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 -*- - -## Copyright (C) 2010 Philippe Normand -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## - -import dbus -from common import app -from common import ged -from common import dbus_support -import gui_interface -from plugins import GajimPlugin -from plugins.helpers import log_calls, log - -GNOME_STATUS = [u'online', u'invisible', u'dnd', u'idle'] -PRESENCE_INTERFACE = "org.gnome.SessionManager.Presence" - -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 = {} - - @log_calls('GnomeSessionManagerPlugin') - def activate(self): - if not dbus_support.supported: - return - - self.bus = dbus_support.session_bus.SessionBus() - try: - self.session_presence = self.bus.get_object("org.gnome.SessionManager", - "/org/gnome/SessionManager/Presence") - except: - app.log.debug("GNOME SessionManager D-Bus service not found") - return - - self.active = True - app.ged.register_event_handler('our-show', ged.POSTGUI, - self.on_our_status) - self.bus.add_signal_receiver(self.gnome_presence_changed, - "StatusChanged", PRESENCE_INTERFACE) - - @log_calls('GnomeSessionManagerPlugin') - def deactivate(self): - if not dbus_support.supported or not self.active: - return - - self.active = False - self.bus.remove_signal_receiver(self.gnome_presence_changed, "StatusChanged", - dbus_interface=PRESENCE_INTERFACE) - app.ged.remove_event_handler('our-show', ged.POSTGUI, self.on_our_status) - - - def gnome_presence_changed(self, status, *args, **kw): - if not app.interface.remote_ctrl: - try: - import remote_control - app.interface.remote_ctrl = remote_control.Remote() - except: - return - remote_gajim = app.interface.remote_ctrl.signal_object - gajim_status = GNOME_STATUS[status] - accounts = remote_gajim.list_accounts() - for account in accounts: - message = remote_gajim.get_status_message(account) - remote_gajim.change_status(gajim_status, message, account) - - def on_our_status(self, network_event): - try: - gnome_status = GNOME_STATUS.index(network_event.show) - except ValueError: - print "GNOME SessionManager doesn't support %r status" % network_event.show - else: - self.session_presence.SetStatus(dbus.UInt32(gnome_status), - dbus_interface=PRESENCE_INTERFACE) diff --git a/gotr/__init__.py b/gotr/__init__.py deleted file mode 100644 index 8fa452d..0000000 --- a/gotr/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from otrmodule import OtrPlugin diff --git a/gotr/config_dialog.ui b/gotr/config_dialog.ui deleted file mode 100644 index b9dd8d9..0000000 --- a/gotr/config_dialog.ui +++ /dev/null @@ -1,382 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - False - True - - - True - False - False - Copy to clipboard - True - - - - - - True - True - - - True - vertical - - - True - 12 - 0 - none - - - True - 12 - - - True - vertical - 5 - - - True - 5 - - - True - Fingerprint: - - - False - 0 - - - - - True - <tt>-------- -------- -------- -------- -------- </tt> - True - True - - - 1 - - - - - False - 0 - - - - - (Re-)generate - True - True - True - - - - False - 1 - - - - - True - 12 - 0 - none - - - True - 12 - - - True - vertical - - - Enable private (Off-the-Record) messaging - True - True - False - True - - - - 0 - - - - - - 1 - - - - - Automatically start private messaging - True - True - False - True - - - - 2 - - - - - Require private messaging - True - True - False - True - - - - 3 - - - - - - - - - True - <b>Default OTR Settings</b> - True - - - - - False - 2 - - - - - - - - - True - 5 - - - True - <b>Off-the-Record settings for:</b> - True - - - False - 0 - - - - - True - account_store - - - - - 0 - - - - - 1 - - - - - - - False - 0 - - - - - - - True - OTR Settings - - - False - - - - - True - 12 - vertical - - - True - True - automatic - automatic - - - True - True - fingerprint_store - 0 - 5 - - - - True - Name - - - - 0 - - - - - - - True - Status - - - - 1 - - - - - - - True - Validated - - - - 2 - - - - - - - True - Fingerprint - - - - 3 - - - - - - - True - Account - - - - 4 - - - - - - - - - 0 - - - - - True - 5 - True - - - Verify Fingerprint - True - True - True - - - - 0 - - - - - Forget Fingerprint - True - True - True - - - - 1 - - - - - False - 1 - - - - - 1 - - - - - True - Known Fingerprints - - - 1 - False - - - - diff --git a/gotr/contact_otr_window.ui b/gotr/contact_otr_window.ui deleted file mode 100644 index 9e697b8..0000000 --- a/gotr/contact_otr_window.ui +++ /dev/null @@ -1,355 +0,0 @@ - - - - - - True - True - - - True - 5 - vertical - 5 - True - - - True - 0 - Your fingerprint: -<span weight="bold" face="monospace">01234567 89ABCDEF 01234567 89ABCDEF 01234567</span> - True - True - - - 0 - - - - - True - 0 - Purported fingerprint for asdfasdf@xyzxyzxyz.de: -<span weight="bold" face="monospace">01234567 89ABCDEF 01234567 89ABCDEF 01234567</span> - True - True - - - 1 - - - - - True - True - verifiedmodel - 0 - - - - 0 - - - - - 2 - - - - - - - True - Authentication - - - 1 - False - - - - - True - 0 - none - - - True - 5 - vertical - 5 - True - - - OTR version 2 allowed - True - True - False - True - True - - - 1 - - - - - Encryption required - True - True - False - True - - - 2 - - - - - Show others we understand OTR - True - True - False - True - True - - - 3 - - - - - Automatically initiate encryption if partner understands OTR - True - True - False - True - True - - - 4 - - - - - - - Use the default settings - True - True - False - True - True - - - - - 1 - - - - - True - OTR Settings - - - 1 - False - - - - - - - - - - - I have NOT verified that the purported fingerprint is in fact the correct fingerprint for that contact. - - - I have verified that the purported fingerprint is in fact the correct fingerprint for that contact. - - - - - False - - - True - vertical - - - True - 5 - vertical - 5 - - - True - label - True - True - - - 0 - - - - - True - - - Use question: - True - True - False - True - - - False - 0 - - - - - True - True - - - 1 - - - - - False - 1 - - - - - True - label - - - 2 - - - - - True - True - - - False - 3 - - - - - 0 - - - - - True - 5 - - - True - - - False - 0 - - - - - True - end - - - gtk-cancel - True - True - True - True - - - False - False - 0 - - - - - gtk-ok - True - True - True - True - - - False - False - 1 - - - - - 1 - - - - - False - 1 - - - - - - - True - False - Off-the-Record Encryption - True - - - - - True - OTR settings / fingerprint - True - - - - - - True - Authenticate contact - True - - - - - - True - Start / Refresh OTR - True - - - - - - True - False - End OTR - True - - - - - - - diff --git a/gotr/gotr.png b/gotr/gotr.png deleted file mode 100644 index 520915e..0000000 Binary files a/gotr/gotr.png and /dev/null differ diff --git a/gotr/manifest.ini b/gotr/manifest.ini deleted file mode 100644 index 07081fa..0000000 --- a/gotr/manifest.ini +++ /dev/null @@ -1,8 +0,0 @@ -[info] -name: Off-The-Record Encryption -short_name: gotr -version: 1.7.2 -description: Provide OTR encryption -authors: Kjell Braden -homepage: http://gajim-otr.pentabarf.de -max_gajim_version: 0.15.9 diff --git a/gotr/otrmodule.py b/gotr/otrmodule.py deleted file mode 100644 index ea6144d..0000000 --- a/gotr/otrmodule.py +++ /dev/null @@ -1,665 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -## otrmodule.py -## -## Copyright 2008-2012 Kjell Braden -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## - - -''' -Off-The-Record encryption plugin. - -:author: Kjell self.Braden -:since: 2008 -:copyright: Copyright 2008-2012 Kjell Braden -:license: GPL -''' - -MINVERSION = (1,0,0,'beta5') -MINCRYPTOVERSION = (2,1,0,'final',0) -IGNORE = True -PASS = False - -DEFAULTFLAGS = { - 'ALLOW_V1':False, - 'ALLOW_V2':True, - 'REQUIRE_ENCRYPTION':False, - 'SEND_TAG':True, - 'WHITESPACE_START_AKE':True, - 'ERROR_START_AKE':True, - } - -MMS = 1024 -PROTOCOL = 'xmpp' - -enc_tip = 'A private chat session is established to this contact ' \ - 'with this fingerprint' -unused_tip = 'A private chat session is established to this contact using ' \ - 'another fingerprint' -ended_tip = 'The private chat session to this contact has ended' -inactive_tip = 'Communication to this contact is currently ' \ - 'unencrypted' - -import logging -import os -import pickle -import time -import sys - -import common.xmpp -from common import gajim -from common import ged -from common.connection_handlers_events import MessageOutgoingEvent -from plugins import GajimPlugin -from message_control import TYPE_CHAT, MessageControl -from plugins.helpers import log_calls, log -from plugins.plugin import GajimPluginException - -import ui - -sys.path.insert(0, os.path.dirname(ui.__file__)) - -from HTMLParser import HTMLParser -from htmlentitydefs import name2codepoint - -HAS_CRYPTO = True -try: - import Crypto - if not hasattr(Crypto, 'version_info') \ - or Crypto.version_info < MINCRYPTOVERSION: - raise ImportError('PyCrypto not found or too old') -except ImportError: - HAS_CRYPTO = False - -HAS_POTR = True -try: - import potr - import potr.crypt - import potr.context - if not hasattr(potr, 'VERSION') or potr.VERSION < MINVERSION: - raise ImportError('old / unsupported python-otr version') - - potrrootlog = logging.getLogger('potr') - potrrootlog.handlers = [] - potrrootlog.propagate = False - gajimrootlog = logging.getLogger('gajim') - for h in gajimrootlog.handlers: - potrrootlog.addHandler(h) - - def get_jid_from_fjid(fjid): - return gajim.get_room_and_nick_from_fjid(fjid)[0] - - class GajimContext(potr.context.Context): - # self.peer is fjid - # self.jid does not contain resource - __slots__ = ['smpWindow', 'jid'] - - def __init__(self, account, peer): - super(GajimContext, self).__init__(account, peer) - self.jid = get_jid_from_fjid(peer) - self.trustName = self.jid - self.smpWindow = ui.ContactOtrSmpWindow(self) - - def inject(self, msg, appdata=None): - log.debug('inject(appdata=%s)', appdata) - msg = unicode(msg) - account = self.user.accountname - - stanza = common.xmpp.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) - gajim.connections[account].connection.send(stanza, now=True) - - def setState(self, newstate): - if self.state == potr.context.STATE_ENCRYPTED: - # we were encrypted - if newstate == potr.context.STATE_ENCRYPTED: - # and are still -> it's just a refresh - OtrPlugin.gajim_log( - _('Private conversation with %s refreshed.') % self.peer, - self.user.accountname, self.peer) - elif newstate == potr.context.STATE_FINISHED: - # and aren't anymore -> other side disconnected - OtrPlugin.gajim_log(_('%s has ended his/her private ' - 'conversation with you. You should do the same.') - % self.peer, self.user.accountname, self.peer) - else: - if newstate == potr.context.STATE_ENCRYPTED: - # we are now encrypted - trust = self.getCurrentTrust() - if trust is None: - fpr = str(self.getCurrentKey()) - OtrPlugin.gajim_log(_('New fingerprint for %(peer)s: %(fpr)s') - % {'peer': self.peer, 'fpr': fpr}, - self.user.accountname, self.peer) - self.setCurrentTrust('') - trustStr = 'authenticated' if bool(trust) else '*unauthenticated*' - OtrPlugin.gajim_log( - _('%(trustStr)s secured OTR conversation with %(peer)s started') - % {'trustStr': trustStr, 'peer': self.peer}, - self.user.accountname, self.peer) - - if self.state != potr.context.STATE_PLAINTEXT and \ - newstate == potr.context.STATE_PLAINTEXT: - # we are now plaintext - OtrPlugin.gajim_log( - _('Private conversation with %s lost.') % self.peer, - self.user.accountname, self.peer) - - super(GajimContext, self).setState(newstate) - OtrPlugin.update_otr(self.peer, self.user.accountname) - self.user.plugin.update_context_list() - - def getPolicy(self, key): - ret = self.user.plugin.get_flags(self.user.accountname, self.jid)[key] - log.debug('getPolicy(key=%s) = %s', key, ret) - return ret - - class GajimOtrAccount(potr.context.Account): - contextclass = GajimContext - def __init__(self, plugin, accountname): - global PROTOCOL, MMS - self.plugin = plugin - self.accountname = accountname - name = gajim.get_jid_from_account(accountname) - super(GajimOtrAccount, self).__init__(name, PROTOCOL, MMS) - self.keyFilePath = os.path.join(gajim.gajimpaths.data_root, accountname) - - def dropPrivkey(self): - try: - os.remove(self.keyFilePath + '.key3') - except IOError, e: - if e.errno != 2: - log.exception('IOError occurred when removing key file for %s', - self.name) - self.privkey = None - - def loadPrivkey(self): - try: - with open(self.keyFilePath + '.key3', 'rb') as keyFile: - return potr.crypt.PK.parsePrivateKey(keyFile.read())[0] - except IOError, e: - if e.errno != 2: - log.exception('IOError occurred when loading key file for %s', - self.name) - return None - - def savePrivkey(self): - try: - with open(self.keyFilePath + '.key3', 'wb') as keyFile: - keyFile.write(self.getPrivkey().serializePrivateKey()) - except IOError, e: - log.exception('IOError occurred when loading key file for %s', - self.name) - - def loadTrusts(self, newCtxCb=None): - ''' load the fingerprint trustdb ''' - # it has the same format as libotr, therefore the - # redundant account / proto field - try: - with open(self.keyFilePath + '.fpr', 'r') as fprFile: - for line in fprFile: - ctx, acc, proto, fpr, trust = line[:-1].split('\t') - - if acc != self.name or proto != PROTOCOL: - continue - - jid = get_jid_from_fjid(ctx) - self.setTrust(jid, fpr, trust) - except IOError, e: - if e.errno != 2: - log.exception('IOError occurred when loading fpr file for %s', - self.name) - - def saveTrusts(self): - try: - with open(self.keyFilePath + '.fpr', 'w') as fprFile: - for uid, trusts in self.trusts.iteritems(): - for fpr, trustVal in trusts.iteritems(): - fprFile.write('\t'.join( - (uid, self.name, PROTOCOL, fpr, trustVal))) - fprFile.write('\n') - except IOError, e: - log.exception('IOError occurred when loading fpr file for %s', - self.name) -except ImportError: - HAS_POTR = False - -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 = {} - - - if not HAS_POTR: - self.activatable = False - self.available_text = _('Can\'t find potr. Verify this ' \ - 'plugin\'s integrity.') - return - - if not HAS_CRYPTO: - self.activatable = False - self.available_text = _('PyCrypto not installed or too old.') - return - - - self.config_dialog = ui.OtrPluginConfigDialog(self) - 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.gui_extension_points = { - 'chat_control' : (self.cc_connect, self.cc_disconnect) - } - - for acc in gajim.contacts.get_accounts(): - self.us[acc] = GajimOtrAccount(self, acc) - self.us[acc].loadTrusts() - - acc = str(acc) - if acc not in self.config or None not in self.config[acc]: - self.config[acc] = {None:DEFAULTFLAGS.copy()} - self.update_context_list() - - @log_calls('OtrPlugin') - def activate(self): - if not HAS_CRYPTO or not HAS_POTR or not hasattr(potr, 'VERSION') \ - or potr.VERSION < MINVERSION: - raise GajimPluginException(self.available_text) - - def get_otr_status(self, account, contact): - ctx = self.us[account].getContext(contact.get_full_jid()) - - finished = ctx.state == potr.context.STATE_FINISHED - encrypted = finished or ctx.state == potr.context.STATE_ENCRYPTED - trusted = encrypted and bool(ctx.getCurrentTrust()) - return (encrypted, trusted, finished) - - def cc_connect(self, cc): - def update_otr(print_status=False): - enc_status, authenticated, finished = \ - self.get_otr_status(cc.account, cc.contact) - otr_status_text = '' - - if finished: - otr_status_text = u'finished OTR connection' - elif authenticated: - otr_status_text = u'authenticated secure OTR connection' - elif enc_status: - otr_status_text = u'*unauthenticated* secure OTR connection' - - cc._show_lock_image(enc_status, u'OTR', enc_status, True, - authenticated) - if print_status and otr_status_text: - cc.print_conversation_line(u'[OTR] %s' % otr_status_text, - 'status', '', None) - cc.update_otr = update_otr - cc.update_otr(True) - - # hijack authentication button with our submenu - def authbutton_cb(widget): - if not cc.gpg_is_active and not (cc.session and - cc.session.enable_encryption): - ui.get_otr_submenu(self, cc).get_submenu().popup(None, - None, None, 0, 0) - else: - cc._on_authentication_button_clicked(widget) - self.overwrite_handler(cc, cc.authentication_button, authbutton_cb) - - # hijack context menu - cc.orig_prepare_context_menu = cc.prepare_context_menu - def inject_menu(hide_buttonbar_items=False): - menu = cc.orig_prepare_context_menu(hide_buttonbar_items) - menu.insert(ui.get_otr_submenu(self, cc), 8) - return menu - cc.prepare_context_menu = inject_menu - - def cc_disconnect(self, cc): - try: - self.overwrite_handler(cc, cc.authentication_button, - cc._on_authentication_button_clicked) - cc.prepare_context_menu = cc.orig_prepare_context_menu - del cc.update_otr - except AttributeError: - pass - - def menu_settings_cb(self, item, control): - ctx = self.us[control.account].getContext(control.contact.get_full_jid()) - dlg = ui.ContactOtrWindow(self, ctx) - dlg.run() - dlg.destroy() - - def menu_start_cb(self, item, control): - gajim.nec.push_outgoing_event(MessageOutgoingEvent(None, - account=control.account, jid=control.contact.jid, - message=u'?OTRv?', type_='chat', - resource=control.contact.resource, is_loggable=False)) - - def menu_end_cb(self, item, control): - fjid = control.contact.get_full_jid() - thread_id = control.session.thread_id if control.session else None - - self.us[control.account].getContext(fjid).disconnect( - appdata={'session':control.session}) - - def menu_smp_cb(self, item, control): - ctx = self.us[control.account].getContext(control.contact.get_full_jid()) - ctx.smpWindow.show(False) - - @staticmethod - def overwrite_handler(window, control, handler): - for id_, v in window.handlers.iteritems(): - if v == control: - break - else: - raise LookupError - - del window.handlers[id_] - control.disconnect(id_) - id_ = control.connect('clicked', handler) - window.handlers[id_] = control - - def set_flags(self, value, account=None, contact=None): - if isinstance(account, unicode): - account = account.encode() - - if account not in self.config: - self.config[account] = {None:DEFAULTFLAGS.copy()} - - if account is None and contact is not None: - # don't set per-contact options without account - raise Exception("can't set contact flags without account") - - config = self.config[account] - config[contact] = value - - self.config[account] = config - - def get_flags(self, account=None, contact=None, fallback=True): - if isinstance(account, unicode): - account = account.encode() - - setting = DEFAULTFLAGS.copy() - if account in self.config: - setting.update(self.config[account][None]) - if contact in self.config[account] \ - and self.config[account][contact] is not None: - setting.update(self.config[account][contact]) - elif not fallback: - return None - return setting - - def update_context_list(self): - self.config_dialog.fpr_model.clear() - for us in self.us.itervalues(): - usedFpr = set() - for fjid, ctx in us.ctxs.iteritems(): - # get active contexts first - key = ctx.getCurrentKey() - if not key: - continue - fpr = key.cfingerprint() - usedFpr.add(fpr) - - human_hash = potr.human_hash(fpr) - trust = bool(us.getTrust(ctx.trustName, fpr)) - - if ctx.state == potr.context.STATE_ENCRYPTED: - state = "encrypted" - tip = enc_tip - elif ctx.state == potr.context.STATE_FINISHED: - state = "finished" - tip = ended_tip - else: - state = 'inactive' - tip = inactive_tip - - self.config_dialog.fpr_model.append((fjid, state, trust, - '%s' % human_hash, us.name, tip, fpr)) - - for uid, trusts in us.trusts.iteritems(): - for fpr, trust in trusts.iteritems(): - if fpr in usedFpr: - continue - - state = 'inactive' - tip = inactive_tip - - human_hash = potr.human_hash(fpr) - - self.config_dialog.fpr_model.append((uid, state, bool(trust), - '%s' % human_hash, us.name, tip, fpr)) - - @classmethod - def gajim_log(cls, msg, account, fjid, no_print=False, - is_status_message=True, thread_id=None): - if not isinstance(fjid, unicode): - fjid = unicode(fjid) - if not isinstance(account, unicode): - account = unicode(account) - - resource = gajim.get_resource_from_jid(fjid) - jid = gajim.get_jid_without_resource(fjid) - tim = time.localtime() - - if is_status_message is True: - if not no_print: - ctrl = cls.get_control(fjid, account) - 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]) - else: - session = gajim.connections[account].get_or_create_session(fjid, - thread_id) - session.received_thread_id |= bool(thread_id) - session.last_receive = time.time() - - if not session.control: - # look for an existing chat control without a session - ctrl = cls.get_control(fjid, account) - if ctrl: - 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) - - @classmethod - def update_otr(cls, user, acc, print_status=False): - ctrl = cls.get_control(user, acc) - if ctrl: - ctrl.update_otr(print_status) - - @staticmethod - def get_control(fjid, account): - # first try to get the window with the full jid - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if ctrl: - # got one, be happy - return ctrl - - # otherwise try without the resource - ctrl = gajim.interface.msg_win_mgr.get_control( - gajim.get_jid_without_resource(fjid), account) - # but only use it when it's not a GC window - if ctrl and ctrl.TYPE_ID == TYPE_CHAT: - return ctrl - - 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): - return PASS - - try: - ctx = self.us[account].getContext(event.fjid) - msgtxt, tlvs = ctx.receiveMessage(event.msgtxt, - appdata={'session':event.session}) - except potr.context.NotOTRMessage, e: - # received message was not OTR - pass it on - return PASS - except potr.context.UnencryptedMessage, e: - # we are encrypted but got some plaintext - # display it with a warning - tlvs = [] - msgtxt = _('The following message received from %(jid)s was ' - '*not encrypted*: [%(error)s]') % {'jid': event.fjid, - 'error': e.args[0]} - except potr.context.NotEncryptedError, e: - # we got some encrypted data - # but we don't have an encrypted session - self.gajim_log(_('The encrypted message received from %s is ' - 'unreadable, as you are not currently communicating ' - 'privately') % event.fjid, account, event.fjid) - return IGNORE - except potr.context.ErrorReceived, e: - # got a protocol error - self.gajim_log(_('We received the following OTR error ' - 'message from %(jid)s: [%(error)s]') % {'jid': event.fjid, - 'error': e.args[0].error}, - account, event.fjid) - return IGNORE - except potr.crypt.InvalidParameterError, e: - # received a packet we cannot process (probably tampered or - # sent to wrong session) - self.gajim_log(_('We received an unreadable OTR message ' - 'from %(jid)s. It has probably been tampered with, ' - 'or was sent from an older OTR session.') - % {'jid':event.fjid}, account, event.fjid) - return IGNORE - except RuntimeError, e: - # generic library bug? - self.gajim_log(_('The following error occurred when trying to ' - 'decrypt a message from %(jid)s: [%(error)s]') % { - 'jid': event.fjid, 'error': e}, - account, event.fjid) - return IGNORE - - if ctx is not None: - ctx.smpWindow.handle_tlv(tlvs) - - stripper = HTMLStripper() - stripper.feed(unicode(msgtxt or '')) - event.msgtxt = stripper.stripped_data - event.stanza.setBody(event.msgtxt) - event.stanza.setXHTML(msgtxt) - - 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) - - try: - newmsg = self.us[event.account].getContext(fjid).sendMessage( - potr.context.FRAGMENT_SEND_ALL_BUT_LAST, message, - appdata={'session':event.session}) - 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) - 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 - - return PASS - - -class HTMLStripper(HTMLParser): - def reset(self): - self.stripped_data = '' - HTMLParser.reset(self) - - def handle_data(self, data): - self.stripped_data += data - - def handle_starttag(self, tag, attrs): - if tag == 'br': - self.stripped_data += '\n' - - def handle_entityref(self, name): - c = unichr(name2codepoint[name]) - self.stripped_data += c - def handle_charref(self, name): - if name.startswith('x'): - c = unichr(int(name[1:], 16)) - else: - c = unichr(int(name)) - self.stripped_data += c - - def unknown_decl(self, data): - if data.startswith('CDATA['): - self.stripped_data += data[6:] - - def feed(self, data): - data = data.replace('\n', '') - HTMLParser.feed(self, data) - -def escape(s): - '''Replace special characters "&", "<" and ">" to HTML-safe sequences. - If the optional flag quote is true, the quotation mark character (") - is also translated.''' - s = s.replace("&", "&") # Must be done first! - s = s.replace("<", "<") - s = s.replace(">", ">") - s = s.replace("\n", "
") - return s - -## TODO: -## - disconnect ctxs on disconnect diff --git a/gotr/potr/__init__.py b/gotr/potr/__init__.py deleted file mode 100644 index c6e8d02..0000000 --- a/gotr/potr/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2011-2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -# some python3 compatibilty -from __future__ import unicode_literals - -from potr import context -from potr import proto -from potr.utils import human_hash - -''' version is: (major, minor, patch, sub) with sub being one of 'alpha', -'beta', 'final' ''' -VERSION = (1, 0, 0, 'beta7') diff --git a/gotr/potr/compatcrypto/__init__.py b/gotr/potr/compatcrypto/__init__.py deleted file mode 100644 index e0f82d0..0000000 --- a/gotr/potr/compatcrypto/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - - -from potr.compatcrypto.common import * - -from potr.compatcrypto.pycrypto import * diff --git a/gotr/potr/compatcrypto/common.py b/gotr/potr/compatcrypto/common.py deleted file mode 100644 index 61f2bea..0000000 --- a/gotr/potr/compatcrypto/common.py +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -# some python3 compatibilty -from __future__ import unicode_literals - -import logging -import struct - -from potr.utils import human_hash, bytes_to_long, unpack, pack_mpi - -DEFAULT_KEYTYPE = 0x0000 -pkTypes = {} -def registerkeytype(cls): - if cls.keyType is None: - raise TypeError('registered key class needs a type value') - pkTypes[cls.keyType] = cls - return cls - -def generateDefaultKey(): - return pkTypes[DEFAULT_KEYTYPE].generate() - -class PK(object): - 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): - raise NotImplementedError - def fingerprint(self): - raise NotImplementedError - - def serializePublicKey(self): - return struct.pack(b'!H', self.keyType) \ - + self.getSerializedPublicPayload() - - def getSerializedPublicPayload(self): - buf = b'' - for x in self.getPublicPayload(): - buf += pack_mpi(x) - return buf - - def getPublicPayload(self): - raise NotImplementedError - - def serializePrivateKey(self): - return struct.pack(b'!H', self.keyType) \ - + self.getSerializedPrivatePayload() - - def getSerializedPrivatePayload(self): - buf = b'' - for x in self.getPrivatePayload(): - buf += pack_mpi(x) - return buf - - def getPrivatePayload(self): - raise NotImplementedError - - def cfingerprint(self): - return '{0:040x}'.format(bytes_to_long(self.fingerprint())) - - @classmethod - def parsePrivateKey(cls, data): - implCls, data = cls.getImplementation(data) - 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) - return implCls.parsePayload(data) - - def __str__(self): - return human_hash(self.cfingerprint()) - def __repr__(self): - return '<{cls}(fpr=\'{fpr}\')>'.format( - cls=self.__class__.__name__, fpr=str(self)) - - @staticmethod - def getImplementation(data): - typeid, data = unpack(b'!H', data) - cls = pkTypes.get(typeid, None) - if cls is None: - raise NotImplementedError('unknown typeid %r' % typeid) - return cls, data diff --git a/gotr/potr/compatcrypto/pycrypto.py b/gotr/potr/compatcrypto/pycrypto.py deleted file mode 100644 index 2800431..0000000 --- a/gotr/potr/compatcrypto/pycrypto.py +++ /dev/null @@ -1,144 +0,0 @@ -# Copyright 2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -from Crypto import Cipher -from Crypto.Hash import SHA256 as _SHA256 -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 read_mpi, bytes_to_long, long_to_bytes - -def SHA256(data): - return _SHA256.new(data).digest() - -def SHA1(data): - return _SHA1.new(data).digest() - -def HMAC(key, data, mod): - return _HMAC.new(key, msg=data, digestmod=mod).digest() - -def SHA1HMAC(key, data): - return HMAC(key, data, _SHA1) - -def SHA256HMAC(key, data): - return HMAC(key, data, _SHA256) - -def SHA256HMAC160(key, data): - return SHA256HMAC(key, data)[:20] - -def AESCTR(key, counter=0): - if isinstance(counter, Number): - counter = Counter(counter) - if not isinstance(counter, Counter): - raise TypeError - return Cipher.AES.new(key, Cipher.AES.MODE_CTR, counter=counter) - -class Counter(object): - def __init__(self, prefix): - self.prefix = prefix - self.val = 0 - - def inc(self): - self.prefix += 1 - self.val = 0 - - def __setattr__(self, attr, val): - if attr == 'prefix': - self.val = 0 - super(Counter, self).__setattr__(attr, val) - - def __repr__(self): - return ''.format(p=self.prefix, v=self.val) - - def byteprefix(self): - return long_to_bytes(self.prefix, 8) - - def __call__(self): - bytesuffix = long_to_bytes(self.val, 8) - self.val += 1 - return self.byteprefix() + bytesuffix - -@common.registerkeytype -class DSAKey(common.PK): - keyType = 0x0000 - - def __init__(self, key=None, private=False): - self.priv = self.pub = None - - if not isinstance(key, tuple): - raise TypeError('4/5-tuple required for key') - - if len(key) == 5 and private: - self.priv = DSA.construct(key) - self.pub = self.priv.publickey() - elif len(key) == 4 and not private: - self.pub = DSA.construct(key) - else: - raise TypeError('wrong number of arguments for ' \ - 'private={0!r}: got {1} ' - .format(private, len(key))) - - def getPublicPayload(self): - return (self.pub.p, self.pub.q, self.pub.g, self.pub.y) - - def getPrivatePayload(self): - return (self.priv.p, self.priv.q, self.priv.g, self.priv.y, self.priv.x) - - def fingerprint(self): - return SHA1(self.getSerializedPublicPayload()) - - def sign(self, data): - # 2 <= K <= q - K = random.randrange(2, self.priv.q) - r, s = self.priv.sign(data, K) - 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:]) - return self.pub.verify(data, (r, s)) - - def __hash__(self): - return bytes_to_long(self.fingerprint()) - - def __eq__(self, other): - if not isinstance(other, type(self)): - return False - return self.fingerprint() == other.fingerprint() - - def __ne__(self, other): - return not (self == other) - - @classmethod - def generate(cls): - privkey = DSA.generate(1024) - return cls((privkey.key.y, privkey.key.g, privkey.key.p, privkey.key.q, - privkey.key.x), private=True) - - @classmethod - def parsePayload(cls, data, private=False): - p, data = read_mpi(data) - q, data = read_mpi(data) - g, data = read_mpi(data) - y, data = read_mpi(data) - if private: - x, data = read_mpi(data) - return cls((y, g, p, q, x), private=True), data - return cls((y, g, p, q), private=False), data diff --git a/gotr/potr/context.py b/gotr/potr/context.py deleted file mode 100644 index 3ec3d74..0000000 --- a/gotr/potr/context.py +++ /dev/null @@ -1,568 +0,0 @@ -# Copyright 2011-2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -# some python3 compatibilty -from __future__ import unicode_literals - -try: - type(basestring) -except NameError: - # all strings are unicode in python3k - basestring = str - unicode = str - -# callable is not available in python 3.0 and 3.1 -try: - type(callable) -except NameError: - from collections import Callable - def callable(x): - return isinstance(x, Callable) - - -import base64 -import logging -import struct - -logger = logging.getLogger(__name__) - -from potr import crypt -from potr import proto -from potr import compatcrypto - -from time import time - -EXC_UNREADABLE_MESSAGE = 1 -EXC_FINISHED = 2 - -HEARTBEAT_INTERVAL = 60 -STATE_PLAINTEXT = 0 -STATE_ENCRYPTED = 1 -STATE_FINISHED = 2 -FRAGMENT_SEND_ALL = 0 -FRAGMENT_SEND_ALL_BUT_FIRST = 1 -FRAGMENT_SEND_ALL_BUT_LAST = 2 - -OFFER_NOTSENT = 0 -OFFER_SENT = 1 -OFFER_REJECTED = 2 -OFFER_ACCEPTED = 3 - -class Context(object): - def __init__(self, account, peername): - self.user = account - self.peer = peername - self.policy = {} - self.crypto = crypt.CryptEngine(self) - self.tagOffer = OFFER_NOTSENT - self.mayRetransmit = 0 - self.lastSend = 0 - self.lastMessage = None - self.state = STATE_PLAINTEXT - self.trustName = self.peer - - self.fragmentInfo = None - self.fragment = None - self.discardFragment() - - def getPolicy(self, key): - raise NotImplementedError - - def inject(self, msg, appdata=None): - raise NotImplementedError - - def policyOtrEnabled(self): - return self.getPolicy('ALLOW_V2') or self.getPolicy('ALLOW_V1') - - def discardFragment(self): - self.fragmentInfo = (0, 0) - self.fragment = [] - - def fragmentAccumulate(self, message): - '''Accumulate a fragmented message. Returns None if the fragment is - to be ignored, returns a string if the message is ready for further - processing''' - - 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) - 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 - - fragData = params[3] - - logger.debug(params) - - if n >= k == 1: - # first fragment - self.discardFragment() - self.fragmentInfo = (k, n) - self.fragment.append(fragData) - elif N == n >= k > 1 and k == K+1: - # accumulate - self.fragmentInfo = (k, n) - self.fragment.append(fragData) - else: - # bad, discard - self.discardFragment() - logger.warning('invalid fragmented message: %r', params) - return message - - if n == k > 0: - assembled = b''.join(self.fragment) - self.discardFragment() - return assembled - - return None - - def removeFingerprint(self, fingerprint): - self.user.removeFingerprint(self.trustName, fingerprint) - - def setTrust(self, fingerprint, trustLevel): - ''' sets the trust level for the given fingerprint. - trust is usually: - - the empty string for known but untrusted keys - - 'verified' for manually verified keys - - 'smp' for smp-style verified keys ''' - self.user.setTrust(self.trustName, fingerprint, trustLevel) - - def getTrust(self, fingerprint, default=None): - return self.user.getTrust(self.trustName, fingerprint, default) - - def setCurrentTrust(self, trustLevel): - self.setTrust(self.crypto.theirPubkey.cfingerprint(), trustLevel) - - def getCurrentKey(self): - return self.crypto.theirPubkey - - def getCurrentTrust(self): - ''' returns a 2-tuple: first element is the current fingerprint, - second is: - - None if the key is unknown yet - - a non-empty string if the key is trusted - - an empty string if the key is untrusted ''' - if self.crypto.theirPubkey is None: - return None - return self.getTrust(self.crypto.theirPubkey.cfingerprint(), None) - - def receiveMessage(self, messageData, appdata=None): - IGN = None, [] - - if not self.policyOtrEnabled(): - raise NotOTRMessage(messageData) - - message = self.parse(messageData) - - if message is None: - # nothing to see. move along. - return IGN - - logger.debug(repr(message)) - - if self.getPolicy('SEND_TAG'): - if isinstance(message, basestring): - # received a plaintext message without tag - # we should not tag anymore - self.tagOffer = OFFER_REJECTED - else: - # got something OTR-ish, cool! - self.tagOffer = OFFER_ACCEPTED - - if isinstance(message, proto.Query): - self.handleQuery(message, appdata=appdata) - - if isinstance(message, proto.TaggedPlaintext): - # it's actually a plaintext message - if self.state != STATE_PLAINTEXT or \ - self.getPolicy('REQUIRE_ENCRYPTION'): - # but we don't want plaintexts - raise UnencryptedMessage(message.msg) - - raise NotOTRMessage(message.msg) - - return IGN - - if isinstance(message, proto.AKEMessage): - self.crypto.handleAKE(message, appdata=appdata) - return IGN - - if isinstance(message, proto.DataMessage): - ignore = message.flags & proto.MSGFLAGS_IGNORE_UNREADABLE - - if self.state != STATE_ENCRYPTED: - self.sendInternal(proto.Error( - 'You sent encrypted to {user}, who wasn\'t expecting it.' - .format(user=self.user.name).encode('utf-8')), appdata=appdata) - if ignore: - return IGN - raise NotEncryptedError(EXC_UNREADABLE_MESSAGE) - - try: - plaintext, tlvs = self.crypto.handleDataMessage(message) - self.processTLVs(tlvs, appdata=appdata) - if plaintext and self.lastSend < time() - HEARTBEAT_INTERVAL: - self.sendInternal(b'', appdata=appdata) - return plaintext or None, tlvs - except crypt.InvalidParameterError: - if ignore: - return IGN - logger.exception('decryption failed') - raise - if isinstance(message, basestring): - if self.state != STATE_PLAINTEXT or \ - self.getPolicy('REQUIRE_ENCRYPTION'): - raise UnencryptedMessage(message) - - if isinstance(message, proto.Error): - raise ErrorReceived(message) - - raise NotOTRMessage(messageData) - - def sendInternal(self, msg, tlvs=[], appdata=None): - self.sendMessage(FRAGMENT_SEND_ALL, msg, tlvs=tlvs, appdata=appdata, - flags=proto.MSGFLAGS_IGNORE_UNREADABLE) - - def sendMessage(self, sendPolicy, msg, flags=0, tlvs=[], appdata=None): - if self.policyOtrEnabled(): - self.lastSend = time() - - if isinstance(msg, proto.OTRMessage): - # we want to send a protocol message (probably internal) - # so we don't need further protocol encryption - # also we can't add TLVs to arbitrary protocol messages - if tlvs: - raise TypeError('can\'t add tlvs to protocol message') - else: - # we got plaintext to send. encrypt it - msg = self.processOutgoingMessage(msg, flags, tlvs) - - if isinstance(msg, proto.OTRMessage) \ - and not isinstance(msg, proto.Query): - # if it's a query message, it must not get fragmented - return self.sendFragmented(bytes(msg), policy=sendPolicy, appdata=appdata) - else: - msg = bytes(msg) - return msg - - def processOutgoingMessage(self, msg, flags, tlvs=[]): - 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 isQuery: - self.lastMessage = msg - self.lastSend = time() - self.mayRetransmit = 2 - # TODO notify - msg = self.user.getDefaultQueryMessage(self.getPolicy) - return msg - if self.getPolicy('SEND_TAG') and self.tagOffer != OFFER_REJECTED: - self.tagOffer = OFFER_SENT - 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) - self.lastSend = time() - return msg - if self.state == STATE_FINISHED: - raise NotEncryptedError(EXC_FINISHED) - - def disconnect(self, appdata=None): - if self.state != STATE_FINISHED: - self.sendInternal(b'', tlvs=[proto.DisconnectTLV()], appdata=appdata) - self.setState(STATE_PLAINTEXT) - self.crypto.finished() - else: - self.setState(STATE_PLAINTEXT) - - def setState(self, newstate): - self.state = newstate - - def _wentEncrypted(self): - self.setState(STATE_ENCRYPTED) - - def sendFragmented(self, msg, policy=FRAGMENT_SEND_ALL, appdata=None): - mms = self.maxMessageSize(appdata) - msgLen = len(msg) - if mms != 0 and msgLen > mms: - fms = mms - 19 - fragments = [ msg[i:i+fms] for i in range(0, msgLen, fms) ] - - fc = len(fragments) - - if fc > 65535: - raise OverflowError('too many fragments') - - for fi in range(len(fragments)): - ctr = unicode(fi+1) + ',' + unicode(fc) + ',' - fragments[fi] = b'?OTR,' + ctr.encode('ascii') \ - + fragments[fi] + b',' - - if policy == FRAGMENT_SEND_ALL: - for f in fragments: - self.inject(f, appdata=appdata) - return None - elif policy == FRAGMENT_SEND_ALL_BUT_FIRST: - for f in fragments[1:]: - self.inject(f, appdata=appdata) - return fragments[0] - elif policy == FRAGMENT_SEND_ALL_BUT_LAST: - for f in fragments[:-1]: - self.inject(f, appdata=appdata) - return fragments[-1] - - else: - if policy == FRAGMENT_SEND_ALL: - self.inject(msg, appdata=appdata) - return None - else: - return msg - - def processTLVs(self, tlvs, appdata=None): - for tlv in tlvs: - if isinstance(tlv, proto.DisconnectTLV): - logger.info('got disconnect tlv, forcing finished state') - self.setState(STATE_FINISHED) - self.crypto.finished() - # TODO cleanup - continue - if isinstance(tlv, proto.SMPTLV): - self.crypto.smpHandle(tlv, appdata=appdata) - continue - logger.info('got unhandled tlv: {0!r}'.format(tlv)) - - def smpAbort(self, appdata=None): - if self.state != STATE_ENCRYPTED: - raise NotEncryptedError - self.crypto.smpAbort(appdata=appdata) - - def smpIsValid(self): - return self.crypto.smp and self.crypto.smp.prog != crypt.SMPPROG_CHEATED - - def smpIsSuccess(self): - return self.crypto.smp.prog == crypt.SMPPROG_SUCCEEDED \ - if self.crypto.smp else None - - def smpGotSecret(self, secret, question=None, appdata=None): - if self.state != STATE_ENCRYPTED: - raise NotEncryptedError - self.crypto.smpSecret(secret, question=question, appdata=appdata) - - def smpInit(self, secret, question=None, appdata=None): - if self.state != STATE_ENCRYPTED: - raise NotEncryptedError - self.crypto.smp = None - self.crypto.smpSecret(secret, question=question, appdata=appdata) - - def handleQuery(self, message, appdata=None): - if 2 in message.versions and self.getPolicy('ALLOW_V2'): - self.authStartV2(appdata=appdata) - elif 1 in message.versions and self.getPolicy('ALLOW_V1'): - self.authStartV1(appdata=appdata) - - def authStartV1(self, appdata=None): - raise NotImplementedError() - - def authStartV2(self, appdata=None): - self.crypto.startAKE(appdata=appdata) - - 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: - return proto.TaggedPlaintext.parse(message) - else: - return message - - indexBase = otrTagPos + len(proto.OTRTAG) - - if len(message) <= indexBase: - return message - - compare = message[indexBase] - - if nofragment is False and compare == b','[0]: - message = self.fragmentAccumulate(message[indexBase:]) - if message is None: - return None - else: - return self.parse(message, nofragment=True) - else: - self.discardFragment() - - queryPayload = self.parseExplicitQuery(message) - if queryPayload is not None: - return proto.Query.parse(queryPayload) - - if compare == b':'[0] and len(message) > indexBase + 4: - 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 - - if message[indexBase:indexBase+7] == b' Error:': - return proto.Error(message[indexBase+7:]) - - return message - - def maxMessageSize(self, appdata=None): - """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): - self.name = name - self.privkey = privkey - self.policy = {} - self.protocol = protocol - self.ctxs = {} - self.trusts = {} - self.maxMessageSize = maxMessageSize - 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__, - name=self.name) - - def getPrivkey(self, autogen=True): - if self.privkey is None: - self.privkey = self.loadPrivkey() - if self.privkey is None: - if autogen is True: - self.privkey = compatcrypto.generateDefaultKey() - self.savePrivkey() - else: - raise LookupError - return self.privkey - - def loadPrivkey(self): - raise NotImplementedError - - def savePrivkey(self): - raise NotImplementedError - - def saveTrusts(self): - raise NotImplementedError - - def getContext(self, uid, newCtxCb=None): - if uid not in self.ctxs: - self.ctxs[uid] = self.contextclass(self, uid) - if callable(newCtxCb): - newCtxCb(self.ctxs[uid]) - return self.ctxs[uid] - - def getDefaultQueryMessage(self, policy): - 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: - self.trusts[key] = {} - self.trusts[key][fingerprint] = trustLevel - self.saveTrusts() - - def getTrust(self, key, fingerprint, default=None): - if key not in self.trusts: - return default - return self.trusts[key].get(fingerprint, default) - - def removeFingerprint(self, key, fingerprint): - if key in self.trusts and fingerprint in self.trusts[key]: - del self.trusts[key][fingerprint] - -class NotEncryptedError(RuntimeError): - pass -class UnencryptedMessage(RuntimeError): - pass -class ErrorReceived(RuntimeError): - pass -class NotOTRMessage(RuntimeError): - pass diff --git a/gotr/potr/crypt.py b/gotr/potr/crypt.py deleted file mode 100644 index 3a4bb4b..0000000 --- a/gotr/potr/crypt.py +++ /dev/null @@ -1,795 +0,0 @@ -# Copyright 2011-2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -# some python3 compatibilty -from __future__ import unicode_literals - -import logging -import struct - - -from potr.compatcrypto import SHA256, SHA1, SHA1HMAC, SHA256HMAC, \ - SHA256HMAC160, Counter, AESCTR, PK, random -from potr.utils import bytes_to_long, long_to_bytes, pack_mpi, read_mpi -from potr import proto - -logger = logging.getLogger(__name__) - -STATE_NONE = 0 -STATE_AWAITING_DHKEY = 1 -STATE_AWAITING_REVEALSIG = 2 -STATE_AWAITING_SIG = 4 -STATE_V1_SETUP = 5 - - -DH_MODULUS = 2410312426921032588552076022197566074856950548502459942654116941958108831682612228890093858261341614673227141477904012196503648957050582631942730706805009223062734745341073406696246014589361659774041027169249453200378729434170325843778659198143763193776859869524088940195577346119843545301547043747207749969763750084308926339295559968882457872412993810129130294592999947926365264059284647209730384947211681434464714438488520940127459844288859336526896320919633919 -DH_MODULUS_2 = DH_MODULUS-2 -DH_GENERATOR = 2 -DH_BITS = 1536 -DH_MAX = 2**DH_BITS -SM_ORDER = (DH_MODULUS - 1) // 2 - -def check_group(n): - return 2 <= n <= DH_MODULUS_2 -def check_exp(n): - return 1 <= n < SM_ORDER - -class DH(object): - @classmethod - def set_params(cls, prime, gen): - cls.prime = prime - cls.gen = gen - - def __init__(self): - self.priv = random.randrange(2, 2**320) - self.pub = pow(self.gen, self.priv, self.prime) - -DH.set_params(DH_MODULUS, DH_GENERATOR) - -class DHSession(object): - def __init__(self, sendenc, sendmac, rcvenc, rcvmac): - self.sendenc = sendenc - self.sendmac = sendmac - self.rcvenc = rcvenc - self.rcvmac = rcvmac - self.sendctr = Counter(0) - self.rcvctr = Counter(0) - self.sendmacused = False - self.rcvmacused = False - - def __repr__(self): - return '<{cls}(send={s!r},rcv={r!r})>' \ - .format(cls=self.__class__.__name__, - s=self.sendmac, r=self.rcvmac) - - @classmethod - def create(cls, dh, y): - s = pow(y, dh.priv, DH_MODULUS) - sb = pack_mpi(s) - - if dh.pub > y: - sendbyte = b'\1' - rcvbyte = b'\2' - else: - sendbyte = b'\2' - rcvbyte = b'\1' - - sendenc = SHA1(sendbyte + sb)[:16] - sendmac = SHA1(sendenc) - rcvenc = SHA1(rcvbyte + sb)[:16] - rcvmac = SHA1(rcvenc) - return cls(sendenc, sendmac, rcvenc, rcvmac) - -class CryptEngine(object): - def __init__(self, ctx): - self.ctx = ctx - self.ake = None - - self.sessionId = None - self.sessionIdHalf = False - self.theirKeyid = 0 - self.theirY = None - self.theirOldY = None - - self.ourOldDHKey = None - self.ourDHKey = None - self.ourKeyid = 0 - - self.sessionkeys = {0:{0:None, 1:None}, 1:{0:None, 1:None}} - self.theirPubkey = None - self.savedMacKeys = [] - - self.smp = None - self.extraKey = None - - def revealMacs(self, ours=True): - if ours: - dhs = self.sessionkeys[1].values() - else: - dhs = ( v[1] for v in self.sessionkeys.values() ) - for v in dhs: - if v is not None: - if v.rcvmacused: - self.savedMacKeys.append(v.rcvmac) - if v.sendmacused: - self.savedMacKeys.append(v.sendmac) - - def rotateDHKeys(self): - self.revealMacs(ours=True) - self.ourOldDHKey = self.ourDHKey - self.sessionkeys[1] = self.sessionkeys[0].copy() - self.ourDHKey = DH() - self.ourKeyid += 1 - - self.sessionkeys[0][0] = None if self.theirY is None else \ - DHSession.create(self.ourDHKey, self.theirY) - self.sessionkeys[0][1] = None if self.theirOldY is None else \ - DHSession.create(self.ourDHKey, self.theirOldY) - - logger.debug('{0}: Refreshing ourkey to {1} {2}'.format( - self.ctx.user.name, self.ourKeyid, self.sessionkeys)) - - def rotateYKeys(self, new_y): - self.theirOldY = self.theirY - self.revealMacs(ours=False) - self.sessionkeys[0][1] = self.sessionkeys[0][0] - self.sessionkeys[1][1] = self.sessionkeys[1][0] - self.theirY = new_y - self.theirKeyid += 1 - - self.sessionkeys[0][0] = DHSession.create(self.ourDHKey, self.theirY) - self.sessionkeys[1][0] = DHSession.create(self.ourOldDHKey, self.theirY) - - logger.debug('{0}: Refreshing theirkey to {1} {2}'.format( - self.ctx.user.name, self.theirKeyid, self.sessionkeys)) - - def handleDataMessage(self, msg): - if self.saneKeyIds(msg) is False: - raise InvalidParameterError - - sesskey = self.sessionkeys[self.ourKeyid - msg.rkeyid] \ - [self.theirKeyid - msg.skeyid] - - logger.debug('sesskeys: {0!r}, our={1}, r={2}, their={3}, s={4}' \ - .format(self.sessionkeys, self.ourKeyid, msg.rkeyid, - self.theirKeyid, msg.skeyid)) - - if msg.mac != SHA1HMAC(sesskey.rcvmac, msg.getMacedData()): - logger.error('HMACs don\'t match') - raise InvalidParameterError - sesskey.rcvmacused = True - - newCtrPrefix = bytes_to_long(msg.ctr) - if newCtrPrefix <= sesskey.rcvctr.prefix: - logger.error('CTR must increase (old %r, new %r)', - sesskey.rcvctr.prefix, newCtrPrefix) - raise InvalidParameterError - - sesskey.rcvctr.prefix = newCtrPrefix - - logger.debug('handle: enc={0!r} mac={1!r} ctr={2!r}' \ - .format(sesskey.rcvenc, sesskey.rcvmac, sesskey.rcvctr)) - - plaintextData = AESCTR(sesskey.rcvenc, sesskey.rcvctr) \ - .decrypt(msg.encmsg) - - if b'\0' in plaintextData: - plaintext, tlvData = plaintextData.split(b'\0', 1) - tlvs = proto.TLV.parse(tlvData) - else: - plaintext = plaintextData - tlvs = [] - - if msg.rkeyid == self.ourKeyid: - self.rotateDHKeys() - if msg.skeyid == self.theirKeyid: - self.rotateYKeys(bytes_to_long(msg.dhy)) - - return plaintext, tlvs - - def smpSecret(self, secret, question=None, appdata=None): - if self.smp is None: - logger.debug('Creating SMPHandler') - self.smp = SMPHandler(self) - - self.smp.gotSecret(secret, question=question, appdata=appdata) - - def smpHandle(self, tlv, appdata=None): - if self.smp is None: - logger.debug('Creating SMPHandler') - self.smp = SMPHandler(self) - self.smp.handle(tlv, appdata=appdata) - - def smpAbort(self, appdata=None): - if self.smp is None: - logger.debug('Creating SMPHandler') - self.smp = SMPHandler(self) - self.smp.abort(appdata=appdata) - - def createDataMessage(self, message, flags=0, tlvs=None): - # check MSGSTATE - if self.theirKeyid == 0: - raise InvalidParameterError - - if tlvs is None: - tlvs = [] - - sess = self.sessionkeys[1][0] - sess.sendctr.inc() - - logger.debug('create: enc={0!r} mac={1!r} ctr={2!r}' \ - .format(sess.sendenc, sess.sendmac, sess.sendctr)) - - # plaintext + TLVS - plainBuf = message + b'\0' + b''.join([ bytes(t) for t in tlvs]) - encmsg = AESCTR(sess.sendenc, sess.sendctr).encrypt(plainBuf) - - msg = proto.DataMessage(flags, self.ourKeyid-1, self.theirKeyid, - long_to_bytes(self.ourDHKey.pub), sess.sendctr.byteprefix(), - encmsg, b'', b''.join(self.savedMacKeys)) - msg.mac = SHA1HMAC(sess.sendmac, msg.getMacedData()) - return msg - - def saneKeyIds(self, msg): - anyzero = self.theirKeyid == 0 or msg.skeyid == 0 or msg.rkeyid == 0 - if anyzero or (msg.skeyid != self.theirKeyid and \ - msg.skeyid != self.theirKeyid - 1) or \ - (msg.rkeyid != self.ourKeyid and msg.rkeyid != self.ourKeyid - 1): - return False - if self.theirOldY is None and msg.skeyid == self.theirKeyid - 1: - return False - return True - - def startAKE(self, appdata=None): - self.ake = AuthKeyExchange(self.ctx.user.getPrivkey(), self.goEncrypted) - outMsg = self.ake.startAKE() - self.ctx.sendInternal(outMsg, appdata=appdata) - - def handleAKE(self, inMsg, appdata=None): - outMsg = None - - if not self.ctx.getPolicy('ALLOW_V2'): - return - - if isinstance(inMsg, proto.DHCommit): - if self.ake is None or self.ake.state != STATE_AWAITING_REVEALSIG: - self.ake = AuthKeyExchange(self.ctx.user.getPrivkey(), - self.goEncrypted) - outMsg = self.ake.handleDHCommit(inMsg) - - elif isinstance(inMsg, proto.DHKey): - if self.ake is None: - return # ignore - outMsg = self.ake.handleDHKey(inMsg) - - elif isinstance(inMsg, proto.RevealSig): - if self.ake is None: - return # ignore - outMsg = self.ake.handleRevealSig(inMsg) - - elif isinstance(inMsg, proto.Signature): - if self.ake is None: - return # ignore - self.ake.handleSignature(inMsg) - - if outMsg is not None: - self.ctx.sendInternal(outMsg, appdata=appdata) - - def goEncrypted(self, ake): - if ake.dh.pub == ake.gy: - logger.warning('We are receiving our own messages') - raise InvalidParameterError - - # TODO handle new fingerprint - self.theirPubkey = ake.theirPubkey - - self.sessionId = ake.sessionId - self.sessionIdHalf = ake.sessionIdHalf - self.theirKeyid = ake.theirKeyid - self.ourKeyid = ake.ourKeyid - self.theirY = ake.gy - self.theirOldY = None - self.extraKey = ake.extraKey - - if self.ourKeyid != ake.ourKeyid + 1 or self.ourOldDHKey != ake.dh.pub: - self.ourDHKey = ake.dh - self.sessionkeys[0][0] = DHSession.create(self.ourDHKey, self.theirY) - self.rotateDHKeys() - - # we don't need the AKE anymore, free the reference - self.ake = None - - self.ctx._wentEncrypted() - logger.info('went encrypted with {0}'.format(self.theirPubkey)) - - def finished(self): - self.smp = None - -class AuthKeyExchange(object): - def __init__(self, privkey, onSuccess): - self.privkey = privkey - self.state = STATE_NONE - self.r = None - self.encgx = None - self.hashgx = None - self.ourKeyid = 1 - self.theirPubkey = None - self.theirKeyid = 1 - self.enc_c = None - self.enc_cp = None - self.mac_m1 = None - self.mac_m1p = None - self.mac_m2 = None - self.mac_m2p = None - self.sessionId = None - self.sessionIdHalf = False - self.dh = DH() - self.onSuccess = onSuccess - self.gy = None - self.extraKey = None - self.lastmsg = None - - def startAKE(self): - self.r = long_to_bytes(random.getrandbits(128)) - - gxmpi = pack_mpi(self.dh.pub) - - self.hashgx = SHA256(gxmpi) - self.encgx = AESCTR(self.r).encrypt(gxmpi) - - self.state = STATE_AWAITING_DHKEY - - return proto.DHCommit(self.encgx, self.hashgx) - - def handleDHCommit(self, msg): - self.encgx = msg.encgx - self.hashgx = msg.hashgx - - self.state = STATE_AWAITING_REVEALSIG - return proto.DHKey(long_to_bytes(self.dh.pub)) - - def handleDHKey(self, msg): - if self.state == STATE_AWAITING_DHKEY: - self.gy = bytes_to_long(msg.gy) - - # check 2 <= g**y <= p-2 - if not check_group(self.gy): - logger.error('Invalid g**y received: %r', self.gy) - return - - self.createAuthKeys() - - aesxb = self.calculatePubkeyAuth(self.enc_c, self.mac_m1) - - self.state = STATE_AWAITING_SIG - - self.lastmsg = proto.RevealSig(self.r, aesxb, b'') - self.lastmsg.mac = SHA256HMAC160(self.mac_m2, - self.lastmsg.getMacedData()) - return self.lastmsg - - elif self.state == STATE_AWAITING_SIG: - logger.info('received DHKey while not awaiting DHKEY') - if msg.gy == self.gy: - logger.info('resending revealsig') - return self.lastmsg - else: - logger.info('bad state for DHKey') - - def handleRevealSig(self, msg): - if self.state != STATE_AWAITING_REVEALSIG: - logger.error('bad state for RevealSig') - raise InvalidParameterError - - self.r = msg.rkey - gxmpi = AESCTR(self.r).decrypt(self.encgx) - if SHA256(gxmpi) != self.hashgx: - logger.error('Hashes don\'t match') - logger.info('r=%r, hashgx=%r, computed hash=%r, gxmpi=%r', - self.r, self.hashgx, SHA256(gxmpi), gxmpi) - raise InvalidParameterError - - self.gy = read_mpi(gxmpi)[0] - self.createAuthKeys() - - if msg.mac != SHA256HMAC160(self.mac_m2, msg.getMacedData()): - logger.error('HMACs don\'t match') - logger.info('mac=%r, mac_m2=%r, data=%r', msg.mac, self.mac_m2, - msg.getMacedData()) - raise InvalidParameterError - - self.checkPubkeyAuth(self.enc_c, self.mac_m1, msg.encsig) - - aesxb = self.calculatePubkeyAuth(self.enc_cp, self.mac_m1p) - self.sessionIdHalf = True - - self.onSuccess(self) - - self.ourKeyid = 0 - self.state = STATE_NONE - - cmpmac = struct.pack(b'!I', len(aesxb)) + aesxb - - return proto.Signature(aesxb, SHA256HMAC160(self.mac_m2p, cmpmac)) - - def handleSignature(self, msg): - if self.state != STATE_AWAITING_SIG: - logger.error('bad state (%d) for Signature', self.state) - raise InvalidParameterError - - if msg.mac != SHA256HMAC160(self.mac_m2p, msg.getMacedData()): - logger.error('HMACs don\'t match') - raise InvalidParameterError - - self.checkPubkeyAuth(self.enc_cp, self.mac_m1p, msg.encsig) - - self.sessionIdHalf = False - - self.onSuccess(self) - - self.ourKeyid = 0 - self.state = STATE_NONE - - def createAuthKeys(self): - s = pow(self.gy, self.dh.priv, DH_MODULUS) - sbyte = pack_mpi(s) - self.sessionId = SHA256(b'\x00' + sbyte)[:8] - enc = SHA256(b'\x01' + sbyte) - self.enc_c = enc[:16] - self.enc_cp = enc[16:] - self.mac_m1 = SHA256(b'\x02' + sbyte) - self.mac_m2 = SHA256(b'\x03' + sbyte) - self.mac_m1p = SHA256(b'\x04' + sbyte) - self.mac_m2p = SHA256(b'\x05' + sbyte) - self.extraKey = SHA256(b'\xff' + sbyte) - - def calculatePubkeyAuth(self, key, mackey): - pubkey = self.privkey.serializePublicKey() - buf = pack_mpi(self.dh.pub) - buf += pack_mpi(self.gy) - buf += pubkey - buf += struct.pack(b'!I', self.ourKeyid) - MB = self.privkey.sign(SHA256HMAC(mackey, buf)) - - buf = pubkey - buf += struct.pack(b'!I', self.ourKeyid) - buf += MB - return AESCTR(key).encrypt(buf) - - def checkPubkeyAuth(self, key, mackey, encsig): - auth = AESCTR(key).decrypt(encsig) - self.theirPubkey, auth = PK.parsePublicKey(auth) - - receivedKeyid, auth = proto.unpack(b'!I', auth) - if receivedKeyid == 0: - raise InvalidParameterError - - authbuf = pack_mpi(self.gy) - authbuf += pack_mpi(self.dh.pub) - authbuf += self.theirPubkey.serializePublicKey() - authbuf += struct.pack(b'!I', receivedKeyid) - - if self.theirPubkey.verify(SHA256HMAC(mackey, authbuf), auth) is False: - raise InvalidParameterError - self.theirKeyid = receivedKeyid - -SMPPROG_OK = 0 -SMPPROG_CHEATED = -2 -SMPPROG_FAILED = -1 -SMPPROG_SUCCEEDED = 1 - -class SMPHandler: - def __init__(self, crypto): - self.crypto = crypto - self.state = 1 - self.g1 = DH_GENERATOR - self.g2 = None - self.g3 = None - self.g3o = None - self.x2 = None - self.x3 = None - self.prog = SMPPROG_OK - self.pab = None - self.qab = None - self.questionReceived = False - self.secret = None - self.p = None - self.q = None - - def abort(self, appdata=None): - self.state = 1 - self.sendTLV(proto.SMPABORTTLV(), appdata=appdata) - - def sendTLV(self, tlv, appdata=None): - self.crypto.ctx.sendInternal(b'', tlvs=[tlv], appdata=appdata) - - def handle(self, tlv, appdata=None): - logger.debug('handling TLV {0.__class__.__name__}'.format(tlv)) - self.prog = SMPPROG_CHEATED - if isinstance(tlv, proto.SMPABORTTLV): - self.state = 1 - return - is1qTlv = isinstance(tlv, proto.SMP1QTLV) - if isinstance(tlv, proto.SMP1TLV) or is1qTlv: - if self.state != 1: - self.abort(appdata=appdata) - return - - msg = tlv.mpis - - if not check_group(msg[0]) or not check_group(msg[3]) \ - or not check_exp(msg[2]) or not check_exp(msg[5]) \ - or not check_known_log(msg[1], msg[2], self.g1, msg[0], 1) \ - or not check_known_log(msg[4], msg[5], self.g1, msg[3], 2): - logger.error('invalid SMP1TLV received') - self.abort(appdata=appdata) - return - - self.questionReceived = is1qTlv - - self.g3o = msg[3] - - self.x2 = random.randrange(2, DH_MAX) - self.x3 = random.randrange(2, DH_MAX) - - self.g2 = pow(msg[0], self.x2, DH_MODULUS) - self.g3 = pow(msg[3], self.x3, DH_MODULUS) - - self.prog = SMPPROG_OK - self.state = 0 - return - if isinstance(tlv, proto.SMP2TLV): - if self.state != 2: - self.abort(appdata=appdata) - return - - msg = tlv.mpis - mp = msg[6] - mq = msg[7] - - if not check_group(msg[0]) or not check_group(msg[3]) \ - or not check_group(msg[6]) or not check_group(msg[7]) \ - or not check_exp(msg[2]) or not check_exp(msg[5]) \ - or not check_exp(msg[9]) or not check_exp(msg[10]) \ - or not check_known_log(msg[1], msg[2], self.g1, msg[0], 3) \ - or not check_known_log(msg[4], msg[5], self.g1, msg[3], 4): - logger.error('invalid SMP2TLV received') - self.abort(appdata=appdata) - return - - self.g3o = msg[3] - self.g2 = pow(msg[0], self.x2, DH_MODULUS) - self.g3 = pow(msg[3], self.x3, DH_MODULUS) - - if not self.check_equal_coords(msg[6:11], 5): - logger.error('invalid SMP2TLV received') - self.abort(appdata=appdata) - return - - r = random.randrange(2, DH_MAX) - self.p = pow(self.g3, r, DH_MODULUS) - msg = [self.p] - qa1 = pow(self.g1, r, DH_MODULUS) - qa2 = pow(self.g2, self.secret, DH_MODULUS) - self.q = qa1*qa2 % DH_MODULUS - msg.append(self.q) - msg += self.proof_equal_coords(r, 6) - - inv = invMod(mp) - self.pab = self.p * inv % DH_MODULUS - inv = invMod(mq) - self.qab = self.q * inv % DH_MODULUS - - msg.append(pow(self.qab, self.x3, DH_MODULUS)) - msg += self.proof_equal_logs(7) - - self.state = 4 - self.prog = SMPPROG_OK - self.sendTLV(proto.SMP3TLV(msg), appdata=appdata) - return - if isinstance(tlv, proto.SMP3TLV): - if self.state != 3: - self.abort(appdata=appdata) - return - - msg = tlv.mpis - - if not check_group(msg[0]) or not check_group(msg[1]) \ - or not check_group(msg[5]) or not check_exp(msg[3]) \ - or not check_exp(msg[4]) or not check_exp(msg[7]) \ - or not self.check_equal_coords(msg[:5], 6): - logger.error('invalid SMP3TLV received') - self.abort(appdata=appdata) - return - - inv = invMod(self.p) - self.pab = msg[0] * inv % DH_MODULUS - inv = invMod(self.q) - self.qab = msg[1] * inv % DH_MODULUS - - if not self.check_equal_logs(msg[5:8], 7): - logger.error('invalid SMP3TLV received') - self.abort(appdata=appdata) - return - - md = msg[5] - msg = [pow(self.qab, self.x3, DH_MODULUS)] - msg += self.proof_equal_logs(8) - - rab = pow(md, self.x3, DH_MODULUS) - self.prog = SMPPROG_SUCCEEDED if self.pab == rab else SMPPROG_FAILED - - if self.prog != SMPPROG_SUCCEEDED: - logger.error('secrets don\'t match') - self.abort(appdata=appdata) - self.crypto.ctx.setCurrentTrust('') - return - - logger.info('secrets matched') - if not self.questionReceived: - self.crypto.ctx.setCurrentTrust('smp') - self.state = 1 - self.sendTLV(proto.SMP4TLV(msg), appdata=appdata) - return - if isinstance(tlv, proto.SMP4TLV): - if self.state != 4: - self.abort(appdata=appdata) - return - - msg = tlv.mpis - - if not check_group(msg[0]) or not check_exp(msg[2]) \ - or not self.check_equal_logs(msg[:3], 8): - logger.error('invalid SMP4TLV received') - self.abort(appdata=appdata) - return - - rab = pow(msg[0], self.x3, DH_MODULUS) - - self.prog = SMPPROG_SUCCEEDED if self.pab == rab else SMPPROG_FAILED - - if self.prog != SMPPROG_SUCCEEDED: - logger.error('secrets don\'t match') - self.abort(appdata=appdata) - self.crypto.ctx.setCurrentTrust('') - return - - logger.info('secrets matched') - self.crypto.ctx.setCurrentTrust('smp') - self.state = 1 - return - - def gotSecret(self, secret, question=None, appdata=None): - ourFP = self.crypto.ctx.user.getPrivkey().fingerprint() - if self.state == 1: - # first secret -> SMP1TLV - combSecret = SHA256(b'\1' + ourFP + - self.crypto.theirPubkey.fingerprint() + - self.crypto.sessionId + secret) - - self.secret = bytes_to_long(combSecret) - - self.x2 = random.randrange(2, DH_MAX) - self.x3 = random.randrange(2, DH_MAX) - - msg = [pow(self.g1, self.x2, DH_MODULUS)] - msg += proof_known_log(self.g1, self.x2, 1) - msg.append(pow(self.g1, self.x3, DH_MODULUS)) - msg += proof_known_log(self.g1, self.x3, 2) - - self.prog = SMPPROG_OK - self.state = 2 - if question is None: - self.sendTLV(proto.SMP1TLV(msg), appdata=appdata) - else: - self.sendTLV(proto.SMP1QTLV(question, msg), appdata=appdata) - if self.state == 0: - # response secret -> SMP2TLV - combSecret = SHA256(b'\1' + self.crypto.theirPubkey.fingerprint() + - ourFP + self.crypto.sessionId + secret) - - self.secret = bytes_to_long(combSecret) - - msg = [pow(self.g1, self.x2, DH_MODULUS)] - msg += proof_known_log(self.g1, self.x2, 3) - msg.append(pow(self.g1, self.x3, DH_MODULUS)) - msg += proof_known_log(self.g1, self.x3, 4) - - r = random.randrange(2, DH_MAX) - - self.p = pow(self.g3, r, DH_MODULUS) - msg.append(self.p) - - qb1 = pow(self.g1, r, DH_MODULUS) - qb2 = pow(self.g2, self.secret, DH_MODULUS) - self.q = qb1 * qb2 % DH_MODULUS - msg.append(self.q) - - msg += self.proof_equal_coords(r, 5) - - self.state = 3 - self.sendTLV(proto.SMP2TLV(msg), appdata=appdata) - - def proof_equal_coords(self, r, v): - r1 = random.randrange(2, DH_MAX) - r2 = random.randrange(2, DH_MAX) - temp2 = pow(self.g1, r1, DH_MODULUS) \ - * pow(self.g2, r2, DH_MODULUS) % DH_MODULUS - temp1 = pow(self.g3, r1, DH_MODULUS) - - cb = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2)) - c = bytes_to_long(cb) - - temp1 = r * c % SM_ORDER - d1 = (r1-temp1) % SM_ORDER - - temp1 = self.secret * c % SM_ORDER - d2 = (r2 - temp1) % SM_ORDER - return c, d1, d2 - - def check_equal_coords(self, coords, v): - (p, q, c, d1, d2) = coords - temp1 = pow(self.g3, d1, DH_MODULUS) * pow(p, c, DH_MODULUS) \ - % DH_MODULUS - - temp2 = pow(self.g1, d1, DH_MODULUS) \ - * pow(self.g2, d2, DH_MODULUS) \ - * pow(q, c, DH_MODULUS) % DH_MODULUS - - cprime = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2)) - - return long_to_bytes(c, 32) == cprime - - def proof_equal_logs(self, v): - r = random.randrange(2, DH_MAX) - temp1 = pow(self.g1, r, DH_MODULUS) - temp2 = pow(self.qab, r, DH_MODULUS) - - cb = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2)) - c = bytes_to_long(cb) - temp1 = self.x3 * c % SM_ORDER - d = (r - temp1) % SM_ORDER - return c, d - - def check_equal_logs(self, logs, v): - (r, c, d) = logs - temp1 = pow(self.g1, d, DH_MODULUS) \ - * pow(self.g3o, c, DH_MODULUS) % DH_MODULUS - - temp2 = pow(self.qab, d, DH_MODULUS) \ - * pow(r, c, DH_MODULUS) % DH_MODULUS - - cprime = SHA256(struct.pack(b'B', v) + pack_mpi(temp1) + pack_mpi(temp2)) - return long_to_bytes(c, 32) == cprime - -def proof_known_log(g, x, v): - r = random.randrange(2, DH_MAX) - c = bytes_to_long(SHA256(struct.pack(b'B', v) + pack_mpi(pow(g, r, DH_MODULUS)))) - temp = x * c % SM_ORDER - return c, (r-temp) % SM_ORDER - -def check_known_log(c, d, g, x, v): - gd = pow(g, d, DH_MODULUS) - xc = pow(x, c, DH_MODULUS) - gdxc = gd * xc % DH_MODULUS - return SHA256(struct.pack(b'B', v) + pack_mpi(gdxc)) == long_to_bytes(c, 32) - -def invMod(n): - return pow(n, DH_MODULUS_2, DH_MODULUS) - -class InvalidParameterError(RuntimeError): - pass diff --git a/gotr/potr/proto.py b/gotr/potr/proto.py deleted file mode 100644 index 91904a8..0000000 --- a/gotr/potr/proto.py +++ /dev/null @@ -1,465 +0,0 @@ -# Copyright 2011-2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -# some python3 compatibilty -from __future__ import unicode_literals - -import base64 -import struct -from potr.utils import pack_mpi, read_mpi, pack_data, read_data, unpack - -OTRTAG = b'?OTR' -MESSAGE_TAG_BASE = b' \t \t\t\t\t \t \t \t ' -MESSAGE_TAGS = { - 1:b' \t \t \t ', - 2:b' \t\t \t ', - 3:b' \t\t \t\t', - } - -MSGTYPE_NOTOTR = 0 -MSGTYPE_TAGGEDPLAINTEXT = 1 -MSGTYPE_QUERY = 2 -MSGTYPE_DH_COMMIT = 3 -MSGTYPE_DH_KEY = 4 -MSGTYPE_REVEALSIG = 5 -MSGTYPE_SIGNATURE = 6 -MSGTYPE_V1_KEYEXCH = 7 -MSGTYPE_DATA = 8 -MSGTYPE_ERROR = 9 -MSGTYPE_UNKNOWN = -1 - -MSGFLAGS_IGNORE_UNREADABLE = 1 - -tlvClasses = {} -messageClasses = {} - -hasByteStr = bytes == str -def bytesAndStrings(cls): - if hasByteStr: - cls.__str__ = lambda self: self.__bytes__() - else: - cls.__str__ = lambda self: str(self.__bytes__(), encoding='ascii') - return cls - -def registermessage(cls): - if not hasattr(cls, 'parsePayload'): - raise TypeError('registered message types need parsePayload()') - messageClasses[cls.version, cls.msgtype] = cls - return cls - -def registertlv(cls): - if not hasattr(cls, 'parsePayload'): - raise TypeError('registered tlv types need parsePayload()') - if cls.typ is None: - raise TypeError('registered tlv type needs type ID') - tlvClasses[cls.typ] = cls - return cls - - -def getslots(cls, base): - ''' helper to collect all the message slots from ancestors ''' - clss = [cls] - - for cls in clss: - if cls == base: - continue - - clss.extend(cls.__bases__) - - for slot in cls.__slots__: - yield slot - -@bytesAndStrings -class OTRMessage(object): - __slots__ = ['payload'] - version = 0x0002 - msgtype = 0 - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return False - - for slot in getslots(self.__class__, OTRMessage): - if getattr(self, slot) != getattr(other, slot): - return False - return True - - def __neq__(self, other): - return not self.__eq__(other) - -class Error(OTRMessage): - __slots__ = ['error'] - def __init__(self, error): - super(Error, self).__init__() - self.error = error - - def __repr__(self): - return '' % self.error - - def __bytes__(self): - return b'?OTR Error:' + self.error - -class Query(OTRMessage): - __slots__ = ['versions'] - def __init__(self, versions=set()): - super(Query, self).__init__() - self.versions = versions - - @classmethod - def parse(cls, data): - if not isinstance(data, bytes): - raise TypeError('can only parse bytes') - udata = data.decode('ascii', errors='replace') - - versions = set() - if len(udata) > 0 and udata[0] == '?': - udata = udata[1:] - versions.add(1) - - if len(udata) > 0 and udata[0] == 'v': - versions.update(( int(c) for c in udata if c.isdigit() )) - return cls(versions) - - def __repr__(self): - return '' % (self.versions) - - def __bytes__(self): - d = b'?OTR' - if 1 in self.versions: - d += b'?' - d += b'v' - - # in python3 there is only int->unicode conversion - # so I convert to unicode and encode it to a byte string - versions = [ '%d' % v for v in self.versions if v != 1 ] - d += ''.join(versions).encode('ascii') - - d += b'?' - return d - -class TaggedPlaintext(Query): - __slots__ = ['msg'] - def __init__(self, msg, versions): - super(TaggedPlaintext, self).__init__(versions) - self.msg = msg - - def __bytes__(self): - data = self.msg + MESSAGE_TAG_BASE - for v in self.versions: - data += MESSAGE_TAGS[v] - return data - - def __repr__(self): - return '' \ - .format(versions=self.versions, msg=self.msg) - - @classmethod - def parse(cls, data): - tagPos = data.find(MESSAGE_TAG_BASE) - if tagPos < 0: - raise TypeError( - 'this is not a tagged plaintext ({0!r:.20})'.format(data)) - - tags = [ data[i:i+8] for i in range(tagPos, len(data), 8) ] - versions = set([ version for version, tag in MESSAGE_TAGS.items() if tag - in tags ]) - - return TaggedPlaintext(data[:tagPos], versions) - -class GenericOTRMessage(OTRMessage): - __slots__ = ['data'] - fields = [] - - def __init__(self, *args): - super(GenericOTRMessage, self).__init__() - if len(args) != len(self.fields): - raise TypeError('%s needs %d arguments, got %d' % - (self.__class__.__name__, len(self.fields), len(args))) - - super(GenericOTRMessage, self).__setattr__('data', - dict(zip((f[0] for f in self.fields), args))) - - def __getattr__(self, attr): - if attr in self.data: - return self.data[attr] - raise AttributeError( - "'{t!r}' object has no attribute '{attr!r}'".format(attr=attr, - t=self.__class__.__name__)) - - def __setattr__(self, attr, val): - if attr in self.__slots__: - super(GenericOTRMessage, self).__setattr__(attr, val) - else: - self.__getattr__(attr) # existence check - self.data[attr] = val - - def __bytes__(self): - data = struct.pack(b'!HB', self.version, self.msgtype) \ - + self.getPayload() - return b'?OTR:' + base64.b64encode(data) + b'.' - - def __repr__(self): - name = self.__class__.__name__ - data = '' - for k, _ in self.fields: - data += '%s=%r,' % (k, self.data[k]) - return '' % (name, data) - - @classmethod - def parsePayload(cls, data): - data = base64.b64decode(data) - args = [] - for _, ftype in cls.fields: - if ftype == 'data': - value, data = read_data(data) - elif isinstance(ftype, bytes): - value, data = unpack(ftype, data) - elif isinstance(ftype, int): - value, data = data[:ftype], data[ftype:] - args.append(value) - return cls(*args) - - def getPayload(self, *ffilter): - payload = b'' - for k, ftype in self.fields: - if k in ffilter: - continue - - if ftype == 'data': - payload += pack_data(self.data[k]) - elif isinstance(ftype, bytes): - payload += struct.pack(ftype, self.data[k]) - else: - payload += self.data[k] - return payload - -class AKEMessage(GenericOTRMessage): - __slots__ = [] - -@registermessage -class DHCommit(AKEMessage): - __slots__ = [] - msgtype = 0x02 - fields = [('encgx', 'data'), ('hashgx', 'data'), ] - -@registermessage -class DHKey(AKEMessage): - __slots__ = [] - msgtype = 0x0a - fields = [('gy', 'data'), ] - -@registermessage -class RevealSig(AKEMessage): - __slots__ = [] - msgtype = 0x11 - fields = [('rkey', 'data'), ('encsig', 'data'), ('mac', 20),] - - def getMacedData(self): - p = self.encsig - return struct.pack(b'!I', len(p)) + p - -@registermessage -class Signature(AKEMessage): - __slots__ = [] - msgtype = 0x12 - fields = [('encsig', 'data'), ('mac', 20)] - - def getMacedData(self): - p = self.encsig - return struct.pack(b'!I', len(p)) + p - -@registermessage -class DataMessage(GenericOTRMessage): - __slots__ = [] - msgtype = 0x03 - fields = [('flags', b'!B'), ('skeyid', b'!I'), ('rkeyid', b'!I'), - ('dhy', 'data'), ('ctr', 8), ('encmsg', 'data'), ('mac', 20), - ('oldmacs', 'data'), ] - - def getMacedData(self): - return struct.pack(b'!HB', self.version, self.msgtype) + \ - self.getPayload('mac', 'oldmacs') - -@bytesAndStrings -class TLV(object): - __slots__ = [] - typ = None - - def getPayload(self): - raise NotImplementedError - - def __repr__(self): - val = self.getPayload() - return '<{cls}(typ={t},len={l},val={v!r})>'.format(t=self.typ, - l=len(val), v=val, cls=self.__class__.__name__) - - def __bytes__(self): - val = self.getPayload() - return struct.pack(b'!HH', self.typ, len(val)) + val - - @classmethod - def parse(cls, data): - if not data: - return [] - typ, length, data = unpack(b'!HH', data) - return [tlvClasses[typ].parsePayload(data[:length])] \ - + cls.parse(data[length:]) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return False - - for slot in getslots(self.__class__, TLV): - if getattr(self, slot) != getattr(other, slot): - return False - return True - - def __neq__(self, other): - return not self.__eq__(other) - -@registertlv -class PaddingTLV(TLV): - typ = 0 - - __slots__ = ['padding'] - - def __init__(self, padding): - super(PaddingTLV, self).__init__() - self.padding = padding - - def getPayload(self): - return self.padding - - @classmethod - def parsePayload(cls, data): - return cls(data) - -@registertlv -class DisconnectTLV(TLV): - typ = 1 - def __init__(self): - super(DisconnectTLV, self).__init__() - - def getPayload(self): - return b'' - - @classmethod - def parsePayload(cls, data): - if len(data) > 0: - raise TypeError('DisconnectTLV must not contain data. got {0!r}' - .format(data)) - return cls() - -class SMPTLV(TLV): - __slots__ = ['mpis'] - dlen = None - - def __init__(self, mpis=None): - super(SMPTLV, self).__init__() - if mpis is None: - mpis = [] - if self.dlen is None: - raise TypeError('no amount of mpis specified in dlen') - if len(mpis) != self.dlen: - raise TypeError('expected {0} mpis, got {1}' - .format(self.dlen, len(mpis))) - self.mpis = mpis - - def getPayload(self): - d = struct.pack(b'!I', len(self.mpis)) - for n in self.mpis: - d += pack_mpi(n) - return d - - @classmethod - def parsePayload(cls, data): - mpis = [] - if cls.dlen > 0: - count, data = unpack(b'!I', data) - for _ in range(count): - n, data = read_mpi(data) - mpis.append(n) - if len(data) > 0: - raise TypeError('too much data for {0} mpis'.format(cls.dlen)) - return cls(mpis) - -@registertlv -class SMP1TLV(SMPTLV): - typ = 2 - dlen = 6 - -@registertlv -class SMP1QTLV(SMPTLV): - typ = 7 - dlen = 6 - __slots__ = ['msg'] - - def __init__(self, msg, mpis): - self.msg = msg - super(SMP1QTLV, self).__init__(mpis) - - def getPayload(self): - return self.msg + b'\0' + super(SMP1QTLV, self).getPayload() - - @classmethod - def parsePayload(cls, data): - msg, data = data.split(b'\0', 1) - mpis = SMP1TLV.parsePayload(data).mpis - return cls(msg, mpis) - -@registertlv -class SMP2TLV(SMPTLV): - typ = 3 - dlen = 11 - -@registertlv -class SMP3TLV(SMPTLV): - typ = 4 - dlen = 8 - -@registertlv -class SMP4TLV(SMPTLV): - typ = 5 - dlen = 3 - -@registertlv -class SMPABORTTLV(SMPTLV): - typ = 6 - dlen = 0 - - def getPayload(self): - return b'' - -@registertlv -class ExtraKeyTLV(TLV): - typ = 8 - - __slots__ = ['appid', 'appdata'] - - def __init__(self, appid, appdata): - super(ExtraKeyTLV, self).__init__() - self.appid = appid - self.appdata = appdata - if appdata is None: - self.appdata = b'' - - def getPayload(self): - return self.appid + self.appdata - - @classmethod - def parsePayload(cls, data): - return cls(data[:4], data[4:]) diff --git a/gotr/potr/utils.py b/gotr/potr/utils.py deleted file mode 100644 index e41ca46..0000000 --- a/gotr/potr/utils.py +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright 2012 Kjell Braden -# -# This file is part of the python-potr library. -# -# python-potr is free software; you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation; either version 3 of the License, or -# any later version. -# -# python-potr is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this library. If not, see . - -# some python3 compatibilty -from __future__ import unicode_literals - - -import struct - -def pack_mpi(n): - return pack_data(long_to_bytes(n)) -def read_mpi(data): - n, data = read_data(data) - return bytes_to_long(n), data -def pack_data(data): - return struct.pack(b'!I', len(data)) + data -def read_data(data): - datalen, data = unpack(b'!I', data) - return data[:datalen], data[datalen:] -def unpack(fmt, buf): - s = struct.Struct(fmt) - return s.unpack(buf[:s.size]) + (buf[s.size:],) - - -def bytes_to_long(b): - l = len(b) - s = 0 - for i in range(l): - s += byte_to_long(b[i:i+1]) << 8*(l-i-1) - return s - -def long_to_bytes(l, n=0): - b = b'' - while l != 0 or n > 0: - b = long_to_byte(l & 0xff) + b - l >>= 8 - n -= 1 - return b - -def byte_to_long(b): - return struct.unpack(b'B', b)[0] -def long_to_byte(l): - return struct.pack(b'B', l) - -def human_hash(fp): - fp = fp.upper() - fplen = len(fp) - wordsize = fplen//5 - buf = '' - for w in range(0, fplen, wordsize): - buf += '{0} '.format(fp[w:w+wordsize]) - return buf.rstrip() diff --git a/gotr/ui.py b/gotr/ui.py deleted file mode 100644 index 31ce978..0000000 --- a/gotr/ui.py +++ /dev/null @@ -1,581 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -## ui.py -## -## Copyright 2008-2012 Kjell Braden -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## -import gobject -import gtk -from common import gajim -from plugins.gui import GajimPluginConfigDialog - -import otrmodule -try: - import potr - import potr.proto -except ImportError: - pass - - -class OtrPluginConfigDialog(GajimPluginConfigDialog): - def init(self): - self.GTK_BUILDER_FILE_PATH = \ - self.plugin.local_file_path('config_dialog.ui') - self.B = gtk.Builder() - self.B.set_translation_domain('gajim_plugins') - self.B.add_from_file(self.GTK_BUILDER_FILE_PATH) - - self.fpr_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, - gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING, - gobject.TYPE_STRING, gobject.TYPE_STRING) - - self.otr_account_store = self.B.get_object('account_store') - - for account in sorted(gajim.contacts.get_accounts()): - self.otr_account_store.append(row=(account,)) - - self.fpr_view = self.B.get_object('fingerprint_view') - self.fpr_view.set_model(self.fpr_model) - self.fpr_view.get_selection().set_mode(gtk.SELECTION_MULTIPLE) - - if len(self.otr_account_store) > 0: - self.B.get_object('account_combobox').set_active(0) - - self.child.pack_start(self.B.get_object('notebook1')) - - self.flags = dict() - flagList = ( - ('ALLOW_V2', 'enable_check'), - ('SEND_TAG', 'advertise_check'), - ('WHITESPACE_START_AKE', 'autoinitiate_check'), - ('REQUIRE_ENCRYPTION', 'require_check') - ) - for flagName, checkBoxName in flagList: - self.flags[flagName] = self.B.get_object(checkBoxName) - - self.B.connect_signals(self) - - def on_run(self): - self.plugin.update_context_list() - self.account_combobox_changed_cb(self.B.get_object('account_combobox')) - - def fpr_button_pressed_cb(self, tw, event): - if event.button == 3: - pthinfo = tw.get_path_at_pos(int(event.x), int(event.y)) - - if pthinfo is None: - # only show the popup when we right clicked on list content - # ie. don't show it when we click at empty rows - return False - - # if the row under the mouse is already selected, we keep the - # selection, otherwise we only select the new item - keep_selection = tw.get_selection().path_is_selected(pthinfo[0]) - - pop = self.B.get_object('fprclipboard_menu') - pop.popup(None, None, None, event.button, event.time) - - # keep_selection=True -> no further processing of click event - # keep_selection=False-> further processing -> GTK usually selects - # the item below the cursor - return keep_selection - - def clipboard_button_cb(self, menuitem): - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - fprs = [] - for path in paths: - it = mod.get_iter(path) - jid, fpr = mod.get(it, 0, 6) - fprs.append('%s: %s' % (jid, potr.human_hash(fpr))) - gtk.Clipboard().set_text('\n'.join(fprs)) - gtk.Clipboard(selection='PRIMARY').set_text('\n'.join(fprs)) - - def flags_toggled_cb(self, button): - if button == self.B.get_object('enable_check'): - new_status = button.get_active() - self.B.get_object('advertise_check').set_sensitive(new_status) - self.B.get_object('autoinitiate_check').set_sensitive(new_status) - self.B.get_object('require_check').set_sensitive(new_status) - - if new_status is False: - self.B.get_object('advertise_check').set_active(False) - self.B.get_object('autoinitiate_check').set_active(False) - self.B.get_object('require_check').set_active(False) - - box = self.B.get_object('account_combobox') - active = box.get_active() - if active > -1: - account = self.otr_account_store[active][0] - - flagValues = {} - for key, box in self.flags.iteritems(): - flagValues[key] = box.get_active() - self.plugin.set_flags(flagValues, account) - - def account_combobox_changed_cb(self, box, *args): - fpr_label = self.B.get_object('fingerprint_label') - regen_button = self.B.get_object('regenerate_button') - - active = box.get_active() - fpr = '-------- -------- -------- -------- --------' - try: - if active > -1: - regen_button.set_sensitive(True) - account = self.otr_account_store[active][0] - - otr_flags = self.plugin.get_flags(account) - for key, box in self.flags.iteritems(): - box.set_active(otr_flags[key]) - - fpr = str(self.plugin.us[account].getPrivkey(autogen=False)) - regen_button.set_label('Regenerate') - else: - regen_button.set_sensitive(False) - except LookupError, e: - # Account not found, no private key available - display the - # empty one - regen_button.set_label('Generate') - finally: - self.B.get_object('fingerprint_label').set_markup('%s'%fpr) - - def forget_button_clicked_cb(self, button, *args): - accounts = {} - for acc in gajim.connections.iterkeys(): - accounts[gajim.get_jid_from_account(acc)] = acc - - - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - for path in paths: - it = mod.get_iter(path) - user, human_fpr, a, fpr = mod.get(it, 0, 3, 4, 6) - - dlg = gtk.Dialog('Confirm removal of fingerprint', self, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, - (gtk.STOCK_YES, gtk.RESPONSE_YES, - gtk.STOCK_NO, gtk.RESPONSE_NO) - ) - l = gtk.Label() - l.set_markup('Are you sure you want remove the following ' - 'fingerprint for the contact %s on the account %s?' - '\n\n%s' % (user, a, human_fpr)) - l.set_line_wrap(True) - dlg.vbox.pack_start(l) - dlg.show_all() - - if dlg.run() == gtk.RESPONSE_YES: - ctx = self.plugin.us[accounts[a]].getContext(user) - ctx.removeFingerprint(fpr) - dlg.destroy() - self.plugin.us[accounts[a]].saveTrusts() - - self.plugin.update_context_list() - - def verify_button_clicked_cb(self, button, *args): - accounts = {} - for acc in gajim.connections.iterkeys(): - accounts[gajim.get_jid_from_account(acc)] = acc - - mod, paths = self.fpr_view.get_selection().get_selected_rows() - - # open the window for the first selected row - for path in paths[0:1]: - it = mod.get_iter(path) - fjid, fpr, a = mod.get(it, 0, 6, 4) - - ctx = self.plugin.us[accounts[a]].getContext(fjid) - - dlg = ContactOtrWindow(self.plugin, ctx, fpr=fpr, parent=self) - dlg.run() - dlg.destroy() - break - - def regenerate_button_clicked_cb(self, button, *args): - box = self.B.get_object('account_combobox') - active = box.get_active() - if active > -1: - account = self.otr_account_store[active][0] - button.set_sensitive(False) - try: - self.plugin.us[account].getPrivkey(autogen=False) - self.plugin.us[account].dropPrivkey() - except LookupError: - pass - self.plugin.us[account].getPrivkey(autogen=True) - self.account_combobox_changed_cb(box, *args) - button.set_sensitive(True) - - -import gtkgui_helpers -from common import gajim - -our_fp_text = _('Your fingerprint:\n' \ - '%s') -their_fp_text = _('Purported fingerprint for %(jid)s:\n' \ - '%(fp)s') - -another_q = _('You may want to authenticate your buddy as well by asking'\ - 'your own question.') -smp_query = _('%s is trying to authenticate you using a secret only known '\ - 'to him/her and you.') -smp_q_query = _('%s has chosen a question for you to answer to '\ - 'authenticate yourself:') -enter_secret = _('Please enter your secret below.') - -smp_init = _('You are trying to authenticate %s using a secret only known ' \ - 'to him/her and yourself.') -choose_q = _('You can choose a question as a hint for your buddy below.') - -class ContactOtrSmpWindow: - def gw(self, n): - return self.xml.get_object(n) - - def __init__(self, ctx): - self.question = None - self.smp_running = False - self.ctx = ctx - self.account = ctx.user.accountname - - self.plugin = ctx.user.plugin - - self.GTK_BUILDER_FILE_PATH = \ - self.plugin.local_file_path('contact_otr_window.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain('gajim_plugins') - self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH) - - self.window = self.gw('otr_smp_window') - self.window.set_title(_('OTR settings for %s') % ctx.peer) - - # the lambda thing is an anonymous helper that just discards the - # parameters and calls hide_on_delete on clicking the window's - # close button - self.window.connect('delete-event', lambda d,o: - self.window.hide_on_delete()) - - self.gw('smp_cancel_button').connect('clicked', self._on_destroy) - self.gw('smp_ok_button').connect('clicked', self._apply) - self.gw('qcheckbutton').connect('toggled', self._toggle) - - self.gw('qcheckbutton').set_no_show_all(False) - self.gw('qentry').set_no_show_all(False) - self.gw('desclabel2').set_no_show_all(False) - - def _toggle(self, w, *args): - self.gw('qentry').set_sensitive(w.get_active()) - - def show(self, response): - self.smp_running = False - self.finished = False - - self.gw('smp_cancel_button').set_sensitive(True) - self.gw('smp_ok_button').set_sensitive(True) - self.gw('progressbar').set_fraction(0) - self.gw('secret_entry').set_text('') - - self.response = response - self.window.show_all() - if response: - self.gw('qcheckbutton').set_sensitive(False) - if self.question is None: - self.gw('qcheckbutton').set_active(False) - self.gw('qcheckbutton').hide() - self.gw('qentry').hide() - self.gw('desclabel2').hide() - self.gw('qcheckbutton').set_sensitive(False) - self.gw('desclabel1').set_markup((smp_query % self.ctx.peer) - + ' ' + enter_secret) - else: - self.gw('qcheckbutton').set_active(True) - self.gw('qcheckbutton').show() - self.gw('qentry').show() - self.gw('qentry').set_sensitive(True) - self.gw('qentry').set_editable(False) - self.gw('desclabel2').show() - self.gw('qentry').set_text(self.question) - - self.gw('desclabel1').set_markup(smp_q_query % self.ctx.peer) - self.gw('desclabel2').set_markup(enter_secret) - else: - self.gw('qcheckbutton').show() - self.gw('qcheckbutton').set_active(True) - self.gw('qcheckbutton').set_mode(True) - self.gw('qcheckbutton').set_sensitive(True) - self.gw('qentry').set_sensitive(True) - self.gw('qentry').show() - self.gw('qentry').set_text("") - - self.gw('qentry').set_editable(True) - self.gw('qentry').set_sensitive(True) - - self.gw('desclabel2').show() - self.gw('desclabel1').set_markup((smp_init % self.ctx.peer) + ' ' - + choose_q) - self.gw('desclabel2').set_markup(enter_secret) - - def _abort(self, text=None, appdata=None): - self.smp_running = False - - self.ctx.smpAbort(appdata=appdata) - if text: - self.plugin.gajim_log(text, self.account, self.ctx.peer) - - def _finish(self, text): - self.smp_running = False - self.finished = True - - self.gw('qcheckbutton').set_active(False) - self.gw('qcheckbutton').hide() - self.gw('qentry').hide() - self.gw('desclabel2').hide() - - self.gw('qcheckbutton').set_sensitive(False) - self.gw('smp_cancel_button').set_sensitive(False) - self.gw('smp_ok_button').set_sensitive(True) - self.gw('progressbar').set_fraction(1) - self.plugin.gajim_log(text, self.account, self.ctx.peer) - self.gw('desclabel1').set_markup(text) - - self.plugin.update_otr(self.ctx.peer, self.account, True) - self.ctx.user.saveTrusts() - self.plugin.update_context_list() - - def get_tlv(self, tlvs, check): - for tlv in tlvs: - if isinstance(tlv, check): - return tlv - return None - - def handle_tlv(self, tlvs): - if tlvs: - is1qtlv = self.get_tlv(tlvs, potr.proto.SMP1QTLV) - # check for TLV_SMP_ABORT or state = CHEATED - if self.smp_running and not self.ctx.smpIsValid(): - self._finish(_('SMP verifying aborted')) - - # check for TLV_SMP1 - elif self.get_tlv(tlvs, potr.proto.SMP1TLV): - self.smp_running = True - self.question = None - self.show(True) - self.gw('progressbar').set_fraction(0.3) - - # check for TLV_SMP1Q - elif is1qtlv: - self.smp_running = True - self.question = is1qtlv.msg - self.show(True) - self.gw('progressbar').set_fraction(0.3) - - # check for TLV_SMP2 - elif self.get_tlv(tlvs, potr.proto.SMP2TLV): - self.gw('progressbar').set_fraction(0.6) - - # check for TLV_SMP3 - elif self.get_tlv(tlvs, potr.proto.SMP3TLV): - if self.ctx.smpIsSuccess(): - text = _('SMP verifying succeeded') - if self.question is not None: - text += ' '+another_q - self._finish(text) - else: - self._finish(_('SMP verifying failed')) - - # check for TLV_SMP4 - elif self.get_tlv(tlvs, potr.proto.SMP4TLV): - if self.ctx.smpIsSuccess(): - text = _('SMP verifying succeeded') - if self.question is not None: - text += ' '+another_q - self._finish(text) - else: - self._finish(_('SMP verifying failed')) - - def _on_destroy(self, widget): - if self.smp_running: - self._abort(_('user aborted SMP authentication')) - self.window.hide_all() - - def _apply(self, widget, appdata=None): - if self.finished: - self.window.hide_all() - return - secret = self.gw('secret_entry').get_text() - if self.response: - self.ctx.smpGotSecret(secret, appdata=appdata) - else: - if self.gw('qcheckbutton').get_active(): - qtext = self.gw('qentry').get_text() - self.ctx.smpInit(secret, question=qtext, appdata=appdata) - else: - self.ctx.smpInit(secret, appdata=appdata) - self.gw('progressbar').set_fraction(0.3) - self.smp_running = True - widget.set_sensitive(False) - -class ContactOtrWindow(gtk.Dialog): - def gw(self, n): - return self.xml.get_object(n) - - def __init__(self, plugin, ctx, fpr=None, parent=None): - fjid = ctx.peer - gtk.Dialog.__init__(self, title=_('OTR settings for %s') % fjid, - parent=parent, - flags=gtk.DIALOG_DESTROY_WITH_PARENT, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, - gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) - - self.ctx = ctx - self.fjid = fjid - self.jid = gajim.get_room_and_nick_from_fjid(self.fjid)[0] - self.account = ctx.user.accountname - self.fpr = fpr - self.plugin = plugin - - if self.fpr is None: - key = self.ctx.getCurrentKey() - if key is not None: - self.fpr = key.cfingerprint() - - self.GTK_BUILDER_FILE_PATH = \ - self.plugin.local_file_path('contact_otr_window.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain('gajim_plugins') - self.xml.add_from_file(self.GTK_BUILDER_FILE_PATH) - self.notebook = self.gw('otr_settings_notebook') - self.child.pack_start(self.notebook) - - self.connect('response', self.on_response) - self.gw('otr_default_checkbutton').connect('toggled', - self._otr_default_checkbutton_toggled) - - # always set the label containing our fingerprint - self.gw('our_fp_label').set_markup(our_fp_text % ctx.user.getPrivkey()) - - if self.fpr is None: - # make the fingerprint widgets insensitive - # when not encrypted - for widget in self.gw('otr_fp_vbox').get_children(): - widget.set_sensitive(False) - # show that the fingerprint is unknown - self.gw('their_fp_label').set_markup(their_fp_text % { - 'jid': self.fjid, 'fp': _('unknown')}) - self.gw('verified_combobox').set_active(-1) - else: - # make the fingerprint widgets sensitive when encrypted - for widget in self.gw('otr_fp_vbox').get_children(): - widget.set_sensitive(True) - # show their fingerprint - fp = potr.human_hash(self.fpr) - self.gw('their_fp_label').set_markup(their_fp_text % { - 'jid': self.fjid, 'fp': fp}) - # set the trust combobox - if ctx.getCurrentTrust(): - self.gw('verified_combobox').set_active(1) - else: - self.gw('verified_combobox').set_active(0) - - otr_flags = self.plugin.get_flags(self.account, self.jid, fallback=False) - - if otr_flags is not None: - self.gw('otr_default_checkbutton').set_active(0) - for w in self.gw('otr_settings_vbox').get_children(): - w.set_sensitive(True) - else: - # per-user settings not available, - # using default settings - otr_flags = self.plugin.get_flags(self.account) - self.gw('otr_default_checkbutton').set_active(1) - for w in self.gw('otr_settings_vbox').get_children(): - w.set_sensitive(False) - - self.gw('otr_policy_allow_v2_checkbutton').set_active( - otr_flags['ALLOW_V2']) - self.gw('otr_policy_require_checkbutton').set_active( - otr_flags['REQUIRE_ENCRYPTION']) - self.gw('otr_policy_send_tag_checkbutton').set_active( - otr_flags['SEND_TAG']) - self.gw('otr_policy_start_on_tag_checkbutton').set_active( - otr_flags['WHITESPACE_START_AKE']) - - self.child.show_all() - - def on_response(self, dlg, response, *args): - if response != gtk.RESPONSE_ACCEPT: - return - - - # -1 when nothing is selected - # (ie. the connection is not encrypted) - trust_state = self.gw('verified_combobox').get_active() - if trust_state == 1 and not self.ctx.getTrust(self.fpr): - self.ctx.setTrust(self.fpr, 'verified') - self.ctx.user.saveTrusts() - self.plugin.update_context_list() - elif trust_state == 0: - self.ctx.setTrust(self.fpr, '') - self.ctx.user.saveTrusts() - self.plugin.update_context_list() - - self.plugin.update_otr(self.ctx.peer, self.ctx.user.accountname, True) - - if self.gw('otr_default_checkbutton').get_active(): - # default is enabled, so remove any user-specific - # settings if available - self.plugin.set_flags(None, self.account, self.jid) - else: - # build the flags using the checkboxes - flags = {} - flags['ALLOW_V2'] = \ - self.gw('otr_policy_allow_v2_checkbutton').get_active() - flags['REQUIRE_ENCRYPTION'] = \ - self.gw('otr_policy_require_checkbutton').get_active() - flags['SEND_TAG'] = \ - self.gw('otr_policy_send_tag_checkbutton').get_active() - flags['WHITESPACE_START_AKE'] = \ - self.gw('otr_policy_start_on_tag_checkbutton').get_active() - - self.plugin.set_flags(flags, self.account, self.jid) - - def _otr_default_checkbutton_toggled(self, widget): - for w in self.gw('otr_settings_vbox').get_children(): - w.set_sensitive(not widget.get_active()) - -def get_otr_submenu(plugin, control): - GTK_BUILDER_FILE_PATH = \ - plugin.local_file_path('contact_otr_window.ui') - xml = gtk.Builder() - xml.set_translation_domain('gajim_plugins') - xml.add_from_file(GTK_BUILDER_FILE_PATH) - - otr_submenu = xml.get_object('otr_submenu') - otr_settings_menuitem, smp_otr_menuitem, start_otr_menuitem, \ - end_otr_menuitem = otr_submenu.get_submenu().get_children() - - otr_submenu.set_sensitive(True) - otr_settings_menuitem.connect('activate', plugin.menu_settings_cb, control) - start_otr_menuitem.connect('activate', plugin.menu_start_cb, control) - end_otr_menuitem.connect('activate', plugin.menu_end_cb, control) - smp_otr_menuitem.connect('activate', plugin.menu_smp_cb, control) - - enc, _, fin = plugin.get_otr_status(control.account, control.contact) - # can end only when not in PLAINTEXT - end_otr_menuitem.set_sensitive(enc) - # can SMP only when ENCRYPTED - smp_otr_menuitem.set_sensitive(enc and not fin) - return otr_submenu diff --git a/offline_bookmarks/__init__.py b/offline_bookmarks/__init__.py deleted file mode 100644 index ef43880..0000000 --- a/offline_bookmarks/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - -from offline_bookmarks import OfflineBookmarksPlugin diff --git a/offline_bookmarks/config_dialog.ui b/offline_bookmarks/config_dialog.ui deleted file mode 100644 index 0091b86..0000000 --- a/offline_bookmarks/config_dialog.ui +++ /dev/null @@ -1,458 +0,0 @@ - - - - - - 12 - Manage Bookmarks - 550 - 300 - dialog - - - True - vertical - 12 - - - True - 12 - - - True - vertical - 6 - - - True - True - automatic - automatic - in - - - True - True - False - - - - - 0 - - - - - True - 6 - end - - - gtk-add - True - True - True - False - True - - - - False - False - 0 - - - - - gtk-remove - True - True - True - False - True - - - - False - False - 1 - - - - - False - 1 - - - - - 0 - - - - - 11 - 2 - 12 - 6 - - - True - 0 - _Password: - True - - - 4 - 5 - GTK_FILL - - - - - - True - False - True - False - - - 1 - 2 - 4 - 5 - - - - - - True - False - True - - - 1 - 2 - 3 - 4 - - - - - - True - 0 - _Server: - True - - - 3 - 4 - GTK_FILL - - - - - - True - 0 - Roo_m: - True - - - 2 - 3 - GTK_FILL - - - - - - True - False - True - - - 1 - 2 - 2 - 3 - - - - - - True - False - True - - - 1 - 2 - 1 - 2 - - - - - - True - 0 - _Nickname: - True - - - 1 - 2 - GTK_FILL - - - - - - True - 0 - _Title: - True - - - GTK_FILL - - - - - - True - False - True - - - 1 - 2 - - - - - - True - 0 - Pr_int status: - True - - - 6 - 7 - GTK_FILL - - - - - - True - False - - - - - 0 - - - - - 1 - 2 - 6 - 7 - GTK_FILL - GTK_FILL - - - - - True - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - - - A_uto join - True - False - True - False - If checked, Gajim will join this group chat on startup - True - True - - - - 0 - - - - - Minimi_ze on Auto Join - True - False - True - False - GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK - True - True - - - - 1 - - - - - 2 - 5 - 6 - GTK_FILL - - - - - True - 0 - 1 - 4 - Import bookmarks: - - - 2 - 7 - 8 - - - - - True - 0 - Import from: - - - 9 - 10 - - - - - True - False - True - True - False - - - - True - - - True - 1 - gtk-add - - - 0 - - - - - True - 0 - Import - - - end - 1 - - - - - - - 1 - 2 - 10 - 11 - - - - - True - True - False - - - - - 0 - - - - - 1 - 2 - 9 - 10 - GTK_FILL - GTK_FILL - - - - - True - 0 - Import to: - - - 8 - 9 - GTK_FILL - - - - - - - - - True - True - False - - - - - 0 - - - - - 1 - 2 - 8 - 9 - GTK_FILL - GTK_FILL - - - - - False - 1 - - - - - 0 - - - - - - - - - diff --git a/offline_bookmarks/manifest.ini b/offline_bookmarks/manifest.ini deleted file mode 100644 index f365aac..0000000 --- a/offline_bookmarks/manifest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[info] -name: Offline Bookmarks -short_name: offline_bookmarks -version: 0.2.1 -description: Saving bookmarks inside the plugin configuration file. Allows the use of locally stored bookmarks if the server does not support the storage of bookmarks (eg talk.google.com). - Support to import bookmarks from one account to another. -authors = Denis Fomin -homepage = http://trac-plugins.gajim.org/wiki/OfflineBookmarksPlugin -max_gajim_version: 0.15.9 diff --git a/offline_bookmarks/offline_bookmarks.png b/offline_bookmarks/offline_bookmarks.png deleted file mode 100644 index 6cf6443..0000000 Binary files a/offline_bookmarks/offline_bookmarks.png and /dev/null differ diff --git a/offline_bookmarks/offline_bookmarks.py b/offline_bookmarks/offline_bookmarks.py deleted file mode 100644 index 285490f..0000000 --- a/offline_bookmarks/offline_bookmarks.py +++ /dev/null @@ -1,371 +0,0 @@ -# -*- coding: utf-8 -*- -## - -import gtk - -import gtkgui_helpers -from plugins.gui import GajimPluginConfigDialog -from plugins import GajimPlugin -from plugins.helpers import log_calls -from common import ged -from common import app -from common.i18n import Q_ -from config import ManageBookmarksWindow - - -class OfflineBookmarksPlugin(GajimPlugin): - - @log_calls('OfflineBookmarksPlugin') - def init(self): - self.description = _('Saving bookmarks inside the plugin configuration ' - 'file. Allows the use of locally stored bookmarks if the server ' - 'does not support the storage of bookmarks (eg talk.google.com).\n' - 'Support to import bookmarks from one account to another.') - - self.events_handlers = { - 'bookmarks-received': (ged.POSTGUI, self.bookmarks_received), - 'signed-in': (ged.POSTGUI, self.handle_event_signed_in),} - - self.gui_extension_points = { - 'groupchat_control': (self.connect_with_gc_control, - self.disconnect_from_gc_control),} - self.controls = [] - self.config_dialog = OfflineBookmarksPluginConfigDialog(self) - - @log_calls('OfflineBookmarksPlugin') - def activate(self): - pass - - @log_calls('OfflineBookmarksPlugin') - def deactivate(self): - pass - - def save_bookmarks(self, account, bookmarks): - jid = app.get_jid_from_account(account) - if jid not in self.config: - self.config[jid] = {} - self.config[jid] = bookmarks - - def bookmarks_received(self, obj): - self.save_bookmarks(obj.conn.name, obj.bookmarks) - - def handle_event_signed_in(self, obj): - account = obj.conn.name - connection = app.connections[account] - jid = app.get_jid_from_account(obj.conn.name) - bm_jids = [b['jid'] for b in connection.bookmarks] - if jid in self.config: - for bm in self.config[jid]: - if bm['jid'] not in bm_jids: - connection.bookmarks.append(bm) - invisible_show = app.SHOW_LIST.index('invisible') - # do not autojoin if we are invisible - if connection.connected == invisible_show: - return - # do not autojoin if bookmarks supported - bookmarks_supported = self.is_bookmark_supported( - app.connections[account]) - if not bookmarks_supported: - app.interface.auto_join_bookmarks(connection.name) - - def connect_with_gc_control(self, gc_control): - control = Base(self, gc_control) - self.controls.append(control) - - def disconnect_from_gc_control(self, gc_control): - for control in self.controls: - control.disconnect_from_gc_control() - self.controls = [] - - def is_bookmark_supported(self, account): - if account.is_zeroconf: - return False - return (account.private_storage_supported or ( - account.pubsub_supported and account.pubsub_publish_options_supported)) - - -class Base(object): - def __init__(self, plugin, gc_control): - self.plugin = plugin - self.gc_control = gc_control - self.create_buttons() - - def create_buttons(self): - # create button - actions_hbox = self.gc_control.xml.get_object('actions_hbox') - self.button = gtk.Button(label=None, stock=None, use_underline=True) - self.button.set_property('relief', gtk.RELIEF_NONE) - self.button.set_property('can-focus', False) - img = gtk.Image() - if gtkgui_helpers.gtk_icon_theme.has_icon('bookmark-new'): - img.set_from_icon_name('bookmark-new', gtk.ICON_SIZE_MENU) - else: - img.set_from_stock('gtk-add', gtk.ICON_SIZE_MENU) - self.button.set_image(img) - self.button.set_tooltip_text(_('Bookmark this room(local)')) - send_button = self.gc_control.xml.get_object('send_button') - send_button_pos = actions_hbox.child_get_property(send_button, - 'position') - actions_hbox.add_with_properties(self.button, 'position', - send_button_pos - 1, 'expand', False) - self.button.set_no_show_all(True) - id_ = self.button.connect('clicked', self.add_bookmark_button_clicked) - self.gc_control.handlers[id_] = self.button - for bm in app.connections[self.gc_control.account].bookmarks: - if bm['jid'] == self.gc_control.contact.jid: - self.button.hide() - break - else: - account = self.gc_control.account - bookmarks_supported = self.plugin.is_bookmark_supported( - app.connections[account]) - self.button.set_sensitive(not bookmarks_supported) - self.button.set_visible(not bookmarks_supported) - - def add_bookmark_button_clicked(self, widget): - """ - Bookmark the room, without autojoin and not minimized - """ - from dialogs import ErrorDialog, InformationDialog - password = app.gc_passwords.get(self.gc_control.room_jid, '') - account = self.gc_control.account - - bm = {'name': self.gc_control.name, - 'jid': self.gc_control.room_jid, - 'autojoin': 0, - 'minimize': 0, - 'password': password, - 'nick': self.gc_control.nick} - - place_found = False - index = 0 - # check for duplicate entry and respect alpha order - for bookmark in app.connections[account].bookmarks: - if bookmark['jid'] == bm['jid']: - ErrorDialog( - _('Bookmark already set'), - _('Group Chat "%s" is already in your bookmarks.') % \ - bm['jid']) - return - if bookmark['name'] > bm['name']: - place_found = True - break - index += 1 - if place_found: - app.connections[account].bookmarks.insert(index, bm) - else: - app.connections[account].bookmarks.append(bm) - self.plugin.save_bookmarks(account, app.connections[account].bookmarks) - app.interface.roster.set_actions_menu_needs_rebuild() - InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) - - def disconnect_from_gc_control(self): - actions_hbox = self.gc_control.xml.get_object('actions_hbox') - actions_hbox.remove(self.button) - - -class OfflineBookmarksPluginConfigDialog(GajimPluginConfigDialog, - ManageBookmarksWindow): - def init(self): - self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( - 'config_dialog.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain('gajim_plugins') - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, - ['vbox86']) - vbox = self.xml.get_object('vbox86') - self.child.pack_start(vbox) - self.import_from_combo = self.xml.get_object('import_from') - self.import_to_combo = self.xml.get_object('import_to') - - def on_run(self): - self.fill_treeview() - - #Prepare comboboxes - self.print_status_combobox = self.xml.get_object('print_status_combobox') - model = gtk.ListStore(str, str) - self.option_list = {'': _('Default'), 'all': Q_('?print_status:All'), - 'in_and_out': _('Enter and leave only'), - 'none': Q_('?print_status:None')} - opts = sorted(self.option_list.keys()) - for opt in opts: - model.append([self.option_list[opt], opt]) - self.print_status_combobox.set_model(model) - self.print_status_combobox.set_active(1) - #Prepare import_from combobox - model = gtk.ListStore(str) - for account in self.accounts: - model.append([account,]) - for account_jid in self.plugin.config: - if account_jid not in self.plugin.config_default_values and \ - account_jid not in self.jids: - model.append([account_jid,]) - self.import_from_combo.set_model(model) - #Prepare import_to combobox - model = gtk.ListStore(str) - for account in self.accounts: - model.append([account,]) - self.import_to_combo.set_model(model) - - self.selection = self.view.get_selection() - self.selection.connect('changed', self.bookmark_selected) - - #Prepare input fields - self.title_entry = self.xml.get_object('title_entry') - self.title_entry.connect('changed', self.on_title_entry_changed) - self.nick_entry = self.xml.get_object('nick_entry') - self.nick_entry.connect('changed', self.on_nick_entry_changed) - self.server_entry = self.xml.get_object('server_entry') - self.server_entry.connect('changed', self.on_server_entry_changed) - self.room_entry = self.xml.get_object('room_entry') - self.room_entry.connect('changed', self.on_room_entry_changed) - self.pass_entry = self.xml.get_object('pass_entry') - self.pass_entry.connect('changed', self.on_pass_entry_changed) - self.autojoin_checkbutton = self.xml.get_object('autojoin_checkbutton') - self.minimize_checkbutton = self.xml.get_object('minimize_checkbutton') - - self.xml.connect_signals(self) - self.connect('hide', self.on_hide) - - - self.show_all() - self.view.set_cursor((0,)) - - def fill_treeview(self): - # Account-JID, RoomName, Room-JID, Autojoin, Minimize, Passowrd, Nick, - # Show_Status - self.treestore = gtk.TreeStore(str, str, str, bool, bool, str, str, str) - self.treestore.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.accounts = [] - self.jids = [] - - # Store bookmarks in treeview. - for account in app.connections: - if app.connections[account].connected <= 1: - continue - if app.connections[account].is_zeroconf: - continue - - self.accounts.append(account) - self.jids.append(app.get_jid_from_account(account)) - iter_ = self.treestore.append(None, [None, account, None, None, - None, None, None, None]) - - for bookmark in app.connections[account].bookmarks: - if bookmark['name'] == '': - # No name was given for this bookmark. - # Use the first part of JID instead... - name = bookmark['jid'].split("@")[0] - bookmark['name'] = name - from common import helpers - # make '1', '0', 'true', 'false' (or other) to True/False - autojoin = helpers.from_xs_boolean_to_python_boolean( - bookmark['autojoin']) - - minimize = helpers.from_xs_boolean_to_python_boolean( - bookmark['minimize']) - - print_status = bookmark.get('print_status', '') - if print_status not in ('', 'all', 'in_and_out', 'none'): - print_status = '' - self.treestore.append(iter_, [ - account, - bookmark['name'], - bookmark['jid'], - autojoin, - minimize, - bookmark['password'], - bookmark['nick'], - print_status ]) - - self.view = self.xml.get_object('bookmarks_treeview') - self.view.set_model(self.treestore) - self.view.expand_all() - - renderer = gtk.CellRendererText() - column = gtk.TreeViewColumn('Bookmarks', renderer, text=1) - if self.view.get_column(0): - self.view.remove_column(self.view.get_column(0)) - self.view.append_column(column) - - def on_hide(self, widget): - """ - Parse the treestore data into our new bookmarks array, then send the new - bookmarks to the server. - """ - (model, iter_) = self.selection.get_selected() - if iter_ and model.iter_parent(iter_): - #bookmark selected, check it - if not self.check_valid_bookmark(): - return - - for account in self.treestore: - account_unicode = account[1].decode('utf-8') - app.connections[account_unicode].bookmarks = [] - - for bm in account.iterchildren(): - # Convert True/False/None to '1' or '0' - autojoin = unicode(int(bm[3])) - minimize = unicode(int(bm[4])) - name = bm[1] - if name: - name = name.decode('utf-8') - jid = bm[2] - if jid: - jid = jid.decode('utf-8') - pw = bm[5] - if pw: - pw = pw.decode('utf-8') - nick = bm[6] - if nick: - nick = nick.decode('utf-8') - - # create the bookmark-dict - bmdict = { 'name': name, 'jid': jid, 'autojoin': autojoin, - 'minimize': minimize, 'password': pw, 'nick': nick, - 'print_status': bm[7]} - - app.connections[account_unicode].bookmarks.append(bmdict) - - bookmarks_supported = self.plugin.is_bookmark_supported( - app.connections[account_unicode]) - if bookmarks_supported: - app.connections[account_unicode].store_bookmarks() - self.plugin.save_bookmarks(account_unicode, - app.connections[account_unicode].bookmarks) - app.interface.roster.set_actions_menu_needs_rebuild() - - def on_import_to_changed(self, treeview): - self.on_import_from_changed(self.import_from_combo) - - def on_import_from_changed(self, widget): - if widget.get_active() == -1 or self.import_to_combo.get_active() == -1: - self.xml.get_object('import_button').set_sensitive(False) - else: - if widget.get_active_text() != self.import_to_combo.get_active_text(): - self.xml.get_object('import_button').set_sensitive(True) - else: - self.xml.get_object('import_button').set_sensitive(False) - - def on_import_button_clicked(self, widget): - from_ = self.import_from_combo.get_active_text() - to_connection = app.connections[self.import_to_combo.get_active_text()] - to_bookmarks = to_connection.bookmarks - - if from_ in self.accounts: - from_bookmarks = app.connections[from_].bookmarks - else: - from_bookmarks = self.plugin.config[from_] - for bm in from_bookmarks: - for bookmark in to_bookmarks: - if bookmark['jid'] == bm['jid']: - break - else: - to_bookmarks.append(bm) - - self.fill_treeview() - self.view.set_cursor((0,)) - self.import_from_combo.set_active(-1) - self.import_to_combo.set_active(-1) diff --git a/snarl_notifications/__init__.py b/snarl_notifications/__init__.py deleted file mode 100644 index b504dfd..0000000 --- a/snarl_notifications/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from plugin import SnarlNotificationsPlugin diff --git a/snarl_notifications/manifest.ini b/snarl_notifications/manifest.ini deleted file mode 100644 index 52d6f98..0000000 --- a/snarl_notifications/manifest.ini +++ /dev/null @@ -1,9 +0,0 @@ -[info] -name: Snarl Notifications -short_name: snarl_notifications -version: 0.1.2 -description: Shows events notification using Snarl (http://www.fullphat.net/) under Windows. Snarl needs to be installed in system. - PySnarl bindings are used (http://sourceforge.net/projects/pysnarl/). -authors = Yann Leboulanger -homepage = http://trac-plugins.gajim.org/wiki/SnarlNotificationsPlugin -max_gajim_version: 0.15.9 diff --git a/snarl_notifications/plugin.py b/snarl_notifications/plugin.py deleted file mode 100644 index 659fdf3..0000000 --- a/snarl_notifications/plugin.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## -''' -Events notifications using Snarl - -Fancy events notifications under Windows using Snarl infrastructure. - -:note: plugin is at proof-of-concept state. - -:author: Yann Leboulanger -:since: 08 April 2012 -:copyright: Copyright (2012) Yann Leboulanger -:license: GPL -''' - -import pySnarl - -from common import gajim -from plugins import GajimPlugin -from plugins.helpers import log_calls, log -from common import ged -import os.path - -class SnarlActionHandler(pySnarl.EventHandler): - def OnNotificationInvoked(self, uid): - account, jid, msg_type = uid.split() - gajim.interface.handle_event(account, jid, msg_type) - -class SnarlNotificationsPlugin(GajimPlugin): - - @log_calls('SnarlNotificationsPlugin') - def init(self): - self.description = _('Shows events notification using Snarl ' - '(http://www.fullphat.net/) under Windows. ' - 'Snarl needs to be installed in system.\n' - 'PySnarl bindings are used (http://code.google.com/p/pysnarl/).') - self.config_dialog = None - self.h = SnarlActionHandler - self.snarl_win = pySnarl.SnarlApp( - "pySnarl/Gajim", # app signature - "Gajim", # app title - os.path.abspath("..\data\pixmaps\gajim.ico"), # icon - "", # config Tool - "Gajim will use Snarl to display notifications", # hint - False, # IsDaemon - self.h, # event handler - [] # classes - ) - - self.events_handlers = {'notification' : (ged.PRECORE, self.notif)} - - @log_calls('SnarlNotificationsPlugin') - def notif(self, obj): - if obj.do_popup: - uid = obj.conn.name + " " + obj.jid + " " + obj.popup_msg_type - self.snarl_win.notify( - [], # actions - "", # callbackScript - "", # callbackScriptType - "", # class - "", # defaultCallback - 5, # duration - os.path.abspath(obj.popup_image),#r"C:\Documents and Settings\Administrateur\Mes documents\gajim\data\pixmaps\gajim.ico", # icon - "", # mergeUID - 0, # priority - "", # replaceUID - obj.popup_text, - obj.popup_title, - uid, # UID - "", # sound - -1, # percent - 0, # log - 64, # sensitivity - ) - obj.do_popup = False diff --git a/snarl_notifications/pySnarl.py b/snarl_notifications/pySnarl.py deleted file mode 100644 index 5488cbe..0000000 --- a/snarl_notifications/pySnarl.py +++ /dev/null @@ -1,365 +0,0 @@ -# -*- coding: utf-8 -*- -# ActiveX/COM Snarl python library -# For proper functioning requires Snarl 2.5.1 or later ! -# Version 0.0.2 - -# Changelog (in reverse chronological order): -# ------------------------------------------- -# 0.0.2 by Pako 2012-01-30 14:12 UTC+1 -# - base64 icon format is now supported -# 0.0.1 by Pako 2012-01-06 15:30 UTC+1 -# - initial version -#------------------------------------------------------- - -from win32com.client import constants, gencache, Dispatch, DispatchWithEvents -#=============================================================================== - -def IsRunning(): - from win32gui import FindWindow - return FindWindow("w>Snarl", "Snarl") -#=============================================================================== - -class EventHandler: - - def OnActivated(self): - self.fn("DaemonActivated") - - def OnNotificationActionSelected(self, uid, command): - self.fn("ActionSelected.%s" % command, payload = uid) - - def OnNotificationClosed(self, uid): - self.fn("Closed", payload = uid) - - def OnNotificationExpired(self, uid): - self.fn("Expired", payload = uid) - - def OnNotificationInvoked(self, uid): - self.fn("Invoked", payload = uid) - - def OnQuit(self): - self.fn("AppQuit") - - def OnShowAbout(self): - self.fn("ShowAbout") - - def OnShowConfig(self): - self.fn("ShowConfig") - - def OnSnarlLaunched(self): - self.fn("Launched") - - def OnSnarlQuit(self): - self.fn("Quit") - - def OnSnarlStarted(self): - self.fn("Started") - - def OnSnarlStopped(self): - self.fn("Stopped") - - def OnUserAway(self): - self.fn("UserAway") - - def OnUserReturned(self): - self.fn("UserReturned") -#=============================================================================== - -class SnarlApp(object): - """Creates an SnarlApp object""" - - def __init__( - self, - signature, - title, - icon = "", - configTool = "", - hint = "", - isDaemon = False, - eventHandler = None, - classes = [] - ): - self.app = gencache.EnsureDispatch("libsnarl25.SnarlApp") - for d in constants.__dicts__: # we must find the right dictionary ... - if 'ERROR_NOTIFICATION_NOT_FOUND' in d: # this is it ! - break - codes = d.items() - codes.sort(reverse = True) - self.statCodes = dict([[v,k] for k,v in codes[6:]]) - self.statCodes[codes[2][1]] = codes[2][0] # added SUCCESS - self.classes = NotifClasses(classes) - if eventHandler: - self.SetEventHandler(eventHandler) - self.SetTo( - configTool, - hint, - icon, - isDaemon, - signature, - title - ) - - - def SetTo( - self, - configTool, - hint, - icon, - isDaemon, - signature, - title - ): - self.app.Classes = self.classes.Classes() - self.app.ConfigTool = configTool - self.app.Hint = hint - self.app.Icon = icon - self.app.IsDaemon = isDaemon - self.app.Signature = signature - self.app.Title = title - - - def GetStatusCode(self, code): - return self.statCodes[code] - - - def SetEventHandler(self, handler): - self.events = DispatchWithEvents(self.app, handler) - - - def register(self): - return self.GetStatusCode(self.app.Register()) - - - def unregister(self): - return self.GetStatusCode(self.app.Unregister()) - - - def tidyUp(self): - self.app.TidyUp() - - - def addClass( - self, - classId, - name, - enabled = True, - title = "", - text = "", - icon = "", - callback = "", - duration = -1, - sound = "", - ): - cntOld = self.classes.count() - cntNew = self.classes.add(classId, name, enabled, title, text, icon, callback, duration, sound) - return int(not cntNew == cntOld + 1) - - - def remClass(self, id): - cntOld = self.classes.count() - cntNew = self.classes.remove(id) - return int(not cntNew == cntOld - 1) - - - def clearClasses(self): - return self.classes.makeEmpty() - - - def classesCount(self): - return self.classes.count() - - - def notify( - self, - actions, - callbackScript, - callbackScriptType, - cls, - defaultCallback, - duration, - icon, - mergeUID, - priority, - replaceUID, - text, - title, - uid, - sound = None, - percent = None, - log = None, - sensitivity = None - ): - note = Notification( - actions, - callbackScript, - callbackScriptType, - cls, - defaultCallback, - duration, - "", - mergeUID, - priority, - replaceUID, - text, - title, - uid, - ) - if icon: - if icon[1] == ":": - note.Add("icon", icon, True) - else: - note.Add("icon-base64", icon.replace("=","%"), True) - if sound: - note.Add("sound", sound, True) - if percent is not None and percent > -1: - note.Add("value-percent", str(percent), True) - if log is not None: - note.Add("log", str(log), True) - if sensitivity is not None: - note.Add("sensitivity", str(sensitivity), True) - return self.GetStatusCode(self.app.Show(note.Note())[0]) - - - def hideNotification(self, uid): - return self.GetStatusCode(self.app.Hide(uid)) - - - def isVisible(self, uid): - return self.GetStatusCode(self.app.IsVisible(uid)) - - - def getEtcPath(self): - return self.app.GetEtcPath() - - - def makePath(self, pth): - return self.app.GetEtcPath(pth) - - - def isInstalled(self): - return self.app.IsSnarlInstalled() - - - def isRunning(self): - return self.app.IsSnarlRunning() - - - def version(self): - ver = self.app.SnarlVersion() - return ver if ver > 0 else self.GetStatusCode(-ver) - - - def isConnected(self): - return self.app.IsConnected - - - def getLibVersion(self): - return self.app.LibVersion - - - def getLibRevision(self): - return self.app.LibRevision - - - def Destroy(self): - self.app.TidyUp() - if self.events: - del self.events - del self.app -#=============================================================================== - -class NotifClasses(object): - def __init__(self, classes = []): - self.clss = Dispatch("libsnarl25.Classes") - for cls in classes: - self.add(*cls) - - def count(self): - return self.clss.Count() - - def add(self, *cls): - self.clss.Add(*cls) - return self.clss.Count() - - def remove(self, cls): - self.clss.Remove(cls) - return self.clss.Count() - - def makeEmpty(self): - self.clss.MakeEmpty() - return self.clss.Count() - - def Classes(self): - return self.clss -#=============================================================================== - -class Notification(object): - def __init__( - self, - actions, - callbackScript, - callbackScriptType, - cls, - defaultCallback, - duration, - icon, - mergeUID, - priority, - replaceUID, - text, - title, - uid, - ): - nt = Dispatch("libsnarl25.Notification") - nt.Actions = NotifActions(actions).Actions() - nt.CallbackScript = callbackScript - nt.CallbackScriptType = callbackScriptType - nt.Class = cls - nt.DefaultCallback = defaultCallback - nt.Duration = duration - if icon: - nt.Icon = icon - nt.MergeUID = mergeUID - nt.Priority = priority - nt.ReplaceUID = replaceUID - nt.Text = text - nt.Title = title - nt.UID = uid - self.nt = nt - - def Add(self, name, value, update): - self.nt.Add(name, value, True) - - def Note(self): - return self.nt -#=============================================================================== - -class NotifActions(object): - def __init__(self, actions=[]): - self.actns = Dispatch("libsnarl25.Actions") - self.actions = actions - for action in actions: - self.actns.Add(*action) - - def add(self, actn): - self.actns.Add(*actn) - self.actions.append(actn) - return self.actns.Count() - - def remove(self, actn): - if actn in self.actions: - ix = self.actions.index(actn) - self.actns.Remove(ix + 1) - self.actions.pop(ix) - return self.actns.Count() - - def makeEmpty(self): - self.actns.MakeEmpty() - self.actions = [] - return self.actns.Count() - - def Actions(self): - return self.actns -#=============================================================================== - - - diff --git a/snarl_notifications/snarl_notifications.png b/snarl_notifications/snarl_notifications.png deleted file mode 100644 index 9535573..0000000 Binary files a/snarl_notifications/snarl_notifications.png and /dev/null differ diff --git a/ubuntu_integration/__init__.py b/ubuntu_integration/__init__.py deleted file mode 100644 index db3c5d3..0000000 --- a/ubuntu_integration/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from plugin import UbuntuIntegrationPlugin diff --git a/ubuntu_integration/doc/example.jpg b/ubuntu_integration/doc/example.jpg deleted file mode 100644 index 97fc76d..0000000 Binary files a/ubuntu_integration/doc/example.jpg and /dev/null differ diff --git a/ubuntu_integration/manifest.ini b/ubuntu_integration/manifest.ini deleted file mode 100644 index d60d83b..0000000 --- a/ubuntu_integration/manifest.ini +++ /dev/null @@ -1,12 +0,0 @@ -[info] -name: Ubuntu Ayatana Integration -short_name: ubuntu_integration -version: 0.1.4 -description: This plugin integrates Gajim with the Ubuntu Messaging Menu. - - You must have python-indicate and python-xdg (and Gajim obviously) installed to enable this plugin. - - Many thanks to the guys from gajim@conference.gajim.org for answering my questions :) -authors: Michael Kainer -homepage: http://trac-plugins.gajim.org/wiki/UbuntuIntegrationPlugin -max_gajim_version: 0.15.9 diff --git a/ubuntu_integration/plugin.py b/ubuntu_integration/plugin.py deleted file mode 100644 index 185e8d9..0000000 --- a/ubuntu_integration/plugin.py +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Ubuntu Ayatana Integration plugin. - -TODO: -* handle gc-invitation, subscription_request: it looks like they don't fire -* nice error if plugin can't load -* me menu -* permanent integration into the messaging menu after quitting gajim -* show/hide gajim on root menu entry -* switch workspace on click on events -* corrent group chat handling -* hide gajim if the plugin is disabled - -:author: Michael Kainer -:since: 21st October 2010 -:copyright: Copyright (2010) Michael Kainer -:license: GPLv3 -""" -# Python -import time -# Gajim -from plugins import GajimPlugin -from plugins.plugin import GajimPluginException -from plugins.helpers import log_calls -from common import gajim -import gtkgui_helpers -try: - from xdg.BaseDirectory import load_data_paths - import indicate -except ImportError: - pass - - -class UbuntuIntegrationPlugin(GajimPlugin): - """ - Class for Messaging Menu and Me Menu. - """ - - @log_calls("UbuntuIntegrationPlugin") - def init(self): - """ - Does nothing. - """ - self.description = _('This plugin integrates Gajim ' - 'with the Ubuntu Messaging Menu.\n\n' - 'You must have python-indicate and python-xdg (and Gajim obviously)' - ' installed to enable this plugin.\n\n' - 'Many thanks to the guys from gajim@conference.gajim.org for ' - 'answering my questions :)') - self.config_dialog = None - self.test_activatable() - - def test_activatable(self): - self.available_text = '' - try: - from xdg.BaseDirectory import load_data_paths - except ImportError: - self.activatable = False - self.available_text += _('python-xdg is missing! ' - 'Install python-xdg.\n') - try: - import indicate - except ImportError: - self.activatable = False - self.available_text += _('python-indicate is missing! ' - 'Install python-indicate.') - - @log_calls("UbuntuIntegrationPlugin") - def activate(self): - """ - Displays gajim in the Messaging Menu. - """ - # {(account, jid): (indicator, [event, ...]), ...} - self.events = {} - - version = gajim.version.split('-')[0] - if version == '0.15' and self.available_text: - raise GajimPluginException(self.available_text) - - self.server = indicate.indicate_server_ref_default() - self.server.set_type("message.im") - dfile = "" - for file in load_data_paths("applications/gajim.desktop"): - dfile = file - break - if not dfile: - raise GajimPluginException("Can't locate gajim.desktop!") - self.server.set_desktop_file(dfile) - self.server.show() - - gajim.events.event_added_subscribe(self.on_event_added) - gajim.events.event_removed_subscribe(self.on_event_removed) - - @log_calls("UbuntuIntegrationPlugin") - def deactivate(self): - """ - Cleaning up. - """ - gajim.events.event_added_unsubscribe(self.on_event_added) - gajim.events.event_removed_unsubscribe(self.on_event_removed) - - if hasattr(self, 'server'): - self.server.hide() - del self.server - - if hasattr(self, 'events'): - for (_, event) in self.events: - event[0].hide() - del self.events - - def on_indicator_activate(self, indicator, _): - """ - Forwards the action to gajims event handler. - """ - key = indicator.key - event = self.events[key][1][0] - gajim.interface.handle_event(event.account, event.jid, event.type_) - - def on_event_added(self, event): - """ - Adds "Nickname Time" to the Messaging menu. - """ - print "----", event.type_ - - # Basic variables - account = event.account - jid = event.jid - when = time.time() - contact = "" - key = (account, jid) - - # Check if the event is valid and modify the variables - if event.type_ == "chat" or \ - event.type_ == "printed_chat" or \ - event.type_ == "normal" or \ - event.type_ == "printed_normal" or \ - event.type_ == "file-request" or \ - event.type_ == "jingle-incoming": - contact = gajim.contacts.get_contact_from_full_jid(account, jid) - if contact: - contact = contact.get_shown_name() - else: - contact = jid - elif event.type_ == "pm" or event.type_ == "printed_pm": - contact = gajim.get_nick_from_jid(gajim.get_room_from_fjid(jid)) +\ - "/" + gajim.get_room_and_nick_from_fjid(jid)[1] - elif event.type_ == "printed_marked_gc_msg": - contact = gajim.get_nick_from_jid(gajim.get_room_from_fjid(jid)) - else: - print "ignored" - return - - print account, jid, when, contact - - # Add a new indicator if necessary - if key not in self.events: - indicator = indicate.Indicator() - indicator.set_property("name", contact) - indicator.set_property_time("time", when) - indicator.set_property_bool("draw-attention", True) - if gajim.config.get("show_avatars_in_roster"): - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) - if pixbuf not in (None, "ask"): - indicator.set_property_icon("icon", pixbuf) - indicator.connect("user-display", self.on_indicator_activate) - indicator.show() - indicator.key = key - self.events[key] = (indicator, []) - - # Prepare the event and save it - event.time = when - self.events[key][1].append(event) - - def on_event_removed(self, events): - """ - Goes through the events and removes them from the array and - the indicator if there are no longer any events pending. - """ - for event in events: - print "====", event.type_ - - key = (event.account, event.jid) - - if key not in self.events and \ - event in self.events[key][1]: - self.events[key][1].remove(event) - - if len(self.events[key][1]) == 0: # remove indicator - self.events[key][0].hide() - del self.events[key] - else: # set the indicator time to the text event - self.events[key][0].set_property_time("time", - self.events[key][1][0].time) - else: - print "ignored" diff --git a/ubuntu_integration/ubuntu_integration.png b/ubuntu_integration/ubuntu_integration.png deleted file mode 100644 index 959e99d..0000000 Binary files a/ubuntu_integration/ubuntu_integration.png and /dev/null differ diff --git a/url_shortener/__init__.py b/url_shortener/__init__.py deleted file mode 100644 index 3592535..0000000 --- a/url_shortener/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from url_shortener import UrlShortenerPlugin diff --git a/url_shortener/config_dialog.ui b/url_shortener/config_dialog.ui deleted file mode 100644 index d1424ec..0000000 --- a/url_shortener/config_dialog.ui +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - True - vertical - - - True - 0 - none - - - True - 2 - 2 - - - True - True - - 6 - True - True - - - - 1 - 2 - - - - - - True - True - - 6 - True - True - - - - 1 - 2 - 1 - 2 - GTK_EXPAND - - - - - True - 0 - 13 - incoming message - False - - - 1 - 2 - GTK_EXPAND - - - - - True - 0 - 12 - outgoing message - False - - - GTK_EXPAND - - - - - - - True - <b>The maximum length not be shortened links(chars):</b> - True - - - - - 6 - 0 - - - - - shorten links in outgoing messages - True - True - False - True - True - - - - 1 - - - - - - diff --git a/url_shortener/manifest.ini b/url_shortener/manifest.ini deleted file mode 100644 index b23ec79..0000000 --- a/url_shortener/manifest.ini +++ /dev/null @@ -1,12 +0,0 @@ -[info] -name: Url Shortener -short_name: url_shortener -version: 0.3.5 -description: Plugin that allows users to shorten a long URL in messages. - For example, you can turn this link: - https://trac.gajim.org/timeline - Into this link: - http://bit.ly/THy6ZK -authors: Denis Fomin -homepage: http://trac-plugins.gajim.org/wiki/UrlShortenerPlugin -max_gajim_version: 0.15.9 diff --git a/url_shortener/url_shortener.png b/url_shortener/url_shortener.png deleted file mode 100644 index fa1faa5..0000000 Binary files a/url_shortener/url_shortener.png and /dev/null differ diff --git a/url_shortener/url_shortener.py b/url_shortener/url_shortener.py deleted file mode 100644 index bd47efe..0000000 --- a/url_shortener/url_shortener.py +++ /dev/null @@ -1,232 +0,0 @@ -# -*- coding: utf-8 -*- - -import gtk -import json -import urllib -import urllib2 -from common import app -from common import ged -from plugins import GajimPlugin -from plugins.helpers import log_calls -from plugins.gui import GajimPluginConfigDialog - -APIKEY = 'R_fcba926fc7978bd19acbca73ec82b2be' -USER = 'dicson' - -class UrlShortenerPlugin(GajimPlugin): - @log_calls('UrlShortenerPlugin') - def init(self): - self.description = _('Plugin that allows users to shorten a long URL ' - 'in received messages.\n' - 'For example, you can turn this link:\n' - 'https://trac.gajim.org/timeline\n' - 'Into this link:\n' - 'http://bit.ly/THy6ZK') - self.config_dialog = UrlShortenerPluginConfigDialog(self) - self.gui_extension_points = { - 'chat_control_base': (self.connect_with_chat_control, - self.disconnect_from_chat_control), - 'print_special_text': (self.print_special_text, - self.print_special_text1),} - self.config_default_values = { - 'MAX_CHARS': (50, ('MAX_CHARS(30-...)')), - 'IN_MAX_CHARS': (50, ('MAX_CHARS(30-...)')), - 'SHORTEN_OUTGOING': (False, ''),} - self.events_handlers = {'message-outgoing': (ged.OUT_PRECORE, - self.handle_outgoing_msg), - 'gc-message-outgoing': (ged.OUT_PRECORE, - self.handle_outgoing_msg)} - self.chat_control = None - self.controls = [] - - def handle_outgoing_msg(self, event): - if not self.active: - return - if not event.message: - return - if not self.config['SHORTEN_OUTGOING']: - return - if hasattr(event, 'shortened'): - return - - iterator = app.interface.basic_pattern_re.finditer(event.message) - for match in iterator: - start, end = match.span() - link = event.message[start:end] - if len(link) < self.config['MAX_CHARS']: - continue - short_link = None - try: - params = urllib.urlencode({'longUrl': link, - 'login': USER, - 'apiKey': APIKEY, - 'format': 'json'}) - req = urllib2.Request('http://api.bit.ly/v3/shorten?%s' % params) - response = urllib2.urlopen(req) - j = json.load(response) - if j['status_code'] == 200: - short_link = j['data']['url'] - except urllib2.HTTPError, e: - pass - if short_link: - event.message = event.message.replace(link, short_link) - event.callback_args[1] = event.message - event.shortened = True - - @log_calls('UrlShortenerPlugin') - def connect_with_chat_control(self, chat_control): - self.chat_control = chat_control - control = Base(self, self.chat_control) - self.controls.append(control) - - @log_calls('UrlShortenerPlugin') - def disconnect_from_chat_control(self, chat_control): - for control in self.controls: - control.disconnect_from_chat_control() - self.controls = [] - - def print_special_text(self, tv, special_text, other_tags, graphics=True, - additional_data={}): - for control in self.controls: - if control.chat_control.conv_textview != tv: - continue - control.print_special_text(special_text, other_tags, graphics=True) - - def print_special_text1(self, chat_control, special_text, other_tags=None, - graphics=True, additional_data={}): - for control in self.controls: - if control.chat_control == chat_control: - control.disconnect_from_chat_control() - self.controls.remove(control) - - -class Base(object): - def __init__(self, plugin, chat_control): - self.plugin = plugin - self.chat_control = chat_control - self.textview = self.chat_control.conv_textview - - self.id_ = self.textview.tv.connect('motion_notify_event', - self.on_textview_motion_notify_event) - self.chat_control.handlers[self.id_] = self.textview.tv - - def print_special_text(self, special_text, other_tags, graphics=True): - if not self.plugin.active: - return - is_xhtml_link = None - text_is_valid_uri = False - buffer_ = self.textview.tv.get_buffer() - - # Detect XHTML-IM link - ttt = buffer_.get_tag_table() - tags_ = [(ttt.lookup(t) if isinstance(t, str) else t) for t in other_tags] - for t in tags_: - is_xhtml_link = getattr(t, 'href', None) - if is_xhtml_link: - break - # Check if we accept this as an uri - schemes = app.config.get('uri_schemes').split() - for scheme in schemes: - if special_text.startswith(scheme): - text_is_valid_uri = True - if special_text.startswith('www.') or special_text.startswith('ftp.') \ - or text_is_valid_uri and not is_xhtml_link: - if len(special_text) < self.plugin.config['IN_MAX_CHARS']: - return - end_iter = buffer_.get_end_iter() - mark = buffer_.create_mark(None, end_iter, True) - app.thread_interface(self.insert_hyperlink, [mark, special_text, - ttt]) - self.textview.plugin_modified = True - - def insert_hyperlink(self, mark, special_text, ttt): - try: - params = urllib.urlencode({'longUrl': special_text, - 'login': USER, - 'apiKey': APIKEY, - 'format': 'json'}) - req = urllib2.Request('http://api.bit.ly/v3/shorten?%s' % params) - response = urllib2.urlopen(req) - j = json.load(response) - if j['status_code'] == 200: - special_text = j['data']['url'] - except urllib2.HTTPError, e: - pass - - buffer_ = mark.get_buffer() - end_iter = buffer_.get_iter_at_mark(mark) - buffer_.insert_with_tags(end_iter, special_text, ttt.lookup('url')) - - def on_textview_motion_notify_event(self, widget, event): - pointer_x, pointer_y = self.textview.tv.window.get_pointer()[0:2] - x, y = self.textview.tv.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, - pointer_x, pointer_y) - tags = self.textview.tv.get_iter_at_location(x, y).get_tags() - tag_table = self.textview.tv.get_buffer().get_tag_table() - buffer_ = self.textview.tv.get_buffer() - for tag in tags: - if tag != tag_table.lookup('url'): - continue - it = self.textview.tv.get_iter_at_location(x, y) - st = it.copy() - st.backward_to_tag_toggle(tag_table.lookup('url')) - it.forward_to_tag_toggle(tag_table.lookup('url')) - text = buffer_.get_text(st, it, include_hidden_chars=True) - if text.startswith('http://bit.ly/'): - try: - params = urllib.urlencode({'shortUrl': text, - 'login': USER, - 'apiKey': APIKEY, - 'format': 'json'}) - req = urllib2.Request('http://api.bit.ly/v3/expand?%s' \ - % params) - response = urllib2.urlopen(req) - j = json.load(response) - if j['status_code'] != 200: - raise Exception('%s'%j['status_txt']) - txt = j['data']['expand'][0]['long_url'] - self.textview.tv.set_tooltip_text(txt) - self.textview.on_textview_motion_notify_event(widget, event) - return - except Exception, e: - break - - self.textview.tv.set_tooltip_text('') - self.textview.on_textview_motion_notify_event(widget, event) - - def disconnect_from_chat_control(self): - pass - - -class UrlShortenerPluginConfigDialog(GajimPluginConfigDialog): - def init(self): - self.GTK_BUILDER_FILE_PATH = self.plugin.local_file_path( - 'config_dialog.ui') - self.xml = gtk.Builder() - self.xml.set_translation_domain('gajim_plugins') - self.xml.add_objects_from_file(self.GTK_BUILDER_FILE_PATH, ['vbox1']) - self.max_chars_spinbutton = self.xml.get_object('max_chars') - self.max_chars_spinbutton.get_adjustment().set_all(30, 30, 99999, 1, - 10, 0) - self.in_max_chars_spinbutton = self.xml.get_object('in_max_chars') - self.in_max_chars_spinbutton.get_adjustment().set_all(30, 30, 99999, 1, - 10, 0) - self.shorten_outgoing = self.xml.get_object('shorten_outgoing') - hbox = self.xml.get_object('vbox1') - self.child.pack_start(hbox) - - self.xml.connect_signals(self) - - def on_run(self): - self.max_chars_spinbutton.set_value(self.plugin.config['MAX_CHARS']) - self.in_max_chars_spinbutton.set_value(self.plugin.config['IN_MAX_CHARS']) - self.shorten_outgoing.set_active(self.plugin.config['SHORTEN_OUTGOING']) - - def avatar_size_value_changed(self, spinbutton): - self.plugin.config['MAX_CHARS'] = spinbutton.get_value() - - def on_in_max_chars_value_changed(self, spinbutton): - self.plugin.config['IN_MAX_CHARS'] = spinbutton.get_value() - - def shorten_outgoing_toggled(self, checkbutton): - self.plugin.config['SHORTEN_OUTGOING'] = checkbutton.get_active() diff --git a/whiteboard/__init__.py b/whiteboard/__init__.py deleted file mode 100644 index 802d00c..0000000 --- a/whiteboard/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from plugin import WhiteboardPlugin diff --git a/whiteboard/brush_tool.png b/whiteboard/brush_tool.png deleted file mode 100644 index 266c321..0000000 Binary files a/whiteboard/brush_tool.png and /dev/null differ diff --git a/whiteboard/line_tool.png b/whiteboard/line_tool.png deleted file mode 100644 index 151f584..0000000 Binary files a/whiteboard/line_tool.png and /dev/null differ diff --git a/whiteboard/manifest.ini b/whiteboard/manifest.ini deleted file mode 100644 index 8a56c0f..0000000 --- a/whiteboard/manifest.ini +++ /dev/null @@ -1,8 +0,0 @@ -[info] -name: Whiteboard -short_name: whiteboard -version: 0.2.2 -description: Shows a whiteboard in chat. python-pygoocanvas is required. -authors = Yann Leboulanger -homepage = http://trac-plugins.gajim.org/wiki/WhiteboardPlugin -max_gajim_version: 0.15.9 diff --git a/whiteboard/oval_tool.png b/whiteboard/oval_tool.png deleted file mode 100644 index efd6f0c..0000000 Binary files a/whiteboard/oval_tool.png and /dev/null differ diff --git a/whiteboard/plugin.py b/whiteboard/plugin.py deleted file mode 100644 index d9e8225..0000000 --- a/whiteboard/plugin.py +++ /dev/null @@ -1,486 +0,0 @@ -## plugins/whiteboard/plugin.py -## -## Copyright (C) 2009 Jeff Ling -## Copyright (C) 2010 Yann Leboulanger -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## - -''' -Whiteboard plugin. - -:author: Yann Leboulanger -:since: 1st November 2010 -:copyright: Copyright (2010) Yann Leboulanger -:license: GPL -''' - - -from common import helpers -from common import gajim -from plugins import GajimPlugin -from plugins.plugin import GajimPluginException -from plugins.helpers import log_calls, log -import common.xmpp -import gtk -import chat_control -from common import ged -from common.jingle_session import JingleSession -from common.jingle_content import JingleContent -from common.jingle_transport import JingleTransport, TransportType -import dialogs -from whiteboard_widget import Whiteboard, HAS_GOOCANVAS -from common import xmpp -from common import caps_cache - -NS_JINGLE_XHTML = 'urn:xmpp:tmp:jingle:apps:xhtml' -NS_JINGLE_SXE = 'urn:xmpp:tmp:jingle:transports:sxe' -NS_SXE = 'urn:xmpp:sxe:0' - -class WhiteboardPlugin(GajimPlugin): - @log_calls('WhiteboardPlugin') - def init(self): - self.description = _('Shows a whiteboard in chat.' - ' python-pygoocanvas is required.') - self.config_dialog = None - self.events_handlers = { - 'jingle-request-received': (ged.GUI1, self._nec_jingle_received), - 'jingle-connected-received': (ged.GUI1, self._nec_jingle_connected), - 'jingle-disconnected-received': (ged.GUI1, - self._nec_jingle_disconnected), - 'raw-message-received': (ged.GUI1, self._nec_raw_message), - } - self.gui_extension_points = { - 'chat_control_base' : (self.connect_with_chat_control, - self.disconnect_from_chat_control), - 'chat_control_base_update_toolbar': (self.update_button_state, - None), - } - self.controls = [] - self.sid = None - - @log_calls('WhiteboardPlugin') - def _compute_caps_hash(self): - for a in gajim.connections: - gajim.caps_hash[a] = caps_cache.compute_caps_hash([ - gajim.gajim_identity], gajim.gajim_common_features + \ - gajim.gajim_optional_features[a]) - # re-send presence with new hash - connected = gajim.connections[a].connected - if connected > 1 and gajim.SHOW_LIST[connected] != 'invisible': - gajim.connections[a].change_status(gajim.SHOW_LIST[connected], - gajim.connections[a].status) - - @log_calls('WhiteboardPlugin') - def activate(self): - if not HAS_GOOCANVAS: - raise GajimPluginException('python-pygoocanvas is missing!') - if NS_JINGLE_SXE not in gajim.gajim_common_features: - gajim.gajim_common_features.append(NS_JINGLE_SXE) - if NS_SXE not in gajim.gajim_common_features: - gajim.gajim_common_features.append(NS_SXE) - self._compute_caps_hash() - - @log_calls('WhiteboardPlugin') - def deactivate(self): - if NS_JINGLE_SXE in gajim.gajim_common_features: - gajim.gajim_common_features.remove(NS_JINGLE_SXE) - if NS_SXE in gajim.gajim_common_features: - gajim.gajim_common_features.remove(NS_SXE) - self._compute_caps_hash() - - @log_calls('WhiteboardPlugin') - def connect_with_chat_control(self, control): - if isinstance(control, chat_control.ChatControl): - base = Base(self, control) - self.controls.append(base) - - @log_calls('WhiteboardPlugin') - def disconnect_from_chat_control(self, chat_control): - for base in self.controls: - base.disconnect_from_chat_control() - self.controls = [] - - @log_calls('WhiteboardPlugin') - def update_button_state(self, control): - for base in self.controls: - if base.chat_control == control: - if control.contact.supports(NS_JINGLE_SXE) and \ - control.contact.supports(NS_SXE): - base.button.set_sensitive(True) - tooltip_text = _('Show whiteboard') - else: - base.button.set_sensitive(False) - tooltip_text = _('Client on the other side ' - 'does not support the whiteboard') - base.button.set_tooltip_text(tooltip_text) - - @log_calls('WhiteboardPlugin') - def show_request_dialog(self, account, fjid, jid, sid, content_types): - def on_ok(): - session = gajim.connections[account].get_jingle_session(fjid, sid) - self.sid = session.sid - if not session.accepted: - session.approve_session() - for content in content_types: - session.approve_content(content) - for _jid in (fjid, jid): - ctrl = gajim.interface.msg_win_mgr.get_control(_jid, account) - if ctrl: - break - if not ctrl: - # create it - gajim.interface.new_chat_from_jid(account, jid) - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - session = session.contents[('initiator', 'xhtml')] - ctrl.draw_whiteboard(session) - - def on_cancel(): - session = gajim.connections[account].get_jingle_session(fjid, sid) - session.decline_session() - - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if contact: - name = contact.get_shown_name() - else: - name = jid - pritext = _('Incoming Whiteboard') - sectext = _('%(name)s (%(jid)s) wants to start a whiteboard with ' - 'you. Do you want to accept?') % {'name': name, 'jid': jid} - dialog = dialogs.NonModalConfirmationDialog(pritext, sectext=sectext, - on_response_ok=on_ok, on_response_cancel=on_cancel) - dialog.popup() - - @log_calls('WhiteboardPlugin') - def _nec_jingle_received(self, obj): - if not HAS_GOOCANVAS: - return - content_types = set(c[0] for c in obj.contents) - if 'xhtml' not in content_types: - return - self.show_request_dialog(obj.conn.name, obj.fjid, obj.jid, obj.sid, - content_types) - - @log_calls('WhiteboardPlugin') - def _nec_jingle_connected(self, obj): - if not HAS_GOOCANVAS: - return - account = obj.conn.name - ctrl = (gajim.interface.msg_win_mgr.get_control(obj.fjid, account) - or gajim.interface.msg_win_mgr.get_control(obj.jid, account)) - if not ctrl: - return - session = gajim.connections[obj.conn.name].get_jingle_session(obj.fjid, - obj.sid) - - if ('initiator', 'xhtml') not in session.contents: - return - - session = session.contents[('initiator', 'xhtml')] - ctrl.draw_whiteboard(session) - - @log_calls('WhiteboardPlugin') - def _nec_jingle_disconnected(self, obj): - for base in self.controls: - if base.sid == obj.sid: - base.stop_whiteboard(reason = obj.reason) - - @log_calls('WhiteboardPlugin') - def _nec_raw_message(self, obj): - if not HAS_GOOCANVAS: - return - if obj.stanza.getTag('sxe', namespace=NS_SXE): - account = obj.conn.name - - try: - fjid = helpers.get_full_jid_from_iq(obj.stanza) - except helpers.InvalidFormat: - obj.conn.dispatch('ERROR', (_('Invalid Jabber ID'), - _('A message from a non-valid JID arrived, it has been ' - 'ignored.'))) - - jid = gajim.get_jid_without_resource(fjid) - ctrl = (gajim.interface.msg_win_mgr.get_control(fjid, account) - or gajim.interface.msg_win_mgr.get_control(jid, account)) - if not ctrl: - return - sxe = obj.stanza.getTag('sxe') - if not sxe: - return - sid = sxe.getAttr('session') - if (jid, sid) not in obj.conn._sessions: - pass -# newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid) -# self.addJingle(newjingle) - - # we already have such session in dispatcher... - session = obj.conn.get_jingle_session(fjid, sid) - cn = session.contents[('initiator', 'xhtml')] - error = obj.stanza.getTag('error') - if error: - action = 'iq-error' - else: - action = 'edit' - - cn.on_stanza(obj.stanza, sxe, error, action) -# def __editCB(self, stanza, content, error, action): - #new_tags = sxe.getTags('new') - #remove_tags = sxe.getTags('remove') - - #if new_tags is not None: - ## Process new elements - #for tag in new_tags: - #if tag.getAttr('type') == 'element': - #ctrl.whiteboard.recieve_element(tag) - #elif tag.getAttr('type') == 'attr': - #ctrl.whiteboard.recieve_attr(tag) - #ctrl.whiteboard.apply_new() - - #if remove_tags is not None: - ## Delete rids - #for tag in remove_tags: - #target = tag.getAttr('target') - #ctrl.whiteboard.image.del_rid(target) - - # Stop propagating this event, it's handled - return True - - -class Base(object): - def __init__(self, plugin, chat_control): - self.plugin = plugin - self.chat_control = chat_control - self.chat_control.draw_whiteboard = self.draw_whiteboard - self.contact = self.chat_control.contact - self.account = self.chat_control.account - self.jid = self.contact.get_full_jid() - self.create_buttons() - self.whiteboard = None - self.sid = None - - def create_buttons(self): - # create whiteboard button - actions_hbox = self.chat_control.xml.get_object('actions_hbox') - self.button = gtk.ToggleButton(label=None, use_underline=True) - self.button.set_property('relief', gtk.RELIEF_NONE) - self.button.set_property('can-focus', False) - img = gtk.Image() - img_path = self.plugin.local_file_path('whiteboard.png') - pixbuf = gtk.gdk.pixbuf_new_from_file(img_path) - iconset = gtk.IconSet(pixbuf=pixbuf) - factory = gtk.IconFactory() - factory.add('whiteboard', iconset) - img_path = self.plugin.local_file_path('brush_tool.png') - pixbuf = gtk.gdk.pixbuf_new_from_file(img_path) - iconset = gtk.IconSet(pixbuf=pixbuf) - factory.add('brush_tool', iconset) - img_path = self.plugin.local_file_path('line_tool.png') - pixbuf = gtk.gdk.pixbuf_new_from_file(img_path) - iconset = gtk.IconSet(pixbuf=pixbuf) - factory.add('line_tool', iconset) - img_path = self.plugin.local_file_path('oval_tool.png') - pixbuf = gtk.gdk.pixbuf_new_from_file(img_path) - iconset = gtk.IconSet(pixbuf=pixbuf) - factory.add('oval_tool', iconset) - factory.add_default() - img.set_from_stock('whiteboard', gtk.ICON_SIZE_MENU) - self.button.set_image(img) - send_button = self.chat_control.xml.get_object('send_button') - send_button_pos = actions_hbox.child_get_property(send_button, - 'position') - actions_hbox.add_with_properties(self.button, 'position', - send_button_pos - 1, 'expand', False) - id_ = self.button.connect('toggled', self.on_whiteboard_button_toggled) - self.chat_control.handlers[id_] = self.button - self.button.show() - - def draw_whiteboard(self, content): - hbox = self.chat_control.xml.get_object('chat_control_hbox') - if len(hbox.get_children()) == 1: - self.whiteboard = Whiteboard(self.account, self.contact, content, - self.plugin) - # set minimum size - self.whiteboard.hbox.set_size_request(300, 0) - hbox.pack_start(self.whiteboard.hbox, expand=False, fill=False) - self.whiteboard.hbox.show_all() - self.button.set_active(True) - content.control = self - self.sid = content.session.sid - - def on_whiteboard_button_toggled(self, widget): - """ - Popup whiteboard - """ - if widget.get_active(): - if not self.whiteboard: - self.start_whiteboard() - else: - self.stop_whiteboard() - - def start_whiteboard(self): - conn = gajim.connections[self.chat_control.account] - jingle = JingleSession(conn, weinitiate=True, jid=self.jid) - self.sid = jingle.sid - conn._sessions[jingle.sid] = jingle - content = JingleWhiteboard(jingle) - content.control = self - jingle.add_content('xhtml', content) - jingle.start_session() - - def stop_whiteboard(self, reason=None): - conn = gajim.connections[self.chat_control.account] - self.sid = None - session = conn.get_jingle_session(self.jid, media='xhtml') - if session: - session.end_session() - self.button.set_active(False) - if reason: - txt = _('Whiteboard stopped: %(reason)s') % {'reason': reason} - self.chat_control.print_conversation(txt, 'info') - if not self.whiteboard: - return - hbox = self.chat_control.xml.get_object('chat_control_hbox') - if self.whiteboard.hbox in hbox.get_children(): - if hasattr(self.whiteboard, 'hbox'): - hbox.remove(self.whiteboard.hbox) - self.whiteboard = None - - def disconnect_from_chat_control(self): - actions_hbox = self.chat_control.xml.get_object('actions_hbox') - actions_hbox.remove(self.button) - -class JingleWhiteboard(JingleContent): - ''' Jingle Whiteboard sessions consist of xhtml content''' - def __init__(self, session, transport=None): - if not transport: - transport = JingleTransportSXE() - JingleContent.__init__(self, session, transport) - self.media = 'xhtml' - self.negotiated = True # there is nothing to negotiate - self.last_rid = 0 - self.callbacks['session-accept'] += [self._sessionAcceptCB] - self.callbacks['session-terminate'] += [self._stop] - self.callbacks['session-terminate-sent'] += [self._stop] - self.callbacks['edit'] = [self._EditCB] - - def _EditCB(self, stanza, content, error, action): - new_tags = content.getTags('new') - remove_tags = content.getTags('remove') - - if new_tags is not None: - # Process new elements - for tag in new_tags: - if tag.getAttr('type') == 'element': - self.control.whiteboard.recieve_element(tag) - elif tag.getAttr('type') == 'attr': - self.control.whiteboard.recieve_attr(tag) - self.control.whiteboard.apply_new() - - if remove_tags is not None: - # Delete rids - for tag in remove_tags: - target = tag.getAttr('target') - self.control.whiteboard.image.del_rid(target) - - def _sessionAcceptCB(self, stanza, content, error, action): - log.debug('session accepted') - self.session.connection.dispatch('WHITEBOARD_ACCEPTED', - (self.session.peerjid, self.session.sid)) - - def generate_rids(self, x): - # generates x number of rids and returns in list - rids = [] - for x in range(x): - rids.append(str(self.last_rid)) - self.last_rid += 1 - return rids - - def send_whiteboard_node(self, items, rids): - # takes int rid and dict items and sends it as a node - # sends new item - jid = self.session.peerjid - sid = self.session.sid - message = xmpp.Message(to=jid) - sxe = message.addChild(name='sxe', attrs={'session': sid}, - namespace=NS_SXE) - - for x in rids: - if items[x]['type'] == 'element': - parent = x - attrs = {'rid': x, - 'name': items[x]['data'][0].getName(), - 'type': items[x]['type']} - sxe.addChild(name='new', attrs=attrs) - if items[x]['type'] == 'attr': - attr_name = items[x]['data'] - chdata = items[parent]['data'][0].getAttr(attr_name) - attrs = {'rid': x, - 'name': attr_name, - 'type': items[x]['type'], - 'chdata': chdata, - 'parent': parent} - sxe.addChild(name='new', attrs=attrs) - self.session.connection.connection.send(message) - - def delete_whiteboard_node(self, rids): - message = xmpp.Message(to=self.session.peerjid) - sxe = message.addChild(name='sxe', attrs={'session': self.session.sid}, - namespace=NS_SXE) - - for x in rids: - sxe.addChild(name='remove', attrs = {'target': x}) - self.session.connection.connection.send(message) - - def send_items(self, items, rids): - # recieves dict items and a list of rids of items to send - # TODO: is there a less clumsy way that doesn't involve passing - # whole list - self.send_whiteboard_node(items, rids) - - def del_item(self, rids): - self.delete_whiteboard_node(rids) - - def encode(self, xml): - # encodes it sendable string - return 'data:text/xml,' + urllib.quote(xml) - - def _fill_content(self, content): - content.addChild(NS_JINGLE_XHTML + ' description') - - def _stop(self, *things): - pass - - def __del__(self): - pass - -def get_content(desc): - return JingleWhiteboard - -common.jingle_content.contents[NS_JINGLE_XHTML] = get_content - -class JingleTransportSXE(JingleTransport): - def __init__(self, node=None): - if gajim.config.get('version') == '0.15': - JingleTransport.__init__(self, TransportType.streaming) - else: - JingleTransport.__init__(self, TransportType.SOCKS5) - - def make_transport(self, candidates=None): - transport = JingleTransport.make_transport(self, candidates) - transport.setNamespace(NS_JINGLE_SXE) - transport.setTagData('host', 'TODO') - return transport - -common.jingle_transport.transports[NS_JINGLE_SXE] = JingleTransportSXE diff --git a/whiteboard/whiteboard.png b/whiteboard/whiteboard.png deleted file mode 100644 index 13318e3..0000000 Binary files a/whiteboard/whiteboard.png and /dev/null differ diff --git a/whiteboard/whiteboard_widget.py b/whiteboard/whiteboard_widget.py deleted file mode 100644 index 2a9562c..0000000 --- a/whiteboard/whiteboard_widget.py +++ /dev/null @@ -1,418 +0,0 @@ -## plugins/whiteboard/whiteboard_widget.py -## -## Copyright (C) 2009 Jeff Ling -## Copyright (C) 2010 Yann Leboulanger -## -## This file is part of Gajim. -## -## Gajim is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## Gajim is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with Gajim. If not, see . -## - -import gtk -import gtkgui_helpers -try: - import goocanvas - HAS_GOOCANVAS = True -except: - HAS_GOOCANVAS = False -from common.xmpp import Node -from common import gajim -from dialogs import FileChooserDialog - -''' -A whiteboard widget made for Gajim. -- Ummu -''' - -class Whiteboard(object): - def __init__(self, account, contact, session, plugin): - self.plugin = plugin - file_path = plugin.local_file_path('whiteboard_widget.ui') - xml = gtk.Builder() - xml.set_translation_domain('gajim_plugins') - xml.add_from_file(file_path) - self.hbox = xml.get_object('whiteboard_hbox') - self.canevas = goocanvas.Canvas() - self.hbox.pack_start(self.canevas) - self.hbox.reorder_child(self.canevas, 0) - self.canevas.set_flags(gtk.CAN_FOCUS) - self.fg_color_select_button = xml.get_object('fg_color_button') - self.root = self.canevas.get_root_item() - self.tool_buttons = [] - for tool in ('brush', 'oval', 'line', 'delete'): - self.tool_buttons.append(xml.get_object(tool + '_button')) - xml.get_object('brush_button').set_active(True) - - # Events - self.canevas.connect('button-press-event', self.button_press_event) - self.canevas.connect('button-release-event', self.button_release_event) - self.canevas.connect('motion-notify-event', self.motion_notify_event) - self.canevas.connect('item-created', self.item_created) - - # Config - self.line_width = 2 - xml.get_object('size_scale').set_value(2) - self.color = str(self.fg_color_select_button.get_color()) - - # SVG Storage - self.image = SVGObject(self.root, session) - - xml.connect_signals(self) - - # Temporary Variables for items - self.item_temp = None - self.item_temp_coords = (0, 0) - self.item_data = None - - # Will be {ID: {type:'element', data:[node, goocanvas]}, ID2: {}} instance - self.recieving = {} - - def on_tool_button_toggled(self, widget): - for btn in self.tool_buttons: - if btn == widget: - continue - btn.set_active(False) - - def on_brush_button_toggled(self, widget): - if widget.get_active(): - self.image.draw_tool = 'brush' - self.on_tool_button_toggled(widget) - - def on_oval_button_toggled(self, widget): - if widget.get_active(): - self.image.draw_tool = 'oval' - self.on_tool_button_toggled(widget) - - def on_line_button_toggled(self, widget): - if widget.get_active(): - self.image.draw_tool = 'line' - self.on_tool_button_toggled(widget) - - def on_delete_button_toggled(self, widget): - if widget.get_active(): - self.image.draw_tool = 'delete' - self.on_tool_button_toggled(widget) - - def on_clear_button_clicked(self, widget): - self.image.clear_canvas() - - def on_export_button_clicked(self, widget): - SvgChooserDialog(self.image.export_svg) - - def on_fg_color_button_color_set(self, widget): - self.color = str(self.fg_color_select_button.get_color()) - - def item_created(self, canvas, item, model): - print 'item created' - item.connect('button-press-event', self.item_button_press_events) - - def item_button_press_events(self, item, target_item, event): - if self.image.draw_tool == 'delete': - self.image.del_item(item) - - def on_size_scale_format_value(self, widget): - self.line_width = int(widget.get_value()) - - def button_press_event(self, widget, event): - x = event.x - y = event.y - state = event.state - self.item_temp_coords = (x, y) - - if self.image.draw_tool == 'brush': - self.item_temp = goocanvas.Ellipse(parent=self.root, - center_x=x, - center_y=y, - radius_x=1, - radius_y=1, - stroke_color=self.color, - fill_color=self.color, - line_width=self.line_width) - self.item_data = 'M %s,%s L ' % (x, y) - - elif self.image.draw_tool == 'oval': - self.item_data = True - - if self.image.draw_tool == 'line': - self.item_data = 'M %s,%s L' % (x, y) - - def motion_notify_event(self, widget, event): - x = event.x - y = event.y - state = event.state - if self.item_temp is not None: - self.item_temp.remove() - - if self.item_data is not None: - if self.image.draw_tool == 'brush': - self.item_data = self.item_data + '%s,%s ' % (x, y) - self.item_temp = goocanvas.Path(parent=self.root, - data=self.item_data, line_width=self.line_width, - stroke_color=self.color) - elif self.image.draw_tool == 'oval': - self.item_temp = goocanvas.Ellipse(parent=self.root, - center_x=self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2, - center_y=self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2, - radius_x=abs(x - self.item_temp_coords[0]) / 2, - radius_y=abs(y - self.item_temp_coords[1]) / 2, - stroke_color=self.color, - line_width=self.line_width) - elif self.image.draw_tool == 'line': - self.item_data = 'M %s,%s L' % self.item_temp_coords - self.item_data = self.item_data + ' %s,%s' % (x, y) - self.item_temp = goocanvas.Path(parent=self.root, - data=self.item_data, line_width=self.line_width, - stroke_color=self.color) - - def button_release_event(self, widget, event): - x = event.x - y = event.y - state = event.state - - if self.image.draw_tool == 'brush': - self.item_data = self.item_data + '%s,%s' % (x, y) - if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]: - goocanvas.Ellipse(parent=self.root, - center_x=x, - center_y=y, - radius_x=1, - radius_y=1, - stroke_color=self.color, - fill_color=self.color, - line_width=self.line_width) - self.image.add_path(self.item_data, self.line_width, self.color) - - if self.image.draw_tool == 'oval': - cx = self.item_temp_coords[0] + (x - self.item_temp_coords[0]) / 2 - cy = self.item_temp_coords[1] + (y - self.item_temp_coords[1]) / 2 - rx = abs(x - self.item_temp_coords[0]) / 2 - ry = abs(y - self.item_temp_coords[1]) / 2 - self.image.add_ellipse(cx, cy, rx, ry, self.line_width, self.color) - - if self.image.draw_tool == 'line': - self.item_data = 'M %s,%s L' % self.item_temp_coords - self.item_data = self.item_data + ' %s,%s' % (x, y) - if x == self.item_temp_coords[0] and y == self.item_temp_coords[1]: - goocanvas.Ellipse(parent=self.root, - center_x=x, - center_y=y, - radius_x=1, - radius_y=1, - stroke_color='black', - fill_color='black', - line_width=self.line_width) - self.image.add_path(self.item_data, self.line_width, self.color) - - if self.image.draw_tool == 'delete': - pass - - self.item_data = None - if self.item_temp is not None: - self.item_temp.remove() - self.item_temp = None - - def recieve_element(self, element): - node = self.image.g.addChild(name=element.getAttr('name')) - self.image.g.addChild(node=node) - self.recieving[element.getAttr('rid')] = {'type':'element', - 'data':[node], - 'children':[]} - - def recieve_attr(self, element): - node = self.recieving[element.getAttr('parent')]['data'][0] - node.setAttr(element.getAttr('name'), element.getAttr('chdata')) - - self.recieving[element.getAttr('rid')] = {'type':'attr', - 'data':element.getAttr('name'), - 'parent':node} - self.recieving[element.getAttr('parent')]['children'].append(element.getAttr('rid')) - - def apply_new(self): - for x in self.recieving.keys(): - if self.recieving[x]['type'] == 'element': - self.image.add_recieved(x, self.recieving) - - self.recieving = {} - -class SvgChooserDialog(FileChooserDialog): - def __init__(self, on_response_ok=None, on_response_cancel=None): - ''' - Choose in which SVG file to store the image - ''' - def on_ok(widget, callback): - ''' - check if file exists and call callback - ''' - path_to_file = self.get_filename() - path_to_file = gtkgui_helpers.decode_filechooser_file_paths( - (path_to_file,))[0] - widget.destroy() - callback(path_to_file) - - FileChooserDialog.__init__(self, - title_text=_('Save Image as...'), - action=gtk.FILE_CHOOSER_ACTION_SAVE, - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_SAVE, - gtk.RESPONSE_OK), - current_folder='', - default_response=gtk.RESPONSE_OK, - on_response_ok=(on_ok, on_response_ok), - on_response_cancel=on_response_cancel) - - filter_ = gtk.FileFilter() - filter_.set_name(_('All files')) - filter_.add_pattern('*') - self.add_filter(filter_) - - filter_ = gtk.FileFilter() - filter_.set_name(_('SVG Files')) - filter_.add_pattern('*.svg') - self.add_filter(filter_) - self.set_filter(filter_) - - -class SVGObject(): - ''' A class to store the svg document and make changes to it.''' - - def __init__(self, root, session, height=300, width=300): - # Will be {ID: {type:'element', data:[node, goocanvas]}, ID2: {}} instance - self.items = {} - self.root = root - self.draw_tool = 'brush' - - # sxe session - self.session = session - - # initialize svg document - self.svg = Node(node='') - self.svg.setAttr('version', '1.1') - self.svg.setAttr('height', str(height)) - self.svg.setAttr('width', str(width)) - self.svg.setAttr('xmlns', 'http://www.w3.org/2000/svg') - # TODO: make this settable - self.g = self.svg.addChild(name='g') - self.g.setAttr('fill', 'none') - self.g.setAttr('stroke-linecap', 'round') - - def add_path(self, data, line_width, color): - ''' adds the path to the items listing, both minidom node and goocanvas - object in a tuple ''' - - goocanvas_obj = goocanvas.Path(parent=self.root, data=data, - line_width=line_width, stroke_color=color) - goocanvas_obj.connect('button-press-event', self.item_button_press_events) - - node = self.g.addChild(name='path') - node.setAttr('d', data) - node.setAttr('stroke-width', str(line_width)) - node.setAttr('stroke', color) - self.g.addChild(node=node) - - rids = self.session.generate_rids(4) - self.items[rids[0]] = {'type':'element', 'data':[node, goocanvas_obj], 'children':rids[1:]} - self.items[rids[1]] = {'type':'attr', 'data':'d', 'parent':node} - self.items[rids[2]] = {'type':'attr', 'data':'stroke-width', 'parent':node} - self.items[rids[3]] = {'type':'attr', 'data':'stroke', 'parent':node} - - self.session.send_items(self.items, rids) - - def add_recieved(self, parent_rid, new_items): - ''' adds the path to the items listing, both minidom node and goocanvas - object in a tuple ''' - node = new_items[parent_rid]['data'][0] - - self.items[parent_rid] = new_items[parent_rid] - for x in new_items[parent_rid]['children']: - self.items[x] = new_items[x] - - if node.getName() == 'path': - goocanvas_obj = goocanvas.Path(parent=self.root, - data=node.getAttr('d'), - line_width=int(node.getAttr('stroke-width')), - stroke_color=node.getAttr('stroke')) - - if node.getName() == 'ellipse': - goocanvas_obj = goocanvas.Ellipse(parent=self.root, - center_x=float(node.getAttr('cx')), - center_y=float(node.getAttr('cy')), - radius_x=float(node.getAttr('rx')), - radius_y=float(node.getAttr('ry')), - stroke_color=node.getAttr('stroke'), - line_width=float(node.getAttr('stroke-width'))) - - self.items[parent_rid]['data'].append(goocanvas_obj) - goocanvas_obj.connect('button-press-event', self.item_button_press_events) - - def add_ellipse(self, cx, cy, rx, ry, line_width, stroke_color): - ''' adds the ellipse to the items listing, both minidom node and goocanvas - object in a tuple ''' - - goocanvas_obj = goocanvas.Ellipse(parent=self.root, - center_x=cx, - center_y=cy, - radius_x=rx, - radius_y=ry, - stroke_color=stroke_color, - line_width=line_width) - goocanvas_obj.connect('button-press-event', self.item_button_press_events) - - node = self.g.addChild(name='ellipse') - node.setAttr('cx', str(cx)) - node.setAttr('cy', str(cy)) - node.setAttr('rx', str(rx)) - node.setAttr('ry', str(ry)) - node.setAttr('stroke-width', str(line_width)) - node.setAttr('stroke', stroke_color) - self.g.addChild(node=node) - - rids = self.session.generate_rids(7) - self.items[rids[0]] = {'type':'element', 'data':[node, goocanvas_obj], 'children':rids[1:]} - self.items[rids[1]] = {'type':'attr', 'data':'cx', 'parent':node} - self.items[rids[2]] = {'type':'attr', 'data':'cy', 'parent':node} - self.items[rids[3]] = {'type':'attr', 'data':'rx', 'parent':node} - self.items[rids[4]] = {'type':'attr', 'data':'ry', 'parent':node} - self.items[rids[5]] = {'type':'attr', 'data':'stroke-width', 'parent':node} - self.items[rids[6]] = {'type':'attr', 'data':'stroke', 'parent':node} - - self.session.send_items(self.items, rids) - - def del_item(self, item): - rids = [] - for x in self.items.keys(): - if self.items[x]['type'] == 'element': - if self.items[x]['data'][1] == item: - for y in self.items[x]['children']: - rids.append(y) - self.del_rid(y) - rids.append(x) - self.del_rid(x) - break - self.session.del_item(rids) - - def clear_canvas(self): - for x in self.items.keys(): - if self.items[x]['type'] == 'element': - self.del_rid(x) - - def del_rid(self, rid): - if self.items[rid]['type'] == 'element': - self.items[rid]['data'][1].remove() - del self.items[rid] - - def export_svg(self, filename): - f = open(filename, 'w') - f.writelines(str(self.svg)) - f.close() - - def item_button_press_events(self, item, target_item, event): - self.del_item(item) diff --git a/whiteboard/whiteboard_widget.ui b/whiteboard/whiteboard_widget.ui deleted file mode 100644 index fddd2cb..0000000 --- a/whiteboard/whiteboard_widget.ui +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - True - 3 - 6 - - - - - - True - 6 - vertical - 6 - - - True - True - True - Brush Tool: Draw freehand lines - - - - True - brush_tool - - - - - False - False - 0 - - - - - True - True - True - Oval Tool: Draw circles and ellipses - - - - True - oval_tool - - - - - False - False - 1 - - - - - True - True - True - Line Tool: Draw straight lines - - - - True - line_tool - - - - - False - False - 2 - - - - - True - True - True - Delete Tool: Remove individual figures - - - - True - gtk-delete - - - - - False - False - 3 - - - - - True - True - True - Clear Canvas: Cleanup canvas - - - - True - gtk-clear - - - - - False - False - 4 - - - - - True - True - True - Export Image: Save image to svg file - - - - True - gtk-save-as - - - - - False - False - 5 - - - - - 68 - True - True - Line width - vertical - adjustment1 - True - 0 - bottom - - - - False - False - 6 - - - - - True - True - True - Foreground color - #000000000000 - - - - False - False - 7 - - - - - False - False - 1 - - - - - True - gtk-delete - - - 2 - 1 - 110 - 1 - 10 - 10 - -