sbin_PROGRAMS = tripe
bin_PROGRAMS = tripectl tripe-mitm pkstream
noinst_SCRIPTS = tripe-init
+bin_SCRIPTS = @pyscripts@ @pygtkscripts@
+PYTHONSCRIPTS = tripe-keys
+PYGTKSCRIPTS = tripemon
+EXTRA_SCRIPTS = ${PYTHONSCRIPTS} ${PYGTKSCRIPTS}
tripe_SOURCES = \
tripe.c tripe.h tripe-protocol.h \
admin.c peer.c \
EXTRA_DIST = tripe.conf \
debian/rules debian/control debian/changelog debian/copyright \
- debian/tripe.postinst debian/tripe.prerm debian/tripe.postrm
+ debian/tripe.postinst debian/tripe.prerm debian/tripe.postrm \
+ tripe-keys.in
##----- That's all, folks ---------------------------------------------------
static void a_lock(admin */*a*/);
static void a_unlock(admin */*a*/);
+#define BOOL(x) ((x) ? "t" : "nil")
+
/*----- Output functions --------------------------------------------------*/
/* --- @trywrite@ --- *
/*----- Backgrounded operations -------------------------------------------*/
+#define BGTAG(bg) \
+ (((admin_bgop *)(bg))->tag ? ((admin_bgop *)(bg))->tag : "<foreground>")
+
/* --- @a_bgrelease@ --- *
*
* Arguments: @admin_bgop *bg@ = backgrounded operation
{
admin *a = bg->a;
- if (bg->tag)
- xfree(bg->tag);
- else
- selbuf_enable(&a->b);
- if (bg->next)
- bg->next->prev = bg->prev;
- if (bg->prev)
- bg->prev->next = bg->next;
- else
- a->bg = bg->next;
+ T( trace(T_ADMIN, "admin: release bgop %s", BGTAG(bg)); )
+ if (bg->tag) xfree(bg->tag);
+ else selbuf_enable(&a->b);
+ if (bg->next) bg->next->prev = bg->prev;
+ if (bg->prev) bg->prev->next = bg->next;
+ else a->bg = bg->next;
xfree(bg);
- if (a->f & AF_CLOSE)
- a_destroy(a);
+ if (a->f & AF_CLOSE) a_destroy(a);
+ a_unlock(a);
}
/* --- @a_bgok@, @a_bginfo@, @a_bgfail@ --- *
bg->cancel = cancel;
bg->next = a->bg;
bg->prev = 0;
+ if (a->bg) a->bg->prev = bg;
a->bg = bg;
+ a_lock(a);
+ T( trace(T_ADMIN, "admin: add bgop %s", BGTAG(bg)); )
if (tag) a_write(a, "DETACH", tag, 0);
}
static void a_addfree(admin_addop *add)
{
+ T( trace(T_ADMIN, "admin: free add op %s", BGTAG(add)); )
if (add->peer.name) xfree(add->peer.name);
if (add->paddr) xfree(add->paddr);
}
{
admin_addop *add = (admin_addop *)bg;
+ T( trace(T_ADMIN, "admin: cancel add op %s", BGTAG(add)); )
sel_rmtimer(&add->t);
bres_abort(&add->r);
a_addfree(add);
{
admin_addop *add = v;
- a_lock(add->bg.a);
- T( trace(T_ADMIN, "admin: %u resolved", add->bg.a->seq); )
+ T( trace(T_ADMIN, "admin: add op %s resolved", BGTAG(add)); )
TIMER;
if (!h)
a_bgfail(&add->bg, "resolve-error %s", add->paddr);
sel_rmtimer(&add->t);
a_addfree(add);
a_bgrelease(&add->bg);
- a_unlock(add->bg.a);
}
/* --- @a_addtimer@ --- *
{
admin_addop *add = v;
- a_lock(add->bg.a);
- T( trace(T_ADMIN, "admin: %u resolver timeout", add->bg.a->seq); )
+ T( trace(T_ADMIN, "admin: add op %s timeout", BGTAG(add)); )
a_bgfail(&add->bg, "resolver-timeout %s\n", add->paddr);
bres_abort(&add->r);
a_addfree(add);
a_bgrelease(&add->bg);
- a_unlock(add->bg.a);
}
/* --- @acmd_add@ --- *
*/
a_bgadd(a, &add->bg, tag, a_addcancel);
+ T( trace(T_ADMIN, "admin: %u, add op %s resolving hostname `%s'",
+ a->seq, BGTAG(add), add->paddr); )
/* --- If the name is numeric, do it the easy way --- */
if (inet_aton(av[i], &add->peer.sa.sin.sin_addr)) {
+ T( trace(T_ADMIN, "admin: add op %s done the easy way", BGTAG(add)); )
a_doadd(add);
a_addfree(add);
a_bgrelease(&add->bg);
tv.tv_sec += T_RESOLVE;
sel_addtimer(&sel, &add->t, &tv, a_addtimer, add);
bres_byname(&add->r, add->paddr, a_addresolve, add);
- T( trace(T_ADMIN, "admin: %u resolving hostname `%s'",
- a->seq, add->paddr); )
return;
bad_syntax:
static void a_pingcancel(admin_bgop *bg)
{
admin_pingop *pg = (admin_pingop *)bg;
+ T( trace(T_ADMIN, "admin: cancel ping op %s", BGTAG(pg)); )
p_pingdone(&pg->ping, PING_NONOTIFY);
}
struct timeval tv;
double millis;
- a_lock(pg->bg.a);
switch (rc) {
case PING_OK:
gettimeofday(&tv, 0);
default:
abort();
}
+ T( trace(T_ADMIN, "admin: ponged ping op %s", BGTAG(pg)); )
a_bgrelease(&pg->bg);
- a_unlock(pg->bg.a);
}
/* --- @acmd_ping@, @acmd_eping@ --- *
pg = xmalloc(sizeof(*pg));
gettimeofday(&pg->pingtime, 0);
a_bgadd(a, &pg->bg, tag, a_pingcancel);
+ T( trace(T_ADMIN, "admin: ping op %s: %s to %s",
+ BGTAG(pg), cmd, p_name(p)); )
if (p_pingsend(p, &pg->ping, msg, t, a_pong, pg)) {
a_bgfail(&pg->bg, "ping-send-failed");
a_bgrelease(&pg->bg);
if (!ac || strcmp(av[0], "?") == 0) {
const trace_opt *t;
- a_info(a, "Current %s status:", what);
for (t = tt; t->ch; t++) {
- a_info(a, "%c %c %s",
- t->ch, (*ff & t->f) == t->f ? '*' : ' ', t->help);
+ a_info(a, "%c%c %s",
+ t->ch, (*ff & t->f) == t->f ? '+' : ' ', t->help);
}
} else {
unsigned sense = 1;
}
}
+static void acmd_peerinfo(admin *a, unsigned ac, char *av[])
+{
+ peer *p;
+ const peerspec *ps;
+
+ if ((p = p_find(av[0])) == 0) {
+ a_fail(a, "unknown-peer %s", av[0]);
+ return;
+ }
+
+ ps = p_spec(p);
+ a_info(a, "tunnel=%s", ps->tops->name);
+ a_info(a, "keepalive=%lu", ps->t_ka);
+ a_ok(a);
+}
+
+static void acmd_servinfo(admin *a, unsigned ac, char *av[])
+{
+ a_info(a, "implementation=edgeware-tripe");
+ a_info(a, "version=%s", VERSION);
+ a_info(a, "daemon=%s", BOOL(flags & F_DAEMON));
+ a_ok(a);
+}
+
static void acmd_stats(admin *a, unsigned ac, char *av[])
{
peer *p;
stats *st;
- if ((p = p_find(av[0])) == 0)
+ if ((p = p_find(av[0])) == 0) {
a_fail(a, "unknown-peer %s", av[0]);
- else {
- st = p_stats(p);
- a_info(a, "start-time=%s", timestr(st->t_start));
- a_info(a, "last-packet-time=%s", timestr(st->t_last));
- a_info(a, "last-keyexch-time=%s", timestr(st->t_kx));
- a_info(a, "packets-in=%lu bytes-in=%lu", st->n_in, st->sz_in);
- a_info(a, "packets-out=%lu bytes-out=%lu",
- st->n_out, st->sz_out);
- a_info(a, "keyexch-packets-in=%lu keyexch-bytes-in=%lu",
- st->n_kxin, st->sz_kxin);
- a_info(a, "keyexch-packets-out=%lu keyexch-bytes-out=%lu",
- st->n_kxout, st->sz_kxout);
- a_info(a, "ip-packets-in=%lu ip-bytes-in=%lu",
- st->n_ipin, st->sz_ipin);
- a_info(a, "ip-packets-out=%lu ip-bytes-out=%lu",
- st->n_ipout, st->sz_ipout);
- a_info(a, "rejected-packets=%lu", st->n_reject);
- a_ok(a);
+ return;
}
+
+ st = p_stats(p);
+ a_info(a, "start-time=%s", timestr(st->t_start));
+ a_info(a, "last-packet-time=%s", timestr(st->t_last));
+ a_info(a, "last-keyexch-time=%s", timestr(st->t_kx));
+ a_info(a, "packets-in=%lu bytes-in=%lu", st->n_in, st->sz_in);
+ a_info(a, "packets-out=%lu bytes-out=%lu",
+ st->n_out, st->sz_out);
+ a_info(a, "keyexch-packets-in=%lu keyexch-bytes-in=%lu",
+ st->n_kxin, st->sz_kxin);
+ a_info(a, "keyexch-packets-out=%lu keyexch-bytes-out=%lu",
+ st->n_kxout, st->sz_kxout);
+ a_info(a, "ip-packets-in=%lu ip-bytes-in=%lu",
+ st->n_ipin, st->sz_ipin);
+ a_info(a, "ip-packets-out=%lu ip-bytes-out=%lu",
+ st->n_ipout, st->sz_ipout);
+ a_info(a, "rejected-packets=%lu", st->n_reject);
+ a_ok(a);
}
static void acmd_kill(admin *a, unsigned ac, char *av[])
{ "kill", "kill PEER", 1, 1, acmd_kill },
{ "list", "list", 0, 0, acmd_list },
{ "notify", "notify MESSAGE ...", 1, 0xffff, acmd_notify },
+ { "peerinfo", "peerinfo PEER", 1, 1, acmd_peerinfo },
{ "ping", "ping [OPTIONS] PEER", 1, 0xffff, acmd_ping },
{ "port", "port", 0, 0, acmd_port },
{ "quit", "quit", 0, 0, acmd_quit },
{ "reload", "reload", 0, 0, acmd_reload },
+ { "servinfo", "servinfo", 0, 0, acmd_servinfo },
{ "stats", "stats PEER", 1, 1, acmd_stats },
#ifndef NTRACE
{ "trace", "trace [OPTIONS]", 0, 1, acmd_trace },
* immediately.
*/
-static void a_lock(admin *a) { assert(!(a->f & AF_LOCK)); a->f |= AF_LOCK; }
+static void a_lock(admin *a) { a->ref++; }
-/* --- @a_unlock@ --- *
+/* --- @a_dodestroy@ --- *
*
* Arguments: @admin *a@ = pointer to an admin block
*
* Returns: ---
*
- * Use: Unlocks an admin block, allowing its destruction. This is
- * also the second half of @a_destroy@.
+ * Use: Actually does the legwork of destroying an admin block.
*/
-static void a_unlock(admin *a)
+static void a_dodestroy(admin *a)
{
admin_bgop *bg, *bbg;
-
- assert(a->f & AF_LOCK);
-
- /* --- If we're not dead, that's fine --- */
-
- if (!(a->f & AF_DEAD)) {
- a->f &= ~AF_LOCK;
- return;
- }
-
- /* --- If we are, then destroy the rest of the block --- */
T( trace(T_ADMIN, "admin: completing destruction of connection %u",
a->seq); )
DESTROY(a);
}
+/* --- @a_unlock@ --- *
+ *
+ * Arguments: @admin *a@ = pointer to an admin block
+ *
+ * Returns: ---
+ *
+ * Use: Unlocks an admin block, allowing its destruction. This is
+ * also the second half of @a_destroy@.
+ */
+
+static void a_unlock(admin *a)
+{
+ assert(a->ref);
+ if (!--a->ref && (a->f & AF_DEAD))
+ a_dodestroy(a);
+}
+
/* --- @a_destroy@ --- *
*
* Arguments: @admin *a@ = pointer to an admin block
/* --- If the block is locked, that's all we can manage --- */
- if (a->f & AF_LOCK) {
- T( trace(T_ADMIN, "admin: deferring destruction..."); )
- return;
- }
- a->f |= AF_LOCK;
- a_unlock(a);
+ if (!a->ref)
+ a_dodestroy(a);
+ T( else
+ trace(T_ADMIN, "admin: deferring destruction..."); )
}
/* --- @a_line@ --- *
AC_PROG_MAKE_SET
AC_PROG_CC
AM_PROG_LIBTOOL
+python=no
+mdw_PROG_PYTHON([2.3],
+ [python=yes
+ pyscripts='${PYTHONSCRIPTS}'
+ pymans='${PYTHONMANS}'])
+AC_SUBST([pyscripts]) AC_SUBST([pymans])
+
+if test $python = yes; then
+ mdw_CHECK_PYTHON([2.3])
+ AC_CACHE_CHECK([for pygtk], [mdw_cv_pygtk], [
+ mdw_cv_pygtk=no
+ python -c >&5 2>&5 '
+import pygtk
+pygtk.require("2.0")
+import gtk
+' && mdw_cv_pygtk=yes
+ ])
+ if test $mdw_cv_pygtk = yes; then
+ pygtkscripts='${PYGTKSCRIPTS}'
+ pygtkmans='${PYGTKMANS}'
+ fi
+fi
+AC_SUBST([pygtkscripts]) AC_SUBST([pygtkmans])
+
AC_CHECK_HEADERS([stdarg.h])
mdw_GCC_FLAGS([-Wall])
mdw_OPT_TRACE
AC_SUBST(initconfig)
])
AC_SUBST(DIRS)
-AC_OUTPUT(Makefile doc/Makefile ethereal/Makefile tripe-init)
+AC_OUTPUT( \
+ Makefile doc/Makefile ethereal/Makefile \
+ tripe-init tripe-keys tripemon)
dnl ----- That's all, folks -------------------------------------------------
and authenticity of packets it sends and receives.
.
This package contains the protocol analysis plug-in for Ethereal.
+
+Package: tripemon
+Architecture: all
+Depends: python (>= 2.3), python-gtk2 (>= 2.6), tripe
+Description: Trivial IP Encryption: a simple virtual private network
+ TrIPE is a simple VPN protocol. It uses cryptography to ensure secrecy
+ and authenticity of packets it sends and receives.
+ .
+ This package contains a graphical monitor program for managing and
+ keeping an eye on a TrIPE server.
+
+Package: tripe-keys
+Architecture: all
+Depends: python (>= 2.3), tripe, catacomb-bin, python-catacomb
+Description: Trivial IP Encryption: a simple virtual private network
+ TrIPE is a simple VPN protocol. It uses cryptography to ensure secrecy
+ and authenticity of packets it sends and receives.
+ .
+ This package contains a tool for centrally managing TrIPE keys.
cp deb-build/tripe-init debian/tripe/etc/init.d/tripe
chmod 755 debian/tripe/etc/init.d/tripe
cp tripe.conf debian/tripe/etc/default/tripe
+ mkdir -p debian/tripe-keys/usr/bin
+ mv debian/tripe/usr/bin/tripe-keys debian/tripe-keys/usr/bin
+ mkdir -p \
+ debian/tripe-keys/usr/share/man/man5 \
+ debian/tripe-keys/usr/share/man/man8
+ mv debian/tripe/usr/share/man/man5/tripe-keys.conf.5 \
+ debian/tripe-keys/usr/share/man/man5
+ mv debian/tripe/usr/share/man/man8/tripe-keys.8 \
+ debian/tripe-keys/usr/share/man/man8
+ mkdir -p debian/tripe-keys/usr/share/doc/tripe-keys/examples
+ cp tripe-keys.master \
+ debian/tripe-keys/usr/share/doc/tripe-keys/examples
+ mkdir -p debian/tripemon/usr/bin
+ mv debian/tripe/usr/bin/tripemon debian/tripemon/usr/bin
+ mkdir -p \
+ debian/tripemon/usr/share/man/man1
+ mv debian/tripe/usr/share/man/man1/tripemon.1 \
+ debian/tripemon/usr/share/man/man1
mkdir -p debian/pkstream/usr/bin
mv debian/tripe/usr/bin/pkstream debian/pkstream/usr/bin
mkdir -p debian/pkstream/usr/share/man/man1
rm -f debian/tripe-ethereal/usr/lib/ethereal/plugins/*/*.a
rmdir debian/tripe/usr/lib
-binary-indep:
+binary-indep: install
+ dh_testdir -i
+ dh_testroot -i
+ dh_compress -i
+ dh_installdocs -i
+ dh_installlogrotate -i
+ dh_gencontrol -i
+ dh_fixperms -i
+ dh_installdeb -i
+ dh_md5sums -i
+ dh_builddeb -i
binary-arch: install
dpkg --status ethereal | \
## -*-makefile-*-
##
-## $Id: Makefile.am,v 1.5 2004/04/08 01:36:17 mdw Exp $
+## $Id$
##
## Makefile for TrIPE documentation
##
AUTOMAKE_OPTIONS = foreign
-man_MANS = tripe.8 tripectl.1 tripe-admin.5 pkstream.1 tripe-mitm.8
-EXTRA_DIST = $(man_MANS)
+man_MANS = \
+ tripe.8 tripectl.1 tripe-admin.5 pkstream.1 tripe-mitm.8 \
+ @pymans@ @pygtkmans@
+PYTHONMANS = tripe-keys.8 tripe-keys.conf.5
+PYGTKMANS = tripemon.1
+EXTRA_DIST = $(man_MANS) $(PYTHONMANS) $(PYGTKMANS)
##----- That's all, folks ---------------------------------------------------
.\" -*-nroff-*-
+.\"
+.ie t \{\
+. if \n(.g \{\
+. fam P
+. \}
+.\}
.TH tripe-admin 5 "18 February 2001" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
.SH NAME
tripe-admin \- administrator commands for TrIPE
.PP
A background command will never issue an
.B OK
-response: it will always detach and then issue a
+or
+.B BGINFO
+response: it will always detach and then issue any
+.B BGINFO
+lines followed by
.B BGOK
response.
.SS "Network addresses"
words are assumed to represent an
.B INET
address.
+.SS "Key-value output"
+Some commands (e.g.,
+.B STATS
+and
+.BR SERVINFO )
+produce output in the form of
+.IB key = value
+pairs, one per word. Neither the
+.I key
+nor the
+.I value
+contain spaces.
+.SS "Trace lists"
+Commands which enable or disable kinds of output (e.g.,
+.B TRACE
+and
+.BR WATCH )
+work in similar ways. They take a single optional argument, which
+consists of a string of letters selecting message types, optionally
+interspersed with
+.RB ` + '
+to enable, or
+.RB ` \- '
+to disable, the subsequently listed types.
+.PP
+If the argument is omitted, the available message types are displayed,
+one to an
+.B INFO
+line, in a fixed-column format. Column zero contains the key letter for
+selecting that message type; column one contains either a space or a
+.RB ` + '
+sign, if the message type is disabled or enabled respectively; and a
+textual description of the message type begins at column 3 and continues
+to the end of the line.
+.PP
+Lowercase key letters control individual message types. Uppercase key
+letters control collections of message types.
.SH "COMMAND REFERENCE"
The commands provided are:
.TP
.B USER
notification to all interested administration clients.
.TP
+.BI "PEERINFO " peer
+Returns information about a peer, in key-value form. The following keys
+are returned.
+.RS
+.TP
+.B tunnel
+The tunnel driver used for this peer.
+.TP
+.B keepalive
+The keepalive interval, in seconds, or zero if no keepalives are to be
+sent.
+.RE
+.TP
.BI "PING \fR[" options "\fR] " peer
Send a transport-level ping to the peer. The ping and its response are
not encrypted or authenticated. This command, possibly in conjunction
.B "QUIT"
Instructs the server to exit immediately. A warning is sent.
.TP
+.B "SERVINFO"
+Returns information about the server, in the form of key-value pairs.
+The following keys are used.
+.RS
+.TP
+.B implementation
+A keyword naming the implementation of the
+.BR tripe (8)
+server. The current implementation is called
+.BR edgeware-tripe .
+.TP
+.B version
+The server's version number, as reported by
+.BR VERSION .
+.TP
+.B daemon
+Either
+.B t
+or
+.BR nil ,
+if the server has or hasn't (respectively) become a daemon.
+.RE
+.TP
.BI "STATS " peer
Emits a number of
.B INFO
The statistics-gathering is experimental and subject to change.
.TP
.BR "TRACE " [\fIoptions\fP]
-A trace argument consists of a string of letters (listed below)
-selecting trace outputs, optionally interspersed with
-.RB ` + '
-to enable, or
-.RB ` \- '
-to disable, the subsequently listed outputs; the initial behaviour is to
-enable listed outputs. For example, the string
-.B ra\-st+x
-enables tracing of peer management, admin-connection handling and
-key-exchange processing, and disables tracing of symmetric keyset
-management and the system-specific tunnel driver. If no argument is
-given, a table is returned showing the available tracing option letters
-and their meanings. Programs should not attempt to parse this table:
-its format is not guaranteed to remain the same.
+Selects trace outputs: see
+.B "Trace lists"
+above. Message types provided are:
.RS
.PP
Currently, the following tracing options are supported:
All of the above.
.RE
.TP
+.B "TUNNELS"
+For each available tunnel driver, an
+.B INFO
+line is printed giving its name.
+.TP
+.B "VERSION"
+Causes the server to emit an
+.B INFO
+line stating its software version, as two words: the server name, and
+its version string. The server name
+.B tripe
+is reserved to the Straylight/Edgeware implementation.
+.TP
.BR "WATCH " [\fIoptions\fP]
Enables or disables asynchronous messages
.IR "for the current connection only" .
-This command has no effect on other connections. A watch argument
-consists of a string of letters (listed below) selecting message types,
-optionally interspersed with
-.RB ` + '
-to enable, or
-.RB ` \- '
-to disable, the subsequently listed types, similar to
-.B trace
+See
+.B "Trace lists"
above. The default watch state for the connection the server opens
automatically on stdin/stdout is to show warnings and trace messages;
other connections show no asynchronous messages. (This is done in order
any warnings.)
.RS
.PP
-Currently, the following watch options are supported:
+Message types provided are:
.TP
.B t
.B TRACE
All of the above.
.RE
.TP
-.B "VERSION"
-Causes the server to emit an
-.B INFO
-line stating its software version, as two words: the server name, and
-its version string. The server name
-.B tripe
-is reserved to the Straylight/Edgeware implementation.
-.TP
.BI "WARN " tokens\fR...
Issues a
.B USER
--- /dev/null
+.\" -*-nroff-*-
+.\".
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.ie t \{\
+. ds o \(bu
+. ds ss \s8\u
+. ds se \d\s0
+. if \n(.g \{\
+. fam P
+. \}
+.\}
+.el \{\
+. ds o o
+. ds ss ^
+. ds se
+.\}
+.TH tripe-keys 8 "14 September 2005" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
+.SH "NAME"
+tripe-keys \- simple centralized key management for tripe
+.SH "SYNOPSIS"
+.B tripe-keys
+.I operation
+.IP "Operations supported:"
+.B "help"
+.br
+.BI "generate " tag
+.br
+.B "update"
+.br
+.B "rebuild"
+.br
+.B "setup"
+.br
+.B "upload"
+.SH "DESCRIPTION"
+The
+.B tripe-keys
+script implements a very simple, centralized key management system for
+.BR tripe (8).
+It assumes that there is a central authority who knows all the public
+keys for a private network.
+.SS "Overview"
+The
+.B tripe-keys
+program maintains a
+.I repository
+of public keys. It provides a way for a master authority to publish the
+repository and for clients to obtain authentic copies of it.
+.PP
+The repository is very simple: it consists of a directory
+.B repos
+full of public-key files, each named
+.BI peer- tag .pub \fR.
+.PP
+The repository setup process creates a master signing key, stored in the
+.B master
+keyring, and a key describing the parameters to be used for generating
+key-exchange keys, stored in
+.BR repos/param .
+.PP
+The master authority has a configuration file
+.BR tripe-keys.master ,
+usually created by copying the template provided and editing it.
+.PP
+The published repository consists of a tarball of the
+.B repos
+directory, containing the key-generation parameters and all the peers'
+public keys, and a client configuration file
+.BR tripe-keys.conf .
+The tarball is signed by the master authority's signing key.
+.PP
+The client configuration file is essentially a copy of
+.B tripe-keys.master
+with some extra bits filled in: in particular, it contains the
+fingerprint of the master signing key, so that the client can be sure
+it's checking the right key.
+.PP
+A peer starts by downloading a copy of
+.B tripe-keys.conf
+and then making sure it's authentic. (This is one of the tricky bits.
+The other is getting public keys back to the master authority.) This is
+enough for the peer to fetch a copy of the repository, verify the
+signature, and assemble a public keyring for the other peers in the
+network.
+.PP
+In fact, it's not
+.I quite
+that simple. The system allows new signing keys to replace old ones, so
+in fact the publication process signs the repository archive using a
+collection of keys. Each signing key is given a sequence number. The
+client configuration file contains the sequence number of the master
+signing key whose fingerprint it knows. During an update, the right
+signature is fetched and checked; if there's a new master key, then the
+.B tripe-keys.conf
+in the new repository archive will have its sequence number and
+fingerprint: the update process will replace its configuration file with
+the new version, and the peer will use the new key from then on.
+.SS "Options"
+The
+.B tripe-keys
+program accepts some standard command-line options:
+.TP
+.B "\-h, \-\-help"
+Print general help about
+.B tripe-keys
+to standard output and exit successfully.
+.TP
+.B "\-v, \-\-version"
+Print the version number of
+.B tripe-keys
+to standard output and exit successfully.
+.TP
+.B "\-u, \-\-usage"
+Print brief usage about
+.B tripe-keys
+to standard output and exit successfully.
+.SS "Subcommands"
+.TP
+.BI help\fR[ command \fR]
+With no arguments, shows help, as for the
+.B \-\-help
+option. With an argument, shows help about that
+.IR command .
+.TP
+.B "setup"
+Constructs a new repository and makes a signing key (as for
+.BR newmaster )
+and key-exchange parameters. Fails if
+.B repos
+already exists.
+.TP
+.B "upload"
+Build a repository archive, sign it with the active signing keys, and
+make a
+.B tripe-keys.conf
+file. Copy the results to the places named by
+.IR repos-file ,
+.IR sig-file ,
+and
+.I conf-file
+respectively. (This command is currently misnamed. It only copies
+stuff about the local filesystem. Some day it'll really upload stuff.)
+.TP
+.BI "generate " tag
+Generate a peer key for the peer named
+.IR tag .
+The private key ends up in
+.BR keyring ;
+the public key is written to
+.BI peer- tag .pub
+in the
+.I current
+directory.
+.TP
+.B update
+Fetches a new copy of the repository archive and its signature. It
+unpacks the archive in a temporary directory, and checks the enclosed
+master public key against the fingerprint in the configuration file. It
+then verifies the signature on the archive using this public key. If
+all is well, it replaces the current
+.B repos
+directory with the version in the new archive, and if necessary it
+replaces the current configuration file with the new one in the
+archive. It then does a
+.B rebuild
+to construct a new
+.B keyring.pub
+file.
+.TP
+.B newmaster
+Generates a new master signing key. The old master key is not deleted.
+.TP
+.B rebuild
+Rebuilds the public keyring
+.B keyring.pub
+from the public keys in the
+.B repos
+directory.
+.TP
+.B clean
+Deletes everything which
+.B tripe-keys
+might have written to a directory. In particular, it deletes
+.BR repos ,
+.BR tmp ,
+.BR master ,
+.BR keyring ,
+.BR keying.pub ,
+and their associated
+.B .old
+files.
+.SH "SEE ALSO"
+.BR key (1),
+.BR tripe\-keys.conf (5),
+.BR tripe (8).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
--- /dev/null
+.\" -*-nroff-*-
+.\".
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.ie t \{\
+. ds o \(bu
+. ds ss \s8\u
+. ds se \d\s0
+. if \n(.g \{\
+. fam P
+. \}
+.\}
+.el \{\
+. ds o o
+. ds ss ^
+. ds se
+.\}
+.TH tripe-keys.conf 5 "14 September 2005" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
+.SH "NAME"
+tripe-keys.conf \- configuration file format for tripe-keys
+.SH "DESCRIPTION"
+The
+.B tripe-keys.master
+or
+.B tripe-keys.conf
+file is a simple line-based configuration file read by
+.BR tripe-keys (1).
+Lines may be empty (consist only of whitespace), be comments (first
+non-whitespace character is
+.RB ` # ')
+or have the form
+.IP
+.I name
+.RB [ = ]
+.I value
+.PP
+A
+.I name
+consists of alphanumeric characters and hyphens. Values may contain
+substitutions, of the form
+.BI ${ name } \fR,
+which are replaced by the value assigned to
+.IR name .
+Many
+.IR name s
+have significance to the
+.B tripe-keys
+program: these are described below. Many have sensible defaults.
+.SS "The tripe-keys.master file"
+The client configuration file is built by applying substitutions to the
+.B tripe-keys.master
+file. The following tokens are substituted:
+.TP
+.B @MASTER-SEQUENCE@
+The sequence number of the most recently-added signing key.
+.TP
+.B @HK-MASTER@
+The fingerprint of the signing key identified by
+.BR @MASTER-SEQUENCE@ .
+.SS "Master repository parameters"
+.TP
+.I base-url
+The base URL of the key repository (usually with a trailing
+.RB ` / ').
+Typically, this will be something like
+.RB http://www.distorted.org.uk/vpn/ .
+No default.
+.TP
+.I repos-base
+The basename for the repository archive. Default is
+.BR tripe-keys.tar.gz .
+.TP
+.I sig-base
+The basename template for repository signatures. Default is
+.BR tripe-keys.sig-<SEQ> .
+The
+.RB ` <SEQ> '
+portion, if any, is replaced by the sequence number of the key which
+made the signature.
+.TP
+.I repos-url
+The URL for the key repository tarball. Default is the concatenation of
+.I base-url
+and
+.IR repos-base .
+.TP
+.I sig-url
+The URL template for key repository signatures. Default is the
+concatenation of
+.I sig-url
+and
+.IR sig-base .
+.TP
+.I master-sequence
+The sequence number of the master authority's current signing key. No
+default. Usually set up automatically.
+.TP
+.I hk-master
+The fingerprint of the current master signing key. No default. Usually
+set up automatically.
+.SS "Crypto parameters"
+.TP
+.I kx
+Key-exchange algorithm to use. Either
+.B dh
+(integer Diffie-Hellman)
+or
+.B ec
+(elliptic curves). The default is
+.BR dh .
+.TP
+.I kx-param
+Options to pass to
+.B "key add"
+when generating the parameters key. Default depends on
+.I kx
+as follows.
+.TS
+center;
+| ci | ci |
+| lb | lb |.
+_
+kx kx-param
+_
+dh \-LS \-b2048 \-B256
+ec \-Cnist-p256
+_
+.TE
+.TP
+.I kx-expire
+Expiry time for generated keys. Default is
+.BR "now + 1 day" .
+.TP
+.I hash
+Hashing algorithm to use. Default is
+.BR sha256 .
+.TP
+.I mac
+Message authentication algorithm to use. Default is
+.IB hash -hmac/ halfhashlen \fR,
+where
+.I halfhashlen
+is half of
+.IR hash 's
+output length.
+.TP
+.I mgf
+Mask-generation algorithm to use. Default is
+.IB hash -mgf \fR.
+This is probably a good choice.
+.TP
+.I cipher
+Symmetric encryption scheme to use. Default is
+.BR blowfish-cbc .
+.TP
+.I sig
+Signature scheme to use. Must be one of those recognized by
+.BR catsign (1).
+Default is
+.B dsa
+if
+.I kx
+is
+.BR dh ,
+or
+.B ecdsa
+if
+.I kx
+is
+.BR ec .
+.TP
+.I sig-genalg
+Key-generation algorithm for signing key. Default depends on
+.I sig
+as follows.
+.TS
+center;
+| ci | ci |
+| lb | lb |.
+_
+sig sig-genalg
+_
+kcdsa dh
+dsa dsa
+rsapcs1 rsa
+rsapss rsa
+ecdsa ec
+eckcdsa ec
+_
+.TE
+.TP
+.I sig-param
+Signature-key generation parameters. Default depends on
+.I sig-genalg
+as follows.
+.TS
+center;
+| ci | ci |
+| lb | lb |.
+_
+sig-genalg sig-param
+_
+dh \-LS \-b2048 \-B256
+dsa \-b2048 \-B256
+rsa \-b2048
+ec \-Cnist-p256
+_
+.TE
+.TP
+.I sig-hash
+Hash function to use for making signatures. Default is
+.IR hash .
+.TP
+.I sig-fresh
+Oldest time we should consider a signed archive to be fresh. Default is
+.BR always ,
+meaning that all signatures are fresh.
+.TP
+.I sig-expire
+Expiry time for master signing key. Default is
+.BR forever .
+.TP
+.I fingerprint-hash
+Hash function to use for key fingerprinting. Default is
+.IR hash .
+.SS "Master maintenance parameters"
+.TP
+.I base-dir
+Local base directory for the repository files. This probably ought to
+end in a
+.RB ` / '
+character. No default.
+.TP
+.I repos-file
+Filename for local repository tarball. Default is the concatenation of
+.I base-dir
+and
+.IB repos-base .
+.TP
+.I sig-file
+Tempalte for repository signatures. Default is the concatenation of
+.I base-dir
+and
+.IR sig-base .
+.TP
+.I conf-file
+Filename for local repository configuration file. Default is
+.IB basedir /tripe-keys.conf \fR.
+.SH "SEE ALSO"
+.BR tripe (8),
+.BR tripe\-keys (8).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
--- /dev/null
+.\" -*-nroff-*-
+.\".
+.de hP
+.IP
+\h'-\w'\fB\\$1\ \fP'u'\fB\\$1\ \fP\c
+..
+.de VS
+.sp 1
+.RS
+.nf
+.ft B
+..
+.de VE
+.ft R
+.fi
+.RE
+.sp 1
+..
+.ie t \{\
+. ds o \(bu
+. ds ss \s8\u
+. ds se \d\s0
+. if \n(.g \{\
+. fam P
+. \}
+.\}
+.el \{\
+. ds o o
+. ds ss ^
+. ds se
+.\}
+.TH tripemon 1 "4 October 2005" "Straylight/Edgeware" "TrIPE: Trivial IP Encryption"
+.SH "NAME"
+tripemon \- graphical monitor for tripe
+.SH "SYNOPSIS"
+.B tripemon
+.RB [ \-d
+.IR dir ]
+.RB [ \-a
+.IR socket ]
+.SH "DESCRIPTION"
+The
+.B tripemon
+program is a fairly simple graphical monitor program for TrIPE. It's
+not ever-so user-friendly in some ways, but it works fairly well. A
+perusal of
+.BR tripe-admin (5)
+would help you understand it better. That said, describing the program
+in detail is probably less interesting than letting you explore it for
+yourself.
+.PP
+The command-line options available are:
+.TP
+.B "\-h, \-\-help"
+Writes a brief description of the command-line options available to
+standard output and exits with status 0.
+.TP
+.B "\-v, \-\-version"
+Writes tripe's version number to standard output and exits with status
+0.
+.TP
+.B "\-u, \-\-usage"
+Writes a brief usage summary to standard output and exits with status 0.
+.TP
+.BI "\-d, \-\-directory=" dir
+Make
+.I dir
+the current directory, before doing anything else. Note that all the
+other filenames (e.g., the log output file) are relative to this
+directory. The default directory, if this option is not specified, is
+taken from the environment variable
+.BR TRIPEDIR ;
+if that's not defined either, a default default of
+.BR /var/lib/tripe
+is used.
+.TP
+.BI "\-a, \-\-admin=" socket
+If connecting to a running server, connect to the socket named
+.IR socket ;
+if running a new server, instruct it to listen for admin
+connections on
+.IR socket .
+.SH "SEE ALSO"
+.BR tripectl (1),
+.BR tripe\-admin (5),
+.BR tripe (8).
+.SH "AUTHOR"
+Mark Wooding, <mdw@distorted.org.uk>
/*----- Main code ---------------------------------------------------------*/
+static void checktimers(void)
+{
+ sel_timer *t, **tt;
+
+ tt = &sel.timers;
+ while (*tt) {
+ assert((*tt)->prev == tt);
+ tt = &(*tt)->next;
+ }
+}
+
/* --- @p_pingtype@ --- *
*
* Arguments: @unsigned msg@ = message type
void p_pingdone(ping *p, int rc)
{
- if (!p->p) return;
if (p->prev) p->prev->next = p->next;
else p->p->pings = p->next;
if (p->next) p->next->prev = p->prev;
if (rc != PING_TIMEOUT) sel_rmtimer(&p->t);
- p->p = 0;
+ T( trace(T_PEER, "peer: ping 0x%08lx done (rc = %d)",
+ (unsigned long)p->id, rc); )
+checktimers();
if (rc >= 0) p->func(rc, p->arg);
}
pg->p = p;
pg->func = func;
pg->arg = arg;
+ if (p->pings) p->pings->prev = pg;
p->pings = pg;
gettimeofday(&tv, 0);
tv.tv_sec += timeout;
sel_addtimer(&sel, &pg->t, &tv, p_pingtimeout, pg);
+checktimers();
T( trace(T_PEER, "peer: send %s 0x%08lx to %s",
p_pingtype(type), (unsigned long)pg->id, p->spec.name); )
return (0);
const char *p_name(peer *p) { return (p->spec.name); }
+/* --- @p_spec@ --- *
+ *
+ * Arguments: @peer *p@ = pointer to a peer block
+ *
+ * Returns: Pointer to the peer's specification
+ */
+
+const peerspec *p_spec(peer *p) { return (&p->spec); }
+
/* --- @p_find@ --- *
*
* Arguments: @const char *name@ = name to look up
--- /dev/null
+#! @PYTHON@
+# -*-python-*-
+
+### External dependencies
+
+import catacomb as C
+import os as OS
+import sys as SYS
+import sre as RX
+import getopt as O
+from cStringIO import StringIO
+from errno import *
+from stat import *
+
+### Useful regular expressions
+
+r_comment = RX.compile(r'^\s*(#|$)')
+r_keyval = RX.compile(r'^\s*([-\w]+)(?:\s+(?!=)|\s*=\s*)(|\S|\S.*\S)\s*$')
+r_dollarsubst = RX.compile(r'\$\{([-\w]+)\}')
+r_atsubst = RX.compile(r'@([-\w]+)@')
+r_nonalpha = RX.compile(r'\W')
+
+### Utility functions
+
+class SubprocessError (Exception): pass
+class VerifyError (Exception): pass
+
+quis = OS.path.basename(SYS.argv[0])
+PACKAGE = "@PACKAGE@"
+VERSION = "@VERSION@"
+
+def moan(msg):
+ SYS.stderr.write('%s: %s\n' % (quis, msg))
+
+def die(msg, rc = 1):
+ moan(msg)
+ SYS.exit(rc)
+
+def subst(s, rx, map):
+ out = StringIO()
+ i = 0
+ for m in rx.finditer(s):
+ out.write(s[i:m.start()] + map[m.group(1)])
+ i = m.end()
+ out.write(s[i:])
+ return out.getvalue()
+
+def rmtree(path):
+ try:
+ st = OS.stat(path)
+ except OSError, err:
+ if err.errno == ENOENT:
+ return
+ raise
+ if not S_ISDIR(st.st_mode):
+ OS.unlink(path)
+ else:
+ cwd = OS.getcwd()
+ try:
+ OS.chdir(path)
+ for i in OS.listdir('.'):
+ rmtree(i)
+ finally:
+ OS.chdir(cwd)
+ OS.rmdir(path)
+
+def zap(file):
+ try:
+ OS.unlink(file)
+ except OSError, err:
+ if err.errno == ENOENT: return
+ raise
+
+def run(args):
+ args = map(conf_subst, args.split())
+ nargs = []
+ for a in args:
+ if len(a) > 0 and a[0] != '!':
+ nargs += [a]
+ else:
+ nargs += a[1:].split()
+ args = nargs
+ print '+ %s' % ' '.join(args)
+ rc = OS.spawnvp(OS.P_WAIT, args[0], args)
+ if rc != 0:
+ raise SubprocessError, rc
+
+def hexhyphens(bytes):
+ out = StringIO()
+ for i in xrange(0, len(bytes)):
+ if i > 0 and i % 4 == 0: out.write('-')
+ out.write('%02x' % ord(bytes[i]))
+ return out.getvalue()
+
+def fingerprint(kf, ktag):
+ h = C.gchashes[conf['fingerprint-hash']]()
+ k = C.KeyFile(kf)[ktag].fingerprint(h, '-secret')
+ return h.done()
+
+### Read configuration
+
+class ConfigFileError (Exception): pass
+conf = {}
+
+def conf_subst(s): return subst(s, r_dollarsubst, conf)
+
+## Read the file
+def conf_read(f):
+ lno = 0
+ for line in file(f):
+ lno += 1
+ if r_comment.match(line): continue
+ if line[-1] == '\n': line = line[:-1]
+ match = r_keyval.match(line)
+ if not match:
+ raise ConfigFileError, "%s:%d: bad line `%s'" % (f, lno, line)
+ k, v = match.groups()
+ conf[k] = conf_subst(v)
+
+## Sift the wreckage
+def conf_defaults():
+ for k, v in [('sig-url', '${base-url}tripe-keys.sig'),
+ ('repos-url', '${base-url}tripe-keys.tar.gz'),
+ ('sig-file', '${base-dir}tripe-keys.sig'),
+ ('repos-file', '${base-dir}tripe-keys.tar.gz'),
+ ('conf-file', '${base-dir}tripe-keys.conf'),
+ ('kx', 'dh'),
+ ('kx-param', lambda: {'dh': '-LS -b2048 -B256',
+ 'ec': '-Cnist-p256'}[conf['kx']]),
+ ('kx-expire', 'now + 1 year'),
+ ('cipher', 'blowfish-cbc'),
+ ('hash', 'sha256'),
+ ('mgf', '${hash}-mgf'),
+ ('mac', lambda: '%s-hmac/%d' %
+ (conf['hash'],
+ C.gchashes[conf['hash']].hashsz * 4)),
+ ('sig', lambda: {'dh': 'dsa', 'ec': 'ecdsa'}[conf['kx']]),
+ ('sig-fresh', 'always'),
+ ('sig-genalg', lambda: {'kcdsa': 'dh',
+ 'dsa': 'dsa',
+ 'rsapkcs1': 'rsa',
+ 'rsapss': 'rsa',
+ 'ecdsa': 'ec',
+ 'eckcdsa': 'ec'}[conf['sig']]),
+ ('sig-param', lambda: {'dh': '-LS -b2048 -B256',
+ 'dsa': '-b2048 -B256',
+ 'ec': '-Cnist-p256',
+ 'rsa': '-b2048'}[conf['sig-genalg']]),
+ ('sig-hash', '${hash}'),
+ ('sig-expire', 'forever'),
+ ('fingerprint-hash', '${hash}')]:
+ try:
+ if k in conf: continue
+ if type(v) == str:
+ conf[k] = conf_subst(v)
+ else:
+ conf[k] = v()
+ except KeyError, exc:
+ if len(exc.args) == 0: raise
+ conf[k] = '<missing-var %s>' % exc.args[0]
+
+### Commands
+
+def version(fp = SYS.stdout):
+ fp.write('%s, %s version %s\n' % (quis, PACKAGE, VERSION))
+
+def usage(fp):
+ fp.write('Usage: %s SUBCOMMAND [ARGS...]\n' % quis)
+
+def cmd_help(args):
+ if len(args) == 0:
+ version(SYS.stdout)
+ print
+ usage(SYS.stdout)
+ print """
+Key management utility for TrIPE.
+
+Options supported:
+
+-h, --help Show this help message.
+-v, --version Show the version number.
+-u, --usage Show pointlessly short usage string.
+
+Subcommands available:
+"""
+ args = commands.keys()
+ args.sort()
+ for c in args:
+ func, min, max, help = commands[c]
+ print '%s %s' % (c, help)
+
+def cmd_setup(args):
+ OS.mkdir('repos')
+
+ ## Generate the master key
+ run('''key -kmaster add
+ -a${sig-genalg} !${sig-param}
+ -e${sig-expire} -l -ttripe-keys-master ccsig
+ sig=${sig} hash=${sig-hash}''')
+ run('key -kmaster extract -f-secret repos/master.pub tripe-keys-master')
+
+ ## Generate the parameters key
+ run('''key -krepos/param add
+ -a${kx}-param !${kx-param}
+ -eforever -tparam tripe-${kx}-param
+ cipher=${cipher} hash=${hash} mac=${mac} mgf=${mgf}''')
+
+ ## Get fingerprints
+ print 'Setup OK: master key = %s' % \
+ hexhyphens(fingerprint('repos/master.pub', 'tripe-keys-master'))
+
+def cmd_upload(args):
+
+ ## Sanitize the repository directory
+ umask = OS.umask(0); OS.umask(umask)
+ mode = 0666 & ~umask
+ for f in OS.listdir('repos'):
+ ff = OS.path.join('repos', f)
+ if f.endswith('.old'):
+ OS.unlink(ff)
+ continue
+ OS.chmod(OS.path.join('repos', f), mode)
+
+ ## Build the configuration file
+ v = {'HK-MASTER': hexhyphens(fingerprint('repos/master.pub',
+ 'tripe-keys-master'))}
+ fin = file('tripe-keys.master')
+ fout = file(conf_subst('${conf-file}.new'), 'w')
+ for line in fin:
+ fout.write(subst(line, r_atsubst, v))
+ fin.close(); fout.close()
+
+ ## Make and sign the repository archive
+ run('tar chozf ${repos-file}.new repos')
+ run('''catsign -kmaster sign -abdC -ktripe-keys-master
+ -o${sig-file}.new ${repos-file}.new''')
+
+ ## Commit the changes
+ for i in ['conf-file', 'repos-file', 'sig-file']:
+ base = conf[i]
+ new = '%s.new' % base
+ OS.rename(new, base)
+
+def cmd_update(args):
+ cwd = OS.getcwd()
+ rmtree('tmp')
+ try:
+
+ ## Fetch a new distribution
+ OS.mkdir('tmp')
+ OS.chdir('tmp')
+ run('wget -q -O tripe-keys.tar.gz ${repos-url}')
+ run('wget -q -O tripe-keys.sig ${sig-url}')
+ run('tar xfz tripe-keys.tar.gz')
+
+ ## Verify the signature
+ want = C.bytes(r_nonalpha.sub('', conf['hk-master']))
+ got = fingerprint('repos/master.pub', 'tripe-keys-master')
+ if want != got: raise VerifyError
+ run('''catsign -krepos/master.pub verify -avC -ktripe-keys-master
+ -t${sig-fresh} tripe-keys.sig tripe-keys.tar.gz''')
+
+ ## OK: update our copy
+ OS.chdir(cwd)
+ if OS.path.exists('repos'): OS.rename('repos', 'repos.old')
+ OS.rename('tmp/repos', 'repos')
+ rmtree('repos.old')
+
+ finally:
+ OS.chdir(cwd)
+ rmtree('tmp')
+ cmd_rebuild(args)
+
+def cmd_rebuild(args):
+ zap('keyring.pub')
+ for i in OS.listdir('repos'):
+ if i.startswith('peer-') and i.endswith('.pub'):
+ run('key -kkeyring.pub merge %s' % OS.path.join('repos', i))
+
+def cmd_generate(args):
+ tag, = args
+ keyring_pub = 'peer-%s.pub' % tag
+ zap('keyring'); zap(keyring_pub)
+ run('key -kkeyring merge repos/param')
+ run('key -kkeyring add -a${kx} -pparam -e${kx-expire} -t%s tripe-${kx}' %
+ (tag,))
+ run('key -kkeyring extract -f-secret %s %s' % (keyring_pub, tag))
+ print 'Generated %s key = %s' % \
+ (tag,
+ hexhyphens(fingerprint('repos/master.pub', 'tripe-keys-master')))
+
+
+def cmd_clean(args):
+ rmtree('repos')
+ rmtree('tmp')
+ for i in 'master', 'keyring.pub':
+ zap(i)
+ zap('%s.old' % i)
+
+### Main driver
+
+class UsageError (Exception): pass
+
+commands = {'help': (cmd_help, 0, 1, ''),
+ 'setup': (cmd_setup, 0, 0, ''),
+ 'upload': (cmd_upload, 0, 0, ''),
+ 'update': (cmd_update, 0, 0, ''),
+ 'clean': (cmd_clean, 0, 0, ''),
+ 'generate': (cmd_generate, 1, 1, 'TAG'),
+ 'rebuild': (cmd_rebuild, 0, 0, '')}
+
+def init():
+ for f in ['tripe-keys.master', 'tripe-keys.conf']:
+ if OS.path.exists(f):
+ conf_read(f)
+ break
+ conf_defaults()
+def main(argv):
+ try:
+ opts, args = O.getopt(argv[1:], 'hvu',
+ ['help', 'version', 'usage'])
+ except O.GetoptError, exc:
+ moan(exc)
+ usage(SYS.stderr)
+ SYS.exit(1)
+ for o, v in opts:
+ if o in ('-h', '--help'):
+ cmd_help([])
+ SYS.exit(0)
+ elif o in ('-v', '--version'):
+ version(SYS.stdout)
+ SYS.exit(0)
+ elif o in ('-u', '--usage'):
+ usage(SYS.stdout)
+ SYS.exit(0)
+ if len(argv) < 2:
+ cmd_help([])
+ else:
+ c = argv[1]
+ func, min, max, help = commands[c]
+ args = argv[2:]
+ if len(args) < min or (max > 0 and len(args) > max):
+ raise UsageError, (c, help)
+ func(args)
+
+init()
+main(SYS.argv)
--- /dev/null
+# tripe-keys configuration file
+#
+# see tripe-keys.conf(5) for full details
+
+### File locations (required)
+
+# The base URL for the repository files. Include the trailing slash if
+# necessary.
+# base-url = http://some.server.somewhere/blah/
+
+# The local directory name for the repository files. Again, include the
+# trailing slash if necessary.
+# base-dir = /some/directory/blah/
+
+### Crypto parameters
+
+# The key-exchange type. May be `dh' or `ec'.
+# kx = dh
+
+# Key-generation parameters for key exchange group.
+# kx-param = -LS -b2048 -B256
+
+# Expiry time for peer key-exchange keys.
+# kx-expire = now + 1 day
+
+# Symmetric encryption scheme to use.
+# cipher = blowfish-cbc
+
+# Hash function to use. (We derive the MGF and MAC from this.)
+# hash = sha256
+
+# Signature scheme to use for signing/verifying repository archives.
+# sig = dsa
+
+# How recently an archive must have been signed to be valid.
+# sig-fresh = always
+
+# When the signing key expires. We're not good at rolling these over.
+# sig-expire = forever
+
+### Master key hash
+
+# Since the master public key is contained within the repository, we must
+# check its integrity: therefore we record its fingerprint here. This is
+# filled in automatically by `tripe-keys upload'. Leave it as it is.
+hk-master = @HK-MASTER@
typedef struct admin {
struct admin *next, *prev; /* Links to next and previous */
unsigned f; /* Various useful flags */
+ unsigned ref; /* Reference counter */
#ifndef NTRACE
unsigned seq; /* Sequence number for tracing */
#endif
} admin;
#define AF_DEAD 1u /* Destroy this admin block */
-#define AF_LOCK 2u /* Don't destroy it yet */
+#define AF_CLOSE 2u /* Client closed connection */
#define AF_NOTE 4u /* Catch notifications */
#define AF_WARN 8u /* Catch warning messages */
#ifndef NTRACE
#define AF_TRACE 16u /* Catch tracing */
#endif
-#define AF_CLOSE 32u /* Client closed connection */
#ifndef NTRACE
# define AF_ALLMSGS (AF_NOTE | AF_TRACE | AF_WARN)
* Arguments: @peer *p@ = pointer to a peer block
*
* Returns: A pointer to the peer's name.
+ *
+ * Use: Equivalent to @p_spec(p)->name@.
*/
extern const char *p_name(peer */*p*/);
+/* --- @p_spec@ --- *
+ *
+ * Arguments: @peer *p@ = pointer to a peer block
+ *
+ * Returns: Pointer to the peer's specification
+ */
+
+extern const peerspec *p_spec(peer */*p*/);
+
/* --- @p_find@ --- *
*
* Arguments: @const char *name@ = name to look up
--- /dev/null
+#! @PYTHON@
+# -*-python-*-
+
+#----- Dependencies ---------------------------------------------------------
+
+import socket as S
+from sys import argv, exit, stdin, stdout, stderr
+import os as OS
+from os import environ
+import sets as SET
+import getopt as O
+import time as T
+import sre 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
+
+#----- Configuration --------------------------------------------------------
+
+tripedir = "@configdir@"
+socketdir = "@socketdir@"
+PACKAGE = "@PACKAGE@"
+VERSION = "@VERSION@"
+
+debug = False
+
+#----- Utility functions ----------------------------------------------------
+
+## Program name, shorn of extraneous stuff.
+quis = OS.path.basename(argv[0])
+
+def moan(msg):
+ """Report a message to standard error."""
+ stderr.write('%s: %s\n' % (quis, msg))
+
+def die(msg, rc = 1):
+ """Report a message to standard error and exit."""
+ moan(msg)
+ exit(rc)
+
+rx_space = RX.compile(r'\s+')
+rx_ordinary = RX.compile(r'[^\\\'\"\s]+')
+rx_weird = RX.compile(r'([\\\'])')
+rx_time = RX.compile(r'^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)$')
+rx_num = RX.compile(r'^[-+]?\d+$')
+
+c_red = GDK.color_parse('red')
+
+def getword(s):
+ """Pull a word from the front of S, handling quoting according to the
+ tripe-admin(5) rules. Returns the word and the rest of S, or (None, None)
+ if there are no more words left."""
+ i = 0
+ m = rx_space.match(s, i)
+ if m: i = m.end()
+ r = ''
+ q = None
+ if i >= len(s):
+ return None, None
+ while i < len(s) and (q or not s[i].isspace()):
+ m = rx_ordinary.match(s, i)
+ if m:
+ r += m.group()
+ i = m.end()
+ elif s[i] == '\\':
+ r += s[i + 1]
+ i += 2
+ elif s[i] == q:
+ q = None
+ i += 1
+ elif not q and s[i] == '`' or s[i] == "'":
+ q = "'"
+ i += 1
+ elif not q and s[i] == '"':
+ q = '"'
+ i += 1
+ else:
+ r += s[i]
+ i += 1
+ if q:
+ raise SyntaxError, 'missing close quote'
+ m = rx_space.match(s, i)
+ if m: i = m.end()
+ return r, s[i:]
+
+def quotify(s):
+ """Quote S according to the tripe-admin(5) rules."""
+ m = rx_ordinary.match(s)
+ if m and m.end() == len(s):
+ return s
+ else:
+ return "'" + rx_weird.sub(r'\\\1', s) + "'"
+
+#----- Random bits of infrastructure ----------------------------------------
+
+class struct (object):
+ """Simple object which stores attributes and has a sensible construction
+ syntax."""
+ def __init__(me, **kw):
+ me.__dict__.update(kw)
+
+class peerinfo (struct): pass
+class pingstate (struct): pass
+
+def invoker(func):
+ """Return a function which throws away its arguments and calls FUNC. (If
+ for loops worked by binding rather than assignment then we wouldn't need
+ this kludge."""
+ return lambda *hunoz, **hukairz: func()
+
+class HookList (object):
+ """I maintain a list of functions, and provide the ability to call them
+ when something interesting happens. The functions are called in the order
+ they were added to the list, with all the arguments. If a function returns
+ a non-None result, no further functions are called."""
+ def __init__(me):
+ me.list = []
+ def add(me, func, obj):
+ me.list.append((obj, func))
+ def prune(me, obj):
+ new = []
+ for o, f in me.list:
+ if o is not obj:
+ new.append((o, f))
+ me.list = new
+ def run(me, *args, **kw):
+ for o, hook in me.list:
+ rc = hook(*args, **kw)
+ if rc is not None: return rc
+ return None
+
+class HookClient (object):
+ def __init__(me):
+ me.hooks = SET.Set()
+ def hook(me, hk, func):
+ hk.add(func, me)
+ me.hooks.add(hk)
+ def unhook(me, hk):
+ hk.prune(me)
+ me.hooks.discard(hk)
+ def unhookall(me):
+ for hk in me.hooks:
+ hk.prune(me)
+ me.hooks.clear()
+ ##def __del__(me):
+ ## print '%s dying' % me
+
+#----- Connections and commands ---------------------------------------------
+
+class ConnException (Exception):
+ """Some sort of problem occurred while communicating with the tripe
+ server."""
+ pass
+
+class Error (ConnException):
+ """A command caused the server to issue a FAIL message."""
+ pass
+
+class ConnectionFailed (ConnException):
+ """The connection failed while communicating with the server."""
+
+jobid_seq = 0
+def jobid():
+ """Return a job tag. Used for background commands."""
+ global jobid_seq
+ jobid_seq += 1
+ return 'bg-%d' % jobid_seq
+
+class BackgroundCommand (HookClient):
+ def __init__(me, conn, cmd):
+ HookClient.__init__(me)
+ me.conn = conn
+ me.tag = None
+ me.cmd = cmd
+ me.donehook = HookList()
+ me.losthook = HookList()
+ me.info = []
+ me.submit()
+ me.hook(me.conn.disconnecthook, me.lost)
+ def submit(me):
+ me.conn.bgcommand(me.cmd, me)
+ def lost(me):
+ me.losthook.run()
+ me.unhookall()
+ def fail(me, msg):
+ me.conn.error("Unexpected error from server command `%s': %s" %
+ (me.cmd % msg))
+ me.unhookall()
+ def ok(me):
+ me.donehook.run(me.info)
+ me.unhookall()
+
+class SimpleBackgroundCommand (BackgroundCommand):
+ def submit(me):
+ try:
+ BackgroundCommand.submit(me)
+ except ConnectionFailed, err:
+ me.conn.error('Unexpected error communicating with server: %s' % msg)
+ raise
+
+class Connection (HookClient):
+
+ """I represent a connection to the TrIPE server. I provide facilities for
+ sending commands and receiving replies. The connection is notional: the
+ underlying socket connection can come and go under our feet.
+
+ Useful attributes:
+ connectedp: whether the connection is active
+ connecthook: called when we have connected
+ disconnecthook: called if we have disconnected
+ notehook: called with asynchronous notifications
+ errorhook: called if there was a command error"""
+
+ def __init__(me, sockname):
+ """Make a new connection to the server listening to SOCKNAME. In fact,
+ we're initially disconnected, to allow the caller to get his life in
+ order before opening the floodgates."""
+ HookClient.__init__(me)
+ me.sockname = sockname
+ me.sock = None
+ me.connectedp = False
+ me.connecthook = HookList()
+ me.disconnecthook = HookList()
+ me.errorhook = HookList()
+ me.inbuf = ''
+ me.info = []
+ me.waitingp = False
+ me.bgcmd = None
+ me.bgmap = {}
+ def connect(me):
+ "Connect to the server. Runs connecthook if it works."""
+ if me.sock: return
+ sock = S.socket(S.AF_UNIX, S.SOCK_STREAM)
+ try:
+ sock.connect(me.sockname)
+ except S.error, err:
+ me.error('error opening connection: %s' % err[1])
+ me.disconnecthook.run()
+ return
+ sock.setblocking(0)
+ me.socketwatch = GO.io_add_watch(sock, GO.IO_IN, me.ready)
+ me.sock = sock
+ me.connectedp = True
+ me.connecthook.run()
+ def disconnect(me):
+ "Disconnects from the server. Runs disconnecthook."
+ if not me.sock: return
+ GO.source_remove(me.socketwatch)
+ me.sock.close()
+ me.sock = None
+ me.connectedp = False
+ me.disconnecthook.run()
+ def error(me, msg):
+ """Reports an error on the connection."""
+ me.errorhook.run(msg)
+
+ def bgcommand(me, cmd, bg):
+ """Sends a background command and feeds it properly."""
+ try:
+ me.bgcmd = bg
+ err = me.docommand(cmd)
+ if err:
+ bg.fail(err)
+ finally:
+ me.bgcmd = None
+ def command(me, cmd):
+ """Sends a command to the server. Returns a list of INFO responses. Do
+ not use this for backgrounded commands: create a BackgroundCommand
+ instead. Raises apprpopriate exceptions on error, but doesn't send
+ report them to the errorhook."""
+ err = me.docommand(cmd)
+ if err:
+ raise Error, err
+ return me.info
+ def docommand(me, cmd):
+ if not me.sock:
+ raise ConnException, 'not connected'
+ if debug: print ">>> %s" % cmd
+ me.sock.sendall(cmd + '\n')
+ me.waitingp = True
+ me.info = []
+ try:
+ me.sock.setblocking(1)
+ while True:
+ rc, err = me.collect()
+ if rc: break
+ finally:
+ me.waitingp = False
+ me.sock.setblocking(0)
+ if len(me.inbuf) > 0:
+ GO.idle_add(lambda: me.flushbuf() and False)
+ return err
+ def simplecmd(me, cmd):
+ """Like command(), but reports errors via the errorhook as well as
+ raising exceptions."""
+ try:
+ i = me.command(cmd)
+ except Error, msg:
+ me.error("Unexpected error from server command `%s': %s" % (cmd, msg))
+ raise
+ except ConnectionFailed, msg:
+ me.error("Unexpected error communicating with server: %s" % msg);
+ raise
+ return i
+ def ready(me, sock, condition):
+ try:
+ me.collect()
+ except ConnException, msg:
+ me.error("Error watching server connection: %s" % msg)
+ if me.sock:
+ me.disconnect()
+ me.connect()
+ return True
+ def collect(me):
+ data = me.sock.recv(16384)
+ if data == '':
+ me.disconnect()
+ raise ConnectionFailed, 'server disconnected'
+ me.inbuf += data
+ return me.flushbuf()
+ def flushbuf(me):
+ while True:
+ nl = me.inbuf.find('\n')
+ if nl < 0: break
+ line = me.inbuf[:nl]
+ if debug: print "<<< %s" % line
+ me.inbuf = me.inbuf[nl + 1:]
+ tag, line = getword(line)
+ rc, err = me.parseline(tag, line)
+ if rc: return rc, err
+ return False, None
+ def parseline(me, code, line):
+ if code == 'BGDETACH':
+ if not me.bgcmd:
+ raise ConnectionFailed, 'unexpected detach'
+ me.bgcmd.tag = line
+ me.bgmap[line] = me.bgcmd
+ me.waitingp = False
+ me.bgcmd = None
+ return True, None
+ elif code == 'BGINFO':
+ tag, line = getword(line)
+ me.bgmap[tag].info.append(line)
+ return False, None
+ elif code == 'BGFAIL':
+ tag, line = getword(line)
+ me.bgmap[tag].fail(line)
+ del me.bgmap[tag]
+ return False, None
+ elif code == 'BGOK':
+ tag, line = getword(line)
+ me.bgmap[tag].ok()
+ del me.bgmap[tag]
+ return False, None
+ elif code == 'INFO':
+ if not me.waitingp or me.bgcmd:
+ raise ConnectionFailed, 'unexpected INFO response'
+ me.info.append(line)
+ return False, None
+ elif code == 'OK':
+ if not me.waitingp or me.bgcmd:
+ raise ConnectionFailed, 'unexpected OK response'
+ return True, None
+ elif code == 'FAIL':
+ if not me.waitingp:
+ raise ConnectionFailed, 'unexpected FAIL response'
+ return True, line
+ else:
+ raise ConnectionFailed, 'unknown response code `%s' % code
+
+class Monitor (Connection):
+ """I monitor a TrIPE server, noticing when it changes state and keeping
+ track of its peers. I also provide facilities for sending the server
+ commands and collecting the answers.
+
+ Useful attributes:
+ addpeerhook: called with a new Peer when the server adds one
+ delpeerhook: called with a Peer when the server kills one
+ tracehook: called with a trace message
+ warnhook: called with a warning message
+ peers: mapping from names to Peer objects"""
+ def __init__(me, sockname):
+ """Initializes the monitor."""
+ Connection.__init__(me, sockname)
+ me.addpeerhook = HookList()
+ me.delpeerhook = HookList()
+ me.tracehook = HookList()
+ me.warnhook = HookList()
+ me.notehook = HookList()
+ me.hook(me.connecthook, me.connected)
+ me.delay = []
+ me.peers = {}
+ def addpeer(me, peer):
+ if peer not in me.peers:
+ p = Peer(me, peer)
+ me.peers[peer] = p
+ me.addpeerhook.run(p)
+ def delpeer(me, peer):
+ if peer in me.peers:
+ p = me.peers[peer]
+ me.delpeerhook.run(p)
+ p.dead()
+ del me.peers[peer]
+ def updatelist(me, peers):
+ newmap = {}
+ for p in peers:
+ newmap[p] = True
+ if p not in me.peers:
+ me.addpeer(p)
+ oldpeers = me.peers.copy()
+ for p in oldpeers:
+ if p not in newmap:
+ me.delpeer(p)
+ def connected(me):
+ try:
+ me.simplecmd('WATCH -A+wnt')
+ me.updatelist([s.strip() for s in me.simplecmd('LIST')])
+ except ConnException:
+ me.disconnect()
+ return
+ def parseline(me, code, line):
+ ## Delay async messages until the current command is done. Otherwise the
+ ## handler for the async message might send another command before this
+ ## one's complete, and the whole edifice turns to jelly.
+ ##
+ ## No, this isn't the server's fault. If we rely on the server to delay
+ ## notifications then there's a race between when we send a command and
+ ## when the server gets it.
+ if me.waitingp and code in ('TRACE', 'WARN', 'NOTE'):
+ if len(me.delay) == 0: GO.idle_add(me.flushdelay)
+ me.delay.append((code, line))
+ elif code == 'TRACE':
+ me.tracehook.run(line)
+ elif code == 'WARN':
+ me.warnhook.run(line)
+ elif code == 'NOTE':
+ note, line = getword(line)
+ me.notehook.run(note, line)
+ if note == 'ADD':
+ me.addpeer(getword(line)[0])
+ elif note == 'KILL':
+ me.delpeer(line)
+ else:
+ ## Well, I asked for it.
+ pass
+ else:
+ return Connection.parseline(me, code, line)
+ return False, None
+ def flushdelay(me):
+ delay = me.delay
+ me.delay = []
+ for tag, line in delay:
+ me.parseline(tag, line)
+ return False
+
+def parseinfo(info):
+ """Parse key=value output into a dictionary."""
+ d = {}
+ for i in info:
+ for w in i.split(' '):
+ q = w.index('=')
+ d[w[:q]] = w[q + 1:]
+ return d
+
+class Peer (object):
+ """I represent a TrIPE peer. Useful attributes are:
+
+ name: peer's name
+ addr: human-friendly representation of the peer's address
+ ifname: interface associated with the peer
+ alivep: true if the peer hasn't been killed
+ deadhook: called with no arguments when the peer is killed"""
+ def __init__(me, monitor, name):
+ me.mon = monitor
+ me.name = name
+ addr = me.mon.simplecmd('ADDR %s' % name)[0].split(' ')
+ if addr[0] == 'INET':
+ ipaddr, port = addr[1:]
+ 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)
+ else:
+ me.addr = ' '.join(addr)
+ me.ifname = me.mon.simplecmd('IFNAME %s' % me.name)[0]
+ me.__dict__.update(parseinfo(me.mon.simplecmd('PEERINFO %s' % me.name)))
+ me.deadhook = HookList()
+ me.alivep = True
+ def dead(me):
+ me.alivep = False
+ me.deadhook.run()
+
+#----- Window management cruft ----------------------------------------------
+
+class MyWindowMixin (G.Window, HookClient):
+ """Mixin for windows which call a closehook when they're destroyed."""
+ def mywininit(me):
+ me.closehook = HookList()
+ HookClient.__init__(me)
+ me.connect('destroy', invoker(me.close))
+ def close(me):
+ me.closehook.run()
+ me.destroy()
+ me.unhookall()
+class MyWindow (MyWindowMixin):
+ """A window which calls a closehook when it's destroyed."""
+ def __init__(me, kind = G.WINDOW_TOPLEVEL):
+ G.Window.__init__(me, kind)
+ me.mywininit()
+class MyDialog (G.Dialog, MyWindowMixin, HookClient):
+ """A dialogue box with a closehook and sensible button binding."""
+ def __init__(me, title = None, flags = 0, buttons = []):
+ """The buttons are a list of (STOCKID, THUNK) pairs: call the appropriate
+ THUNK when the button is pressed. The others are just like GTK's Dialog
+ class."""
+ i = 0
+ br = []
+ me.rmap = []
+ for b, f in buttons:
+ br.append(b)
+ br.append(i)
+ me.rmap.append(f)
+ i += 1
+ G.Dialog.__init__(me, title, None, flags, tuple(br))
+ HookClient.__init__(me)
+ me.mywininit()
+ me.set_default_response(i - 1)
+ me.connect('response', me.respond)
+ def respond(me, hunoz, rid, *hukairz):
+ if rid >= 0: me.rmap[rid]()
+
+class WindowSlot (HookClient):
+ """A place to store a window. If the window is destroyed, remember this;
+ when we come to open the window, raise it if it already exists; otherwise
+ make a new one."""
+ def __init__(me, createfunc):
+ """Constructor: CREATEFUNC must return a new Window which supports the
+ closehook protocol."""
+ HookClient.__init__(me)
+ me.createfunc = createfunc
+ me.window = None
+ def open(me):
+ """Opens the window, creating it if necessary."""
+ if me.window:
+ me.window.window.raise_()
+ else:
+ me.window = me.createfunc()
+ me.hook(me.window.closehook, me.closed)
+ def closed(me):
+ me.unhook(me.window.closehook)
+ me.window = None
+
+class ValidationError (Exception):
+ """Raised by ValidatingEntry.get_text() if the text isn't valid."""
+ pass
+class ValidatingEntry (G.Entry):
+ """Like an Entry, but makes the text go red if the contents are invalid.
+ If get_text is called, and the text is invalid, ValidationError is
+ raised."""
+ def __init__(me, valid, text = '', size = -1, *arg, **kw):
+ """Make an Entry. VALID is a regular expression or a predicate on
+ strings. TEXT is the default text to insert. SIZE is the size of the
+ box to set, in characters (ish). Other arguments are passed to Entry."""
+ G.Entry.__init__(me, *arg, **kw)
+ me.connect("changed", me.check)
+ if callable(valid):
+ me.validate = valid
+ else:
+ me.validate = RX.compile(valid).match
+ me.ensure_style()
+ me.c_ok = me.get_style().text[G.STATE_NORMAL]
+ me.c_bad = c_red
+ if size != -1: me.set_width_chars(size)
+ me.set_activates_default(True)
+ me.set_text(text)
+ me.check()
+ def check(me, *hunoz):
+ if me.validate(G.Entry.get_text(me)):
+ me.validp = True
+ me.modify_text(G.STATE_NORMAL, me.c_ok)
+ else:
+ me.validp = False
+ me.modify_text(G.STATE_NORMAL, me.c_bad)
+ def get_text(me):
+ if not me.validp:
+ raise ValidationError
+ return G.Entry.get_text(me)
+
+def numericvalidate(min = None, max = None):
+ """Validation function for numbers. Entry must consist of an optional sign
+ followed by digits, and the resulting integer must be within the given
+ bounds."""
+ return lambda x: (rx_num.match(x) and
+ (min is None or long(x) >= min) and
+ (max is None or long(x) <= max))
+
+#----- 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.
+
+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.
+
+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."""
+
+class AboutBox (G.AboutDialog, MyWindowMixin):
+ """The program `About' box."""
+ def __init__(me):
+ G.AboutDialog.__init__(me)
+ me.mywininit()
+ me.set_name('TrIPEmon')
+ me.set_version(VERSION)
+ me.set_license(GPL)
+ me.set_authors(['Mark Wooding'])
+ me.connect('unmap', invoker(me.close))
+ me.show()
+aboutbox = WindowSlot(AboutBox)
+
+def moanbox(msg):
+ """Report an error message in a window."""
+ d = G.Dialog('Error from %s' % quis,
+ flags = G.DIALOG_MODAL,
+ buttons = ((G.STOCK_OK, G.RESPONSE_NONE)))
+ label = G.Label(msg)
+ label.set_padding(20, 20)
+ d.vbox.pack_start(label)
+ label.show()
+ d.run()
+ d.destroy()
+
+#----- Logging windows ------------------------------------------------------
+
+class LogModel (G.ListStore):
+ """A simple list of log messages."""
+ def __init__(me, columns):
+ """Call with a list of column names. All must be strings. We add a time
+ column to the left."""
+ me.cols = ('Time',) + columns
+ G.ListStore.__init__(me, *((GO.TYPE_STRING,) * len(me.cols)))
+ def add(me, *entries):
+ """Adds a new log message, with a timestamp."""
+ now = T.strftime('%Y-%m-%d %H:%M:%S')
+ me.append((now,) + entries)
+
+class TraceLogModel (LogModel):
+ """Log model for trace messages."""
+ def __init__(me):
+ LogModel.__init__(me, ('Message',))
+ def notify(me, line):
+ """Call with a new trace message."""
+ me.add(line)
+
+class WarningLogModel (LogModel):
+ """Log model for warnings. We split the category out into a separate
+ column."""
+ def __init__(me):
+ LogModel.__init__(me, ('Category', 'Message'))
+ def notify(me, line):
+ """Call with a new warning message."""
+ me.add(*getword(line))
+
+class LogViewer (MyWindow):
+ """Log viewer window. Nothing very exciting."""
+ def __init__(me, model):
+ MyWindow.__init__(me)
+ me.model = model
+ scr = G.ScrolledWindow()
+ scr.set_policy(G.POLICY_AUTOMATIC, G.POLICY_AUTOMATIC)
+ me.list = G.TreeView(me.model)
+ me.closehook = HookList()
+ i = 0
+ for c in me.model.cols:
+ me.list.append_column(G.TreeViewColumn(c,
+ G.CellRendererText(),
+ text = i))
+ i += 1
+ me.set_default_size(440, 256)
+ scr.add(me.list)
+ me.add(scr)
+ me.show_all()
+
+def makeactiongroup(name, acts):
+ """Creates an ActionGroup called NAME. ACTS is a list of tuples
+ containing:
+ ACT: an action name
+ LABEL: the label string for the action
+ ACCEL: accelerator string, or None
+ FUNC: thunk to call when the action is invoked"""
+ actgroup = G.ActionGroup(name)
+ for act, label, accel, func in acts:
+ a = G.Action(act, label, None, None)
+ if func: a.connect('activate', invoker(func))
+ actgroup.add_action_with_accel(a, accel)
+ return actgroup
+
+class TraceOptions (MyDialog):
+ """Tracing options window."""
+ def __init__(me, monitor):
+ MyDialog.__init__(me, title = 'Tracing options',
+ buttons = [(G.STOCK_CLOSE, me.destroy),
+ (G.STOCK_OK, me.ok)])
+ me.mon = monitor
+ me.opts = []
+ for o in me.mon.simplecmd('TRACE'):
+ char = o[0]
+ onp = o[1]
+ text = o[3].upper() + o[4:]
+ if char.isupper(): continue
+ ticky = G.CheckButton(text)
+ ticky.set_active(onp != ' ')
+ me.vbox.pack_start(ticky)
+ me.opts.append((char, ticky))
+ me.show_all()
+ def ok(me):
+ on = []
+ off = []
+ for char, ticky in me.opts:
+ if ticky.get_active():
+ on.append(char)
+ else:
+ off.append(char)
+ setting = ''.join(on) + '-' + ''.join(off)
+ me.mon.simplecmd('TRACE %s' % setting)
+ me.destroy()
+
+def unimplemented(*hunoz):
+ """Indicator of laziness."""
+ moanbox("I've not written that bit yet.")
+
+class GridPacker (G.Table):
+ """Like a Table, but with more state: makes filling in the widgets
+ easier."""
+ def __init__(me):
+ G.Table.__init__(me)
+ me.row = 0
+ me.col = 0
+ me.rows = 1
+ me.cols = 1
+ me.set_border_width(4)
+ me.set_col_spacings(4)
+ me.set_row_spacings(4)
+ def pack(me, w, width = 1, newlinep = False,
+ xopt = G.EXPAND | G.FILL | G.SHRINK, yopt = 0,
+ xpad = 0, ypad = 0):
+ """Packs a new widget. W is the widget to add. XOPY, YOPT, XPAD and
+ YPAD are as for Table. WIDTH is how many cells to take up horizontally.
+ NEWLINEP is whether to start a new line for this widget. Returns W."""
+ if newlinep:
+ me.row += 1
+ me.col = 0
+ bot = me.row + 1
+ right = me.col + width
+ if bot > me.rows or right > me.cols:
+ if bot > me.rows: me.rows = bot
+ if right > me.cols: me.cols = right
+ me.resize(me.rows, me.cols)
+ me.attach(w, me.col, me.col + width, me.row, me.row + 1,
+ xopt, yopt, xpad, ypad)
+ me.col += width
+ return w
+ def labelled(me, lab, w, newlinep = False, **kw):
+ """Packs a labelled widget. Other arguments are as for pack. Returns
+ W."""
+ label = G.Label(lab)
+ label.set_alignment(1.0, 0)
+ me.pack(label, newlinep = newlinep, xopt = G.FILL)
+ me.pack(w, **kw)
+ return w
+ def info(me, label, text = None, len = 18, **kw):
+ e = G.Entry()
+ if text is not None: e.set_text(text)
+ e.set_width_chars(len)
+ e.set_editable(False)
+ me.labelled(label, e, **kw)
+ return e
+
+def xlate_time(t):
+ """Translate a time in tripe's stats format to something a human might
+ actually want to read."""
+ if t == 'NEVER': return '(never)'
+ Y, M, D, h, m, s = map(int, rx_time.match(t).group(1, 2, 3, 4, 5, 6))
+ return '%04d:%02d:%02d %02d:%02d:%02d' % (Y, M, D, h, m, s)
+def xlate_bytes(b):
+ """Translate a number of bytes into something a human might want to read."""
+ suff = 'B'
+ b = int(b)
+ for s in 'KMG':
+ if b < 4096: break
+ b /= 1024
+ suff = s
+ return '%d %s' % (b, suff)
+
+## How to translate peer stats. Maps the stat name to a translation
+## function.
+statsxlate = \
+ [('start-time', xlate_time),
+ ('last-packet-time', xlate_time),
+ ('last-keyexch-time', xlate_time),
+ ('bytes-in', xlate_bytes),
+ ('bytes-out', xlate_bytes),
+ ('keyexch-bytes-in', xlate_bytes),
+ ('keyexch-bytes-out', xlate_bytes),
+ ('ip-bytes-in', xlate_bytes),
+ ('ip-bytes-out', xlate_bytes)]
+
+## 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.
+statslayout = \
+ [('Start time', '%(start-time)s'),
+ ('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)'),
+ ('Key-exchange in/out',
+ '%(keyexch-packets-in)s (%(keyexch-bytes-in)s) / %(keyexch-packets-out)s (%(keyexch-bytes-out)s)'),
+ ('IP in/out',
+ '%(ip-packets-in)s (%(ip-bytes-in)s) / %(ip-packets-in)s (%(ip-bytes-in)s)'),
+ ('Rejected packets', '%(rejected-packets)s')]
+
+class PeerWindow (MyWindow):
+ """Show information about a peer."""
+ def __init__(me, monitor, peer):
+ MyWindow.__init__(me)
+ me.set_title('TrIPE statistics: %s' % peer.name)
+ me.mon = monitor
+ me.peer = peer
+ table = GridPacker()
+ me.add(table)
+ me.e = {}
+ def add(label, text = None):
+ me.e[label] = table.info(label, text, len = 42, newlinep = True)
+ add('Peer name', peer.name)
+ add('Tunnel', peer.tunnel)
+ add('Interface', peer.ifname)
+ add('Keepalives',
+ (peer.keepalive == '0' and 'never') or '%s s' % peer.keepalive)
+ add('Address', peer.addr)
+ add('Transport pings')
+ add('Encrypted pings')
+ for label, format in statslayout: add(label)
+ me.timeout = None
+ me.hook(me.mon.connecthook, me.tryupdate)
+ me.hook(me.mon.disconnecthook, me.stopupdate)
+ me.hook(me.closehook, me.stopupdate)
+ me.hook(me.peer.deadhook, me.dead)
+ me.hook(me.peer.pinghook, me.ping)
+ me.tryupdate()
+ me.ping()
+ me.show_all()
+ def update(me):
+ if not me.peer.alivep or not me.mon.connectedp: return False
+ stat = parseinfo(me.mon.simplecmd('STATS %s' % me.peer.name))
+ for s, trans in statsxlate:
+ stat[s] = trans(stat[s])
+ for label, format in statslayout:
+ me.e[label].set_text(format % stat)
+ return True
+ def tryupdate(me):
+ if me.timeout is None and me.update():
+ me.timeout = GO.timeout_add(1000, me.update)
+ def stopupdate(me):
+ if me.timeout is not None:
+ GO.source_remove(me.timeout)
+ me.timeout = None
+ def dead(me):
+ me.set_title('TrIPE statistics: %s [defunct]' % me.peer.name)
+ me.e['Peer name'].set_text('%s [defunct]' % me.peer.name)
+ me.stopupdate()
+ def ping(me):
+ for ping in me.peer.ping, me.peer.eping:
+ s = '%d/%d' % (ping.ngood, ping.n)
+ if ping.ngood:
+ s += '; %.2f ms (last %.1f ms)' % (ping.ttot/ping.ngood, ping.tlast);
+ me.e[ping.cmd].set_text(s)
+
+class AddPeerCommand (SimpleBackgroundCommand):
+ def __init__(me, conn, dlg, name, addr, port,
+ keepalive = None, tunnel = None):
+ me.name = name
+ me.addr = addr
+ me.port = port
+ me.keepalive = keepalive
+ me.tunnel = tunnel
+ cmd = StringIO()
+ cmd.write('ADD %s' % name)
+ cmd.write(' -background %s' % jobid())
+ if keepalive is not None: cmd.write(' -keepalive %s' % keepalive)
+ if tunnel is not None: cmd.write(' -tunnel %s' % tunnel)
+ cmd.write(' INET %s %s' % (addr, port))
+ SimpleBackgroundCommand.__init__(me, conn, cmd.getvalue())
+ me.hook(me.donehook, invoker(dlg.destroy))
+ def fail(me, err):
+ token, msg = getword(str(err))
+ if token in ('resolve-error', 'resolver-timeout'):
+ moanbox("Unable to resolve hostname `%s'" % me.addr)
+ elif token == 'peer-create-fail':
+ moanbox("Couldn't create new peer `%s'" % me.name)
+ elif token == 'peer-exists':
+ moanbox("Peer `%s' already exists" % me.name)
+ else:
+ moanbox("Unexpected error from server command `ADD': %s" % err)
+
+class AddPeerDialog (MyDialog):
+ def __init__(me, monitor):
+ MyDialog.__init__(me, 'Add peer',
+ buttons = [(G.STOCK_CANCEL, me.destroy),
+ (G.STOCK_OK, me.ok)])
+ me.mon = monitor
+ table = GridPacker()
+ me.vbox.pack_start(table)
+ me.e_name = table.labelled('Name',
+ ValidatingEntry(r'^[^\s.:]+$', '', 16),
+ width = 3)
+ me.e_addr = table.labelled('Address',
+ ValidatingEntry(r'^[a-zA-Z0-9.-]+$', '', 24),
+ newlinep = True)
+ me.e_port = table.labelled('Port',
+ ValidatingEntry(numericvalidate(0, 65535),
+ '22003',
+ 5))
+ me.c_keepalive = G.CheckButton('Keepalives')
+ me.l_tunnel = table.labelled('Tunnel',
+ G.combo_box_new_text(),
+ newlinep = True, width = 3)
+ me.tuns = me.mon.simplecmd('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)
+ me.show_all()
+ def ok(me):
+ 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]
+ AddPeerCommand(me.mon, me,
+ me.e_name.get_text(),
+ me.e_addr.get_text(),
+ me.e_port.get_text(),
+ keepalive = ka,
+ tunnel = tun)
+ except ValidationError:
+ GDK.beep()
+ return
+
+class ServInfo (MyWindow):
+ def __init__(me, monitor):
+ MyWindow.__init__(me)
+ me.set_title('TrIPE server info')
+ me.mon = monitor
+ me.table = GridPacker()
+ me.add(me.table)
+ me.e = {}
+ def add(label, tag, text = None, **kw):
+ me.e[tag] = me.table.info(label, text, **kw)
+ add('Implementation', 'implementation')
+ add('Version', 'version', newlinep = True)
+ me.update()
+ me.hook(me.mon.connecthook, me.update)
+ me.show_all()
+ def update(me):
+ info = parseinfo(me.mon.simplecmd('SERVINFO'))
+ for i in me.e:
+ me.e[i].set_text(info[i])
+
+class PingCommand (SimpleBackgroundCommand):
+ def __init__(me, conn, cmd, peer, func):
+ me.peer = peer
+ me.func = func
+ SimpleBackgroundCommand.__init__ \
+ (me, conn, '%s -background %s %s' % (cmd, jobid(), peer.name))
+ def ok(me):
+ tok, rest = getword(me.info[0])
+ if tok == 'ping-ok':
+ me.func(me.peer, float(rest))
+ else:
+ me.func(me.peer, None)
+ me.unhookall()
+ def fail(me, err): me.unhookall()
+ def lost(me): me.unhookall()
+
+class MonitorWindow (MyWindow):
+
+ def __init__(me, monitor):
+ MyWindow.__init__(me)
+ me.set_title('TrIPE monitor')
+ me.mon = monitor
+ me.hook(me.mon.errorhook, me.report)
+ me.warnings = WarningLogModel()
+ me.hook(me.mon.warnhook, me.warnings.notify)
+ me.trace = TraceLogModel()
+ me.hook(me.mon.tracehook, me.trace.notify)
+
+ me.warnview = WindowSlot(lambda: LogViewer(me.warnings))
+ me.traceview = WindowSlot(lambda: LogViewer(me.trace))
+ me.traceopts = WindowSlot(lambda: TraceOptions(me.mon))
+ me.addpeerwin = WindowSlot(lambda: AddPeerDialog(me.mon))
+ me.servinfo = WindowSlot(lambda: ServInfo(me.mon))
+
+ vbox = G.VBox()
+ me.add(vbox)
+
+ me.ui = G.UIManager()
+ actgroup = makeactiongroup('monitor',
+ [('file-menu', '_File', None, None),
+ ('connect', '_Connect', '<Alt>C', me.mon.connect),
+ ('disconnect', '_Disconnect', '<Alt>D', me.mon.disconnect),
+ ('quit', '_Quit', '<Alt>Q', me.close),
+ ('server-menu', '_Server', None, None),
+ ('daemon', 'Run in _background', None,
+ lambda: me.mon.simplecmd('DAEMON')),
+ ('server-version', 'Server version', None, me.servinfo.open),
+ ('server-quit', 'Terminate server', None,
+ lambda: me.mon.simplecmd('QUIT')),
+ ('logs-menu', '_Logs', None, None),
+ ('show-warnings', 'Show _warnings', '<Alt>W', me.warnview.open),
+ ('show-trace', 'Show _trace', '<Alt>T', me.traceview.open),
+ ('trace-options', 'Trace _options...', None, me.traceopts.open),
+ ('help-menu', '_Help', None, None),
+ ('about', '_About tripemon...', None, aboutbox.open),
+ ('add-peer', '_Add peer...', '<Alt>A', me.addpeerwin.open),
+ ('kill-peer', '_Kill peer', None, me.killpeer),
+ ('force-kx', 'Force key e_xchange', None, me.forcekx)])
+ uidef = '''
+ <ui>
+ <menubar>
+ <menu action="file-menu">
+ <menuitem action="quit"/>
+ </menu>
+ <menu action="server-menu">
+ <menuitem action="connect"/>
+ <menuitem action="disconnect"/>
+ <separator/>
+ <menuitem action="add-peer"/>
+ <menuitem action="daemon"/>
+ <menuitem action="server-version"/>
+ <separator/>
+ <menuitem action="server-quit"/>
+ </menu>
+ <menu action="logs-menu">
+ <menuitem action="show-warnings"/>
+ <menuitem action="show-trace"/>
+ <menuitem action="trace-options"/>
+ </menu>
+ <menu action="help-menu">
+ <menuitem action="about"/>
+ </menu>
+ </menubar>
+ <popup name="peer-popup">
+ <menuitem action="add-peer"/>
+ <menuitem action="kill-peer"/>
+ <menuitem action="force-kx"/>
+ </popup>
+ </ui>
+ '''
+ me.ui.insert_action_group(actgroup, 0)
+ me.ui.add_ui_from_string(uidef)
+ vbox.pack_start(me.ui.get_widget('/menubar'), expand = False)
+ me.add_accel_group(me.ui.get_accel_group())
+ me.status = G.Statusbar()
+
+ me.listmodel = G.ListStore(*(GO.TYPE_STRING,) * 6)
+ me.listmodel.set_sort_column_id(0, G.SORT_ASCENDING)
+ me.hook(me.mon.addpeerhook, me.addpeer)
+ me.hook(me.mon.delpeerhook, me.delpeer)
+
+ scr = G.ScrolledWindow()
+ scr.set_policy(G.POLICY_AUTOMATIC, G.POLICY_AUTOMATIC)
+ me.list = G.TreeView(me.listmodel)
+ me.list.append_column(G.TreeViewColumn('Peer name',
+ G.CellRendererText(),
+ text = 0))
+ me.list.append_column(G.TreeViewColumn('Address',
+ G.CellRendererText(),
+ text = 1))
+ me.list.append_column(G.TreeViewColumn('T-ping',
+ G.CellRendererText(),
+ text = 2,
+ foreground = 3))
+ me.list.append_column(G.TreeViewColumn('E-ping',
+ G.CellRendererText(),
+ text = 4,
+ foreground = 5))
+ me.list.get_column(1).set_expand(True)
+ me.list.connect('row-activated', me.activate)
+ me.list.connect('button-press-event', me.buttonpress)
+ 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(me.status, expand = False)
+ me.hook(me.mon.connecthook, me.connected)
+ me.hook(me.mon.disconnecthook, me.disconnected)
+ me.hook(me.mon.notehook, me.notify)
+ me.pinger = None
+ me.set_default_size(420, 180)
+ me.mon.connect()
+ me.show_all()
+
+ def addpeer(me, peer):
+ peer.i = me.listmodel.append([peer.name, peer.addr,
+ '???', 'green', '???', 'green'])
+ peer.win = WindowSlot(lambda: PeerWindow(me.mon, peer))
+ peer.pinghook = HookList()
+ peer.ping = pingstate(n = 0, ngood = 0, nmiss = 0, nmissrun = 0,
+ tlast = 0, ttot = 0,
+ tcol = 2, ccol = 3, cmd = 'Transport pings')
+ peer.eping = pingstate(n = 0, ngood = 0, nmiss = 0, nmissrun = 0,
+ tlast = 0, ttot = 0,
+ tcol = 4, ccol = 5, cmd = 'Encrypted pings')
+ def delpeer(me, peer):
+ me.listmodel.remove(peer.i)
+ def path_peer(me, path):
+ return me.mon.peers[me.listmodel[path][0]]
+
+ def activate(me, l, path, col):
+ peer = me.path_peer(path)
+ peer.win.open()
+ def buttonpress(me, l, ev):
+ if ev.button == 3:
+ r = me.list.get_path_at_pos(ev.x, ev.y)
+ for i in '/peer-popup/kill-peer', '/peer-popup/force-kx':
+ me.ui.get_widget(i).set_sensitive(me.mon.connectedp and
+ r is not None)
+ if r:
+ me.menupeer = me.path_peer(r[0])
+ else:
+ me.menupeer = None
+ me.ui.get_widget('/peer-popup').popup(None, None, None,
+ ev.button, ev.time)
+
+ def killpeer(me):
+ me.mon.simplecmd('KILL %s' % me.menupeer.name)
+ def forcekx(me):
+ me.mon.simplecmd('FORCEKX %s' % me.menupeer.name)
+
+ def reping(me):
+ if me.pinger is not None:
+ GO.source_remove(me.pinger)
+ me.pinger = GO.timeout_add(10000, me.ping)
+ me.ping()
+ def unping(me):
+ if me.pinger is not None:
+ GO.source_remove(me.pinger)
+ me.pinger = None
+ def ping(me):
+ for name in me.mon.peers:
+ p = me.mon.peers[name]
+ PingCommand(me.mon, 'PING', p, lambda p, t: me.pong(p, p.ping, t))
+ PingCommand(me.mon, 'EPING', p, lambda p, t: me.pong(p, p.eping, t))
+ return True
+ def pong(me, p, ping, t):
+ ping.n += 1
+ if t is None:
+ ping.nmiss += 1
+ ping.nmissrun += 1
+ me.listmodel[p.i][ping.tcol] = '(miss %d)' % ping.nmissrun
+ me.listmodel[p.i][ping.ccol] = 'red'
+ else:
+ ping.ngood += 1
+ ping.nmissrun = 0
+ ping.tlast = t
+ ping.ttot += t
+ me.listmodel[p.i][ping.tcol] = '%.1f ms' % t
+ me.listmodel[p.i][ping.ccol] = 'black'
+ p.pinghook.run()
+ def setstatus(me, status):
+ me.status.pop(0)
+ me.status.push(0, status)
+ def notify(me, note, rest):
+ if note == 'DAEMON':
+ me.ui.get_widget('/menubar/server-menu/daemon').set_sensitive(False)
+ def connected(me):
+ me.setstatus('Connected (port %s)' % me.mon.simplecmd('PORT')[0])
+ me.ui.get_widget('/menubar/server-menu/connect').set_sensitive(False)
+ for i in ('/menubar/server-menu/disconnect',
+ '/menubar/server-menu/server-version',
+ '/menubar/server-menu/add-peer',
+ '/menubar/server-menu/server-quit',
+ '/menubar/logs-menu/trace-options'):
+ me.ui.get_widget(i).set_sensitive(True)
+ me.ui.get_widget('/menubar/server-menu/daemon'). \
+ set_sensitive(parseinfo(me.mon.simplecmd('SERVINFO'))['daemon'] ==
+ 'nil')
+ me.reping()
+ def disconnected(me):
+ me.setstatus('Disconnected')
+ me.ui.get_widget('/menubar/server-menu/connect').set_sensitive(True)
+ for i in ('/menubar/server-menu/disconnect',
+ '/menubar/server-menu/server-version',
+ '/menubar/server-menu/add-peer',
+ '/menubar/server-menu/daemon',
+ '/menubar/server-menu/server-quit',
+ '/menubar/logs-menu/trace-options'):
+ me.ui.get_widget(i).set_sensitive(False)
+ me.unping()
+ def destroy(me):
+ if me.pinger is not None:
+ GO.source_remove(me.pinger)
+ def report(me, msg):
+ moanbox(msg)
+ return True
+
+#----- Parse options --------------------------------------------------------
+
+def version(fp = stdout):
+ """Print the program's version number."""
+ fp.write('%s, %s version %s\n' % (quis, PACKAGE, VERSION))
+
+def usage(fp):
+ """Print a brief usage message for the program."""
+ fp.write('Usage: %s [-d DIR] [-a SOCK]\n' % quis)
+
+def main():
+ global tripedir
+ if 'TRIPEDIR' in environ:
+ tripedir = environ['TRIPEDIR']
+ tripesock = '%s/%s' % (socketdir, 'tripesock')
+
+ try:
+ opts, args = O.getopt(argv[1:],
+ 'hvud:a:',
+ ['help', 'version', 'usage',
+ 'directory=', 'admin-socket='])
+ except O.GetoptError, exc:
+ moan(exc)
+ usage(stderr)
+ exit(1)
+ for o, v in opts:
+ if o in ('-h', '--help'):
+ version(stdout)
+ print
+ usage(stdout)
+ print """
+Graphical monitor for TrIPE VPN.
+
+Options supported:
+
+-h, --help Show this help message.
+-v, --version Show the version number.
+-u, --usage Show pointlessly short usage string.
+
+-d, --directory=DIR Use TrIPE directory DIR.
+-a, --admin-socket=FILE Select socket to connect to."""
+ exit(0)
+ elif o in ('-v', '--version'):
+ version(stdout)
+ exit(0)
+ elif o in ('-u', '--usage'):
+ usage(stdout)
+ exit(0)
+ elif o in ('-d', '--directory'):
+ tripedir = v
+ elif o in ('-a', '--admin-socket'):
+ tripesock = v
+ else:
+ raise "can't happen!"
+ if len(args) > 0:
+ usage(stderr)
+ exit(1)
+
+ OS.chdir(tripedir)
+ mon = Monitor(tripesock)
+ root = MonitorWindow(mon)
+ HookClient().hook(root.closehook, exit)
+ G.main()
+
+if __name__ == '__main__':
+ main()
+
return;
}
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: packet arrived");
- trace_block(T_PACKET, "tunnel: packet contents", buf_i, n);
+ trace(T_TUNNEL, "tun-bsd: packet arrived");
+ trace_block(T_PACKET, "tun-bsd: packet contents", buf_i, n);
})
buf_init(&b, buf_i, n);
p_tun(t->p, &b);
t->n = n;
sel_initfile(&sel, &t->f, fd, SEL_READ, t_read, t);
sel_addfile(&t->f);
- T( trace(T_TUNNEL, "tunnel: attached interface %s to peer `%s'",
+ T( trace(T_TUNNEL, "tun-bsd: attached interface %s to peer `%s'",
t_ifname(t), p_name(p)); )
return (t);
}
static void t_inject(tunnel *t, buf *b)
{
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: inject decrypted packet");
- trace_block(T_PACKET, "tunnel: packet contents", BBASE(b), BLEN(b));
+ trace(T_TUNNEL, "tun-bsd: inject decrypted packet");
+ trace_block(T_PACKET, "tun-bsd: packet contents", BBASE(b), BLEN(b));
})
write(t->f.fd, BBASE(b), BLEN(b));
}
return;
}
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: packet arrived");
- trace_block(T_PACKET, "tunnel: packet contents", buf_i, n);
+ trace(T_TUNNEL, "tun-linux: packet arrived");
+ trace_block(T_PACKET, "tun-linux: packet contents", buf_i, n);
})
buf_init(&b, buf_i, n);
p_tun(t->p, &b);
return (0);
}
fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+ memset(&iff, 0, sizeof(iff));
iff.ifr_name[0] = 0;
iff.ifr_flags = IFF_TUN | IFF_NO_PI;
if ((f = ioctl(fd, TUNSETIFF, &iff)) < 0) {
sel_addfile(&t->f);
iff.ifr_name[IFNAMSIZ - 1] = 0;
strcpy(t->ifn, iff.ifr_name);
- T( trace(T_TUNNEL, "tunnel: attached interface %s to peer `%s'",
+ T( trace(T_TUNNEL, "tun-linux: attached interface %s to peer `%s'",
t->ifn, p_name(p)); )
return (t);
}
static void t_inject(tunnel *t, buf *b)
{
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: inject decrypted packet");
+ trace(T_TUNNEL, "tun-linux: inject decrypted packet");
trace_block(T_PACKET, "tunnel: packet contents", BBASE(b), BLEN(b));
})
write(t->f.fd, BBASE(b), BLEN(b));
return;
}
IF_TRACING(T_TUNNEL, {
- trace_block(T_PACKET, "tunnel: SLIP-encapsulated data",
+ trace_block(T_PACKET, "tun-slip: SLIP-encapsulated data",
buf_t, n);
})
else if (st & ST_ESC)
a_warn("TUN %s slip escape-end", t->sl->name);
else if (q == t->buf) {
- T( trace(T_TUNNEL, "tunnel: empty packet"); )
+ T( trace(T_TUNNEL, "tun-slip: empty packet"); )
} else {
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: packet arrived");
- trace_block(T_PACKET, "tunnel: packet contents",
+ trace(T_TUNNEL, "tun-slip: packet arrived");
+ trace_block(T_PACKET, "tun-slip: packet contents",
t->buf, q - t->buf);
})
buf_init(&b, t->buf, q - t->buf);
for (;;) {
if (*p == '/' || *p == '.') {
slipcmd = p;
- T( trace(T_TUNNEL, "tunnel: declared slip command `%s'", slipcmd); )
+ T( trace(T_TUNNEL, "tun-slip: declared slip command `%s'", slipcmd); )
break;
}
uli = strtoul(p, &q, 0);
sl->name[n] = 0;
*tail = sl;
tail = &sl->next;
- T( trace(T_TUNNEL, "tunnel: declared slipif %d,%d=%s",
+ T( trace(T_TUNNEL, "tun-slip: declared slipif %d,%d=%s",
sl->ifd, sl->ofd, sl->name); )
p = q + n + 1;
if (!*p)
for (sl = slipifs; sl; sl = sl->next) {
if (!(sl->f & F_INUSE)) {
- T( trace(T_TUNNEL, "tunnel: %s using static slipif %s",
+ T( trace(T_TUNNEL, "tun-slip: %s using static slipif %s",
p_name(p), sl->name); )
goto found;
}
sl->kid = kid;
sl->next = 0;
sl->f = F_DYNAMIC;
- T( trace(T_TUNNEL, "tunnel: %s using dynamic slipif %s",
+ T( trace(T_TUNNEL, "tun-slip: %s using dynamic slipif %s",
p_name(p), sl->name); )
fdflags(pout[0], O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
fdflags(pin[1], O_NONBLOCK, 0, FD_CLOEXEC, FD_CLOEXEC);
octet *q;
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: inject decrypted packet");
- trace_block(T_PACKET, "tunnel: packet contents", BBASE(b), BLEN(b));
+ trace(T_TUNNEL, "tun-slip: inject decrypted packet");
+ trace_block(T_PACKET, "tun-slip: packet contents", BBASE(b), BLEN(b));
})
q = buf;
}
*q++ = SL_END;
IF_TRACING(T_TUNNEL, {
- trace_block(T_PACKET, "tunnel: SLIP-encapsulated contents",
+ trace_block(T_PACKET, "tun-slip: SLIP-encapsulated contents",
buf, q - buf);
})
write(t->sl->ofd, buf, q - buf);
sl->f &= ~F_INUSE;
}
if (sl && (sl->f & F_DYNAMIC)) {
- T( trace(T_TUNNEL, "tunnel: releasing dynamic slipif %s", sl->name); )
+ T( trace(T_TUNNEL, "tun-slip: releasing dynamic slipif %s", sl->name); )
close(sl->ofd);
close(sl->ifd);
kill(sl->kid, SIGTERM);
return;
}
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: packet arrived");
- trace_block(T_PACKET, "tunnel: packet contents", buf_i, n);
+ trace(T_TUNNEL, "tun-unet: packet arrived");
+ trace_block(T_PACKET, "tun-unet: packet contents", buf_i, n);
})
buf_init(&b, buf_i, n);
p_tun(t->p, &b);
t->p = p;
sel_initfile(&sel, &t->f, fd, SEL_READ, t_read, t);
sel_addfile(&t->f);
- T( trace(T_TUNNEL, "tunnel: attached interface %s to peer `%s'",
+ T( trace(T_TUNNEL, "tun-unet: attached interface %s to peer `%s'",
t_ifname(t), p_name(p)); )
return (t);
}
static void t_inject(tunnel *t, buf *b)
{
IF_TRACING(T_TUNNEL, {
- trace(T_TUNNEL, "tunnel: inject decrypted packet");
- trace_block(T_PACKET, "tunnel: packet contents", BBASE(b), BLEN(b));
+ trace(T_TUNNEL, "tun-unet: inject decrypted packet");
+ trace_block(T_PACKET, "tun-unet: packet contents", BBASE(b), BLEN(b));
})
write(t->f.fd, BBASE(b), BLEN(b));
}