###
### This file is part of Trivial IP Encryption (TrIPE).
###
-### TrIPE 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; either version 2 of the License, or
-### (at your option) any later version.
+### TrIPE 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; either version 3 of the License, or (at your
+### option) any later version.
###
-### TrIPE 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.
+### TrIPE 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 TrIPE; if not, write to the Free Software Foundation,
-### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+### along with TrIPE. If not, see <https://www.gnu.org/licenses/>.
###--------------------------------------------------------------------------
### Dependencies.
import re as RX
from cStringIO import StringIO
-import pygtk
-pygtk.require('2.0')
-import gtk as G
-import gobject as GO
-import gtk.gdk as GDK
+try:
+ if OS.getenv('TRIPEMON_FORCE_GI'): raise ImportError()
+ import pygtk
+ pygtk.require('2.0')
+ import gtk as G
+ import gobject as GO
+ import gtk.gdk as GDK
+ GL = GO
+ GDK.KEY_Escape = G.keysyms.Escape
+ def raise_window(w): w.window.raise_()
+ combo_box_text = G.combo_box_new_text
+ def set_entry_bg(e, c): e.modify_base(G.STATE_NORMAL, c)
+except ImportError:
+ from gi.repository import GObject as GO, GLib as GL, Gtk as G, Gdk as GDK
+ G.WINDOW_TOPLEVEL = G.WindowType.TOPLEVEL
+ G.EXPAND = G.AttachOptions.EXPAND
+ G.SHRINK = G.AttachOptions.SHRINK
+ G.FILL = G.AttachOptions.FILL
+ G.SORT_ASCENDING = G.SortType.ASCENDING
+ G.POLICY_AUTOMATIC = G.PolicyType.AUTOMATIC
+ G.SHADOW_IN = G.ShadowType.IN
+ G.SELECTION_NONE = G.SelectionMode.NONE
+ G.DIALOG_MODAL = G.DialogFlags.MODAL
+ G.RESPONSE_CANCEL = G.ResponseType.CANCEL
+ G.RESPONSE_NONE = G.ResponseType.NONE
+ def raise_window(w): getattr(w.get_window(), 'raise')()
+ combo_box_text = G.ComboBoxText
+ def set_entry_bg(e, c): e.modify_bg(G.StateType.NORMAL, c)
if OS.getenv('TRIPE_DEBUG_MONITOR') is not None:
T._debug = 1
"""
Monitor I/O events using glib.
"""
- def __init__(me, conn, mc = GO.main_context_default()):
+ def __init__(me, conn, mc = GL.main_context_default()):
me._conn = conn
me._watch = None
me._mc = mc
def connected(me, sock):
- me._watch = GO.io_add_watch(sock, GO.IO_IN,
+ me._watch = GL.io_add_watch(sock, GL.IO_IN,
lambda *hunoz: me._conn.receive())
def disconnected(me):
- GO.source_remove(me._watch)
+ GL.source_remove(me._watch)
me._watch = None
def iterate(me):
me._mc.iteration(True)
"""Initialize the object with the given name."""
MonitorObject.__init__(me, name)
me.pinghook = HookList()
+ me.__dict__.update(conn.algs(name))
me.update()
def update(me, hunoz = None):
def _setaddr(me, addr):
"""Set the peer's address."""
- if addr[0] == 'INET':
- ipaddr, port = addr[1:]
+ if addr[0] in ['INET', 'INET6']:
+ af, ipaddr, port = addr
try:
- name = S.gethostbyaddr(ipaddr)[0]
- me.addr = 'INET %s:%s [%s]' % (name, port, ipaddr)
- except S.herror:
- me.addr = 'INET %s:%s' % (ipaddr, port)
+ name, _ = S.getnameinfo((ipaddr, int(port)),
+ S.NI_NUMERICSERV | S.NI_NAMEREQD)
+ except S.gaierror:
+ me.addr = '%s %s%s%s:%s' % (af,
+ af == 'INET6' and '[' or '',
+ ipaddr,
+ af == 'INET6' and ']' or '',
+ port)
+ else:
+ me.addr = '%s %s:%s [%s]' % (af, name, port, ipaddr)
else:
me.addr = ' '.join(addr)
G.Window.__init__(me, kind)
me.mywininit()
+class TrivialWindowMixin (MyWindowMixin):
+ """A simple window which you can close with Escape."""
+ def mywininit(me):
+ super(TrivialWindowMixin, me).mywininit()
+ me.connect('key-press-event', me._keypress)
+ def _keypress(me, _, ev):
+ if ev.keyval == GDK.KEY_Escape: me.destroy()
+
+class TrivialWindow (MyWindow, TrivialWindowMixin):
+ pass
+
class MyDialog (G.Dialog, MyWindowMixin, HookClient):
"""A dialogue box with a closehook and sensible button binding."""
def open(me):
"""Opens the window, creating it if necessary."""
if me.window:
- me.window.window.raise_()
+ raise_window(me.window)
else:
me.window = me.createfunc()
me.hook(me.window.closehook, me.closed)
"""Check the current text and update validp and the text colour."""
if me.validate(G.Entry.get_text(me)):
me.validp = True
- me.modify_base(G.STATE_NORMAL, None)
+ set_entry_bg(me, None)
else:
me.validp = False
- me.modify_base(G.STATE_NORMAL, me.is_sensitive() and c_red or None)
+ set_entry_bg(me, me.is_sensitive() and c_red or None)
def get_text(me):
"""
ValidationError.
"""
if not me.validp:
- raise ValidationError
+ raise ValidationError()
return G.Entry.get_text(me)
def numericvalidate(min = None, max = None):
###--------------------------------------------------------------------------
### Various minor dialog boxen.
-GPL = """This program 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; either version 2 of the License, or
-(at your option) any later version.
+GPL = """\
+TrIPE 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; either version 3 of the License, or (at your
+option) any later version.
-This program 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.
+TrIPE 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 this program; if not, write to the Free Software Foundation,
-Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."""
+along with TrIPE. If not, see <https://www.gnu.org/licenses/>."""
-class AboutBox (G.AboutDialog, MyWindowMixin):
+class AboutBox (G.AboutDialog, TrivialWindowMixin):
"""The program `About' box."""
def __init__(me):
G.AboutDialog.__init__(me)
buttons = ((G.STOCK_OK, G.RESPONSE_NONE)))
label = G.Label(msg)
label.set_padding(20, 20)
- d.vbox.pack_start(label)
+ d.vbox.pack_start(label, True, True, 0)
label.show()
d.run()
d.destroy()
"""Call with a new warning message."""
me.add(tag, ' '.join([T.quotify(w) for w in rest]))
-class LogViewer (MyWindow):
+class LogViewer (TrivialWindow):
"""
A log viewer window.
"""
Create a log viewer showing the LogModel MODEL.
"""
- MyWindow.__init__(me)
+ TrivialWindow.__init__(me)
me.model = model
scr = MyScrolledWindow()
me.list = MyTreeView(me.model)
def _connected(me):
"""Respond to connection: start pinging thngs."""
- me._timer = GO.timeout_add(1000, me._timerfunc)
+ me._timer = GL.timeout_add(1000, me._timerfunc)
def _timerfunc(me):
"""Timer function: put a timer event on the queue."""
def _disconnected(me, reason):
"""Respond to disconnection: stop pinging."""
- GO.source_remove(me._timer)
+ GL.source_remove(me._timer)
def run(me):
"""
* e_name, e_addr, e_port, c_keepalive, l_tunnel: widgets in the dialog
"""
+ AFS = ['ANY', 'INET', 'INET6']
+
def __init__(me):
"""Initialize the dialogue."""
MyDialog.__init__(me, 'Add peer',
def _setup(me):
"""Coroutine function: background setup for AddPeerDialog."""
table = GridPacker()
- me.vbox.pack_start(table)
+ me.vbox.pack_start(table, True, True, 0)
me.e_name = table.labelled('Name',
- ValidatingEntry(r'^[^\s.:]+$', '', 16),
+ ValidatingEntry(r'^[^\s:]+$', '', 16),
width = 3)
+ me.l_af = table.labelled('Family', combo_box_text(),
+ newlinep = True, width = 3)
+ for af in me.AFS:
+ me.l_af.append_text(af)
+ me.l_af.set_active(0)
me.e_addr = table.labelled('Address',
- ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24),
+ ValidatingEntry(r'^[a-zA-Z0-9.-:]+$', '', 24),
newlinep = True)
me.e_port = table.labelled('Port',
ValidatingEntry(numericvalidate(0, 65535),
'4070',
5))
- me.c_keepalive = G.CheckButton('Keepalives')
- me.l_tunnel = table.labelled('Tunnel',
- G.combo_box_new_text(),
+ me.l_tunnel = table.labelled('Tunnel', combo_box_text(),
newlinep = True, width = 3)
- me.tuns = conn.tunnels()
+ me.tuns = ['(Default)'] + conn.tunnels()
for t in me.tuns:
me.l_tunnel.append_text(t)
me.l_tunnel.set_active(0)
- table.pack(me.c_keepalive, newlinep = True, xopt = G.FILL)
- me.c_keepalive.connect('toggled',
- lambda t: me.e_keepalive.set_sensitive\
- (t.get_active()))
- me.e_keepalive = ValidatingEntry(r'^\d+[hms]?$', '', 5)
- me.e_keepalive.set_sensitive(False)
- table.pack(me.e_keepalive, width = 3)
+
+ def tickybox_sensitivity(tickybox, target):
+ tickybox.connect('toggled',
+ lambda t: target.set_sensitive (t.get_active()))
+
+ def optional_entry(label, rx_valid, width):
+ c = G.CheckButton(label)
+ table.pack(c, newlinep = True, xopt = G.FILL)
+ e = ValidatingEntry(rx_valid, '', width)
+ e.set_sensitive(False)
+ tickybox_sensitivity(c, e)
+ table.pack(e, width = 3)
+ return c, e
+
+ me.c_keepalive, me.e_keepalive = \
+ optional_entry('Keepalives', r'^\d+[hms]?$', 5)
+
+ me.c_cork = G.CheckButton('Cork')
+ table.pack(me.c_cork, newlinep = True, width = 4, xopt = G.FILL)
+
+ me.c_mobile = G.CheckButton('Mobile')
+ table.pack(me.c_mobile, newlinep = True, width = 4, xopt = G.FILL)
+
+ me.c_ephem = G.CheckButton('Ephemeral')
+ table.pack(me.c_ephem, newlinep = True, width = 4, xopt = G.FILL)
+
+ me.c_peerkey, me.e_peerkey = \
+ optional_entry('Peer key tag', r'^[^.:\s]+$', 16)
+ me.c_privkey, me.e_privkey = \
+ optional_entry('Private key tag', r'^[^.:\s]+$', 16)
+
+ me.c_knock, me.e_knock = \
+ optional_entry('Knock string', r'^[^:\s]+$', 16)
+
me.show_all()
def ok(me):
"""Handle an OK press: create the peer."""
try:
- if me.c_keepalive.get_active():
- ka = me.e_keepalive.get_text()
- else:
- ka = None
t = me.l_tunnel.get_active()
- if t == 0:
- tun = None
- else:
- tun = me.tuns[t]
- me._addpeer(me.e_name.get_text(),
- me.e_addr.get_text(),
- me.e_port.get_text(),
- ka,
- tun)
+ afix = me.l_af.get_active()
+ me._addpeer(me.e_name.get_text(),
+ me.AFS[afix],
+ me.e_addr.get_text(),
+ me.e_port.get_text(),
+ keepalive = (me.c_keepalive.get_active() and
+ me.e_keepalive.get_text() or None),
+ tunnel = t and me.tuns[t] or None,
+ cork = me.c_cork.get_active() or None,
+ mobile = me.c_mobile.get_active() or None,
+ ephemeral = me.c_ephem.get_active() or None,
+ key = (me.c_peerkey.get_active() and
+ me.e_peerkey.get_text() or None),
+ priv = (me.c_privkey.get_active() and
+ me.e_privkey.get_text() or None),
+ knock = (me.c_knock.get_active() and
+ me.e_knock.get_text() or None))
except ValidationError:
GDK.beep()
return
@incr
- def _addpeer(me, name, addr, port, keepalive, tunnel):
+ def _addpeer(me, *args, **kw):
"""Coroutine function: actually do the ADD command."""
try:
- conn.add(name, addr, port, keepalive = keepalive, tunnel = tunnel)
+ conn.add(*args, **kw)
me.destroy()
except T.TripeError, exc:
T.defer(moanbox, ' '.join(exc))
-class ServInfo (MyWindow):
+class ServInfo (TrivialWindow):
"""
Show information about the server and available services.
"""
def __init__(me):
- MyWindow.__init__(me)
+ TrivialWindow.__init__(me)
me.set_title('TrIPE server info')
table = GridPacker()
me.add(table)
text = desc[0].upper() + desc[1:]
ticky = G.CheckButton(text)
ticky.set_active(st == '+')
- me.vbox.pack_start(ticky)
+ me.vbox.pack_start(ticky, True, True, 0)
me.opts.append((ch, ticky))
me.show_all()
def ok(me):
return '%04d:%02d:%02d %02d:%02d:%02d (%.1f %s ago)' % \
(YY, MM, DD, hh, mm, ss, ago, unit)
def xlate_bytes(b):
- """Translate a number of bytes into something a human might want to read."""
+ """Translate a raw byte count into something a human might want to read."""
suff = 'B'
b = int(b)
for s in 'KMG':
('ip-bytes-in', xlate_bytes),
('ip-bytes-out', xlate_bytes)]
+def format_stat(format, dict):
+ if callable(format): return format(dict)
+ else: return format % dict
+
## How to lay out the stats dialog. Format is (LABEL, FORMAT): LABEL is
## the label to give the entry box; FORMAT is the format string to write into
## the entry.
+cryptolayout = \
+ [('Diffie-Hellman group',
+ '%(kx-group)s '
+ '(%(kx-group-order-bits)s-bit order, '
+ '%(kx-group-elt-bits)s-bit elements)'),
+ ('Bulk crypto transform',
+ '%(bulk-transform)s (%(bulk-overhead)s byte overhead)'),
+ ('Data encryption', lambda d: '%s (%s; %s)' % (
+ d['cipher'],
+ '%d-bit key' % (8*int(d['cipher-keysz'])),
+ d.get('cipher-blksz', '0') == '0'
+ and 'stream cipher'
+ or '%d-bit block' % (8*int(d['cipher-blksz'])))),
+ ('Message authentication', lambda d: '%s (%s; %s)' % (
+ d['mac'],
+ d.get('mac-keysz') is None
+ and 'one-time MAC'
+ or '%d-bit key' % (8*int(d['mac-keysz'])),
+ '%d-bit tag' % (8*int(d['mac-tagsz'])))),
+ ('Hash', lambda d: '%s (%d-bit output)' %
+ (d['hash'], 8*int(d['hash-sz'])))]
+
statslayout = \
[('Start time', '%(start-time)s'),
- ('Last key-exchange', '%(last-keyexch-time)s'),
+ ('Private key', '%(current-key)s')] + \
+ cryptolayout + \
+ [('Last key-exchange', '%(last-keyexch-time)s'),
('Last packet', '%(last-packet-time)s'),
('Packets in/out',
'%(packets-in)s (%(bytes-in)s) / %(packets-out)s (%(bytes-out)s)'),
'%(ip-packets-in)s (%(ip-bytes-in)s) / %(ip-packets-out)s (%(ip-bytes-out)s)'),
('Rejected packets', '%(rejected-packets)s')]
-class PeerWindow (MyWindow):
+class PeerWindow (TrivialWindow):
"""
Show information about a peer.
def __init__(me, peer):
"""Construct a PeerWindow, showing information about PEER."""
- MyWindow.__init__(me)
+ TrivialWindow.__init__(me)
me.set_title('TrIPE statistics: %s' % peer.name)
me.peer = peer
def change(me):
"""Update the display in response to a notification."""
me.e['Interface'].set_text(me.peer.ifname)
+ me.e['Address'].set_text(me.peer.addr)
def _update(me):
"""
stat = conn.stats(me.peer.name)
for s, trans in statsxlate:
stat[s] = trans(stat[s])
+ stat.update(me.peer.__dict__)
for label, format in statslayout:
- me.e[label].set_text(format % stat)
- GO.timeout_add(1000, lambda: me.cr.switch() and False)
+ me.e[label].set_text(format_stat(format, stat))
+ GL.timeout_add(1000, lambda: me.cr.switch() and False)
me.cr.parent.switch()
me.cr = None
###--------------------------------------------------------------------------
### Cryptographic status.
-class CryptoInfo (MyWindow):
+class CryptoInfo (TrivialWindow):
"""Simple display of cryptographic algorithms in use."""
def __init__(me):
- MyWindow.__init__(me)
+ TrivialWindow.__init__(me)
me.set_title('Cryptographic algorithms')
T.aside(me.populate)
def populate(me):
me.add(table)
crypto = conn.algs()
- table.info('Diffie-Hellman group',
- '%s (%d-bit order, %d-bit elements)' %
- (crypto['kx-group'],
- int(crypto['kx-group-order-bits']),
- int(crypto['kx-group-elt-bits'])),
- len = 32)
- table.info('Data encryption',
- '%s (%d-bit key; %s)' %
- (crypto['cipher'],
- int(crypto['cipher-keysz']) * 8,
- crypto['cipher-blksz'] == '0'
- and 'stream cipher'
- or '%d-bit block' % (int(crypto['cipher-blksz']) * 8)),
- newlinep = True)
- table.info('Message authentication',
- '%s (%d-bit key; %d-bit tag)' %
- (crypto['mac'],
- int(crypto['mac-keysz']) * 8,
- int(crypto['mac-tagsz']) * 8),
- newlinep = True)
- table.info('Hash function',
- '%s (%d-bit output)' %
- (crypto['hash'],
- int(crypto['hash-sz']) * 8),
- newlinep = True)
+ firstp = True
+ for label, format in cryptolayout:
+ table.info(label, format_stat(format, crypto),
+ len = 42, newlinep = not firstp)
+ firstp = False
me.show_all()
me.ui.add_ui_from_string(uidef)
## Construct the menu bar.
- vbox.pack_start(me.ui.get_widget('/menubar'), expand = False)
+ vbox.pack_start(me.ui.get_widget('/menubar'), False, True, 0)
me.add_accel_group(me.ui.get_accel_group())
## Construct and attach the auto-peers menu. (This is a horrible bodge
me.list.set_reorderable(True)
me.list.get_selection().set_mode(G.SELECTION_NONE)
scr.add(me.list)
- vbox.pack_start(scr)
+ vbox.pack_start(scr, True, True, 0)
## Construct the status bar, and listen on hooks which report changes to
## connection status.
me.status = G.Statusbar()
- vbox.pack_start(me.status, expand = False)
+ vbox.pack_start(me.status, False, True, 0)
me.hook(conn.connecthook, cr(me.connected))
me.hook(conn.disconnecthook, me.disconnected)
me.hook(conn.notehook, me.notify)
'???', 'green', '???', 'green'])
peer.win = WindowSlot(lambda: PeerWindow(peer))
me.hook(peer.pinghook, me._ping)
+ me.hook(peer.changehook, lambda: me._change(peer))
me.apchange()
def delpeer(me, peer):
me.listmodel[p.i][textcol] = '%.1f ms' % ps.tlast
me.listmodel[p.i][colourcol] = 'black'
+ def _change(me, p):
+ """Hook: notified when the peer changes state."""
+ me.listmodel[p.i][1] = p.addr
+
def setstatus(me, status):
"""Update the message in the status bar."""
me.status.pop(0)