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)
* 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',
table = GridPacker()
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),
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:
t = me.l_tunnel.get_active()
+ 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
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))
+ 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
.IR key .
.hP \*o
An occurrence of
- .BI $[ host ]
+ .BI $ flags [ host ]
is replaced by the IP address of the named
.IR host .
Note that
.I host
may itself contain
.BI $( key )
- substitutions.
+ substitutions. The
+ .I flags
+ consist of zero or more of the following characters:
+ .RB ` 4 '
+ looks up the
+ .IR host 's
+ IPv4 address(es);
+ .RB ` 6 '
+ looks up the
+ .IR host 's
+ IPv6 address(es);
+ and
+ .RB ` * '
+ returns all of the found addresses, separated by spaces, rather than
+ just the first one. If neither address family is requested, then
+ .RB ` 46 '
+ is assumed. IPv6 address lookup of names, rather than address literals,
+ depends on the external
+ .BR adnshost (1)
+ program; if it is not present then only IPv4 lookups will be performed.
.PP
There is a simple concept of
.I inheritance
Shell command for closing down connection to this peer. Used by
.BR connect (8).
.TP
+.B ephemeral
+Mark the peer as ephemeral: see
+.BR tripe-admin (5)
+for what this means. Used by
+.BR connect (8).
+.TP
.B every
Interval for checking that the peer is still alive and well. Used by
.BR connect (8).
Key tag to use to authenticate the peer. Used by
.BR connect (8).
.TP
+.B knock
+Knock string to send when establishing a dynamic connection. Used by
+.BR connect (8).
+.TP
.B mobile
Peer's IP address is highly volatile. Used by
.BR connect (8).
*['ADD'] +
_kwopts(kw, ['tunnel', 'keepalive',
'key', 'priv', 'cork',
- 'mobile']) +
+ 'mobile', 'knock',
+ 'ephemeral']) +
[peer] +
list(addr)))
def addr(me, peer):
*['PING'] +
_kwopts(kw, ['timeout']) +
[peer]))
- def port(me):
- return _oneline(me.command('PORT', filter = _tokenjoin))
+ def port(me, af = None):
+ return _oneline(me.command('PORT',
+ *((af is not None) and [af] or []),
+ filter = _tokenjoin))
def quit(me):
return _simple(me.command('QUIT'))
def reload(me):
include $(top_srcdir)/vars.am
sbin_PROGRAMS =
+noinst_PROGRAMS =
man_MANS =
- LDADD = $(libtripe) $(libpriv) $(catacomb_LIBS)
+ LDADD = $(libtripe) $(libpriv) \
+ $(catacomb_LIBS) $(ADNS_LIBS)
###--------------------------------------------------------------------------
### The main server.
CLEANFILES += tripe-service.7tripe
EXTRA_DIST += tripe-service.7.in
+###--------------------------------------------------------------------------
+### Unit-test program.
+
+noinst_PROGRAMS += tripe-test
+
+tripe_test_SOURCES = test.c
+
+tripe_test_SOURCES += admin.c
+tripe_test_SOURCES += addrmap.c
+tripe_test_SOURCES += bulkcrypto.c
+tripe_test_SOURCES += chal.c
+tripe_test_SOURCES += dh.c
+tripe_test_SOURCES += keyexch.c
+tripe_test_SOURCES += keymgmt.c
+tripe_test_SOURCES += keyset.c
+tripe_test_SOURCES += peer.c
+tripe_test_SOURCES += privsep.c
+tripe_test_SOURCES += servutil.c
+
+tripe_test_SOURCES += tun-std.c
+tripe_test_SOURCES += tun-slip.c
+
###----- That's all, folks --------------------------------------------------
/*----- Static variables --------------------------------------------------*/
+ #ifdef HAVE_LIBADNS
+ static adns_state ads;
+ sel_hook hook;
+ #endif
static admin *admins;
static admin *a_dead;
static sel_file sock;
} else if (*fmt == '?') {
if (strcmp(fmt, "?ADDR") == 0) {
const addr *a = va_arg(*ap, const addr *);
- switch (a->sa.sa_family) {
- case AF_INET:
- u_quotify(d, "INET");
- u_quotify(d, inet_ntoa(a->sin.sin_addr));
- dstr_putf(d, " %u", (unsigned)ntohs(a->sin.sin_port));
- break;
- default:
- abort();
+ char name[NI_MAXHOST], serv[NI_MAXSERV];
+ int ix, err;
+ if ((err = getnameinfo(&a->sa, addrsz(a),
+ name, sizeof(name), serv, sizeof(serv),
+ (NI_NUMERICHOST | NI_NUMERICSERV |
+ NI_DGRAM)))) {
+ dstr_putf(d, " E%d", err);
+ u_quotify(d, gai_strerror(err));
+ } else {
+ ix = afix(a->sa.sa_family); assert(ix >= 0);
+ u_quotify(d, aftab[ix].name);
+ u_quotify(d, name);
+ u_quotify(d, serv);
}
} else if (strcmp(fmt, "?B64") == 0) {
const octet *p = va_arg(*ap, const octet *);
{
close(sock.fd);
unlink(sockname);
- FOREACH_PEER(p, { p_destroy(p); });
+ FOREACH_PEER(p, { p_destroy(p, 1); });
ps_quit();
exit(0);
}
/*----- Name resolution operations ----------------------------------------*/
+ #ifdef HAVE_LIBADNS
+
+ /* --- @before_select@ --- *
+ *
+ * Arguments: @sel_state *s@ = the @sel@ multiplexor (unused)
+ * @sel_args *a@ = input to @select@, to be updated
+ * @void *p@ = a context pointer (unused)
+ *
+ * Returns: ---
+ *
+ * Use: An I/O multiplexor hook, called just before waiting for I/O
+ * events.
+ *
+ * Currently its main purpose is to wire ADNS into the event
+ * loop.
+ */
+
+ static void before_select(sel_state *s, sel_args *a, void *p)
+ {
+ struct timeval now;
+ adns_query q;
+ adns_answer *n;
+ admin_resop *r;
+ int any = 0;
+
+ /* --- Check for name-resolution progress --- *
+ *
+ * If there is any, then clobber the timeout: one of the resolver
+ * callbacks might have renewed its interest in a file descriptor, but too
+ * late to affect this @select@ call.
+ *
+ * I think, strictly, this is an mLib bug, but it's cheap enough to hack
+ * around here. Fixing it will wait for mLib 3.
+ */
+
+ for (;;) {
+ q = 0;
+ if (adns_check(ads, &q, &n, &p)) break;
+ r = p;
+ any = 1;
+ if (n->status != adns_s_ok) {
+ T( trace(T_ADMIN, "admin: resop %s failed: %s",
+ BGTAG(r), adns_strerror(n->status)); )
+ a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+ r->func(r, ARES_FAIL);
+ } else {
+ T( trace(T_ADMIN, "admin: resop %s ok", BGTAG(r)); )
+ assert(n->type == adns_r_addr);
+ assert(n->nrrs > 0);
+ assert(n->rrs.addr[0].len <= sizeof(r->sa));
+ memcpy(&r->sa, &n->rrs.addr[0].addr, n->rrs.addr[0].len);
+ setport(&r->sa, r->port);
+ r->func(r, ARES_OK);
+ }
+ free(n);
+ sel_rmtimer(&r->t);
+ xfree(r->addr);
+ a_bgrelease(&r->bg);
+ }
+
+ if (any) { a->tvp = &a->tv; a->tv.tv_sec = 0; a->tv.tv_usec = 0; }
+
+ gettimeofday(&now, 0);
+ adns_beforeselect(ads, &a->maxfd,
+ &a->fd[SEL_READ], &a->fd[SEL_WRITE], &a->fd[SEL_EXC],
+ &a->tvp, &a->tv, &now);
+ }
+
+ /* --- @after_select@ --- *
+ *
+ * Arguments: @sel_state *s@ = the @sel@ multiplexor (unused)
+ * @sel_args *a@ = input to @select@, to be updated
+ * @void *p@ = a context pointer (unused)
+ *
+ * Returns: ---
+ *
+ * Use: An I/O multiplexor hook, called just after waiting for I/O
+ * events.
+ *
+ * Currently its main purpose is to wire ADNS into the event
+ * loop.
+ */
+
+ static void after_select(sel_state *s, sel_args *a, void *p)
+ {
+ struct timeval now;
+
+ gettimeofday(&now, 0);
+ adns_afterselect(ads, a->maxfd,
+ &a->fd[SEL_READ], &a->fd[SEL_WRITE], &a->fd[SEL_EXC],
+ &now);
+ }
+
+ #else
+
/* --- @a_resolved@ --- *
*
* Arguments: @struct hostent *h@ = pointer to resolved hostname
{
admin_resop *r = v;
- T( trace(T_ADMIN, "admin: resop %s resolved", BGTAG(r)); )
QUICKRAND;
if (!h) {
+ T( trace(T_ADMIN, "admin: resop %s failed: %s",
+ BGTAG(r), hstrerror(h_errno)); )
a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
r->func(r, ARES_FAIL);
} else {
+ T( trace(T_ADMIN, "admin: resop %s ok", BGTAG(r)); )
+ r->sa.sin.sin_family = AF_INET;
memcpy(&r->sa.sin.sin_addr, h->h_addr, sizeof(struct in_addr));
+ setport(&r->sa, r->port);
r->func(r, ARES_OK);
}
sel_rmtimer(&r->t);
a_bgrelease(&r->bg);
}
+ #endif
+
/* --- @a_restimer@ --- *
*
* Arguments: @struct timeval *tv@ = timer
T( trace(T_ADMIN, "admin: resop %s timeout", BGTAG(r)); )
a_bgfail(&r->bg, "resolver-timeout", "%s", r->addr, A_END);
r->func(r, ARES_FAIL);
+ #ifdef HAVE_LIBADNS
+ adns_cancel(r->q);
+ #else
bres_abort(&r->r);
+ #endif
xfree(r->addr);
a_bgrelease(&r->bg);
}
r->func(r, ARES_FAIL);
sel_rmtimer(&r->t);
xfree(r->addr);
+ #ifdef HAVE_LIBADNS
+ adns_cancel(r->q);
+ #else
bres_abort(&r->r);
+ #endif
}
/* --- @a_resolve@ --- *
{
struct timeval tv;
unsigned long pt;
+ int af = AF_UNSPEC;
+ const char *fam = "ANY";
char *p;
- int i = 0;
+ int i = 0, j;
+ struct addrinfo *ai, *ailist, aihint = { 0 };
+ #ifdef HAVE_LIBADNS
+ int err;
+ adns_queryflags qf;
+ #endif
/* --- Fill in the easy bits of address --- */
r->bg.tag = "<starting>";
r->addr = 0;
r->func = func;
- if (mystrieq(av[i], "inet")) i++;
+ if (mystrieq(av[i], "any"))
+ { fam = "ANY"; af = AF_UNSPEC; i++; }
+ else for (j = 0; j < NADDRFAM; j++) {
+ if (mystrieq(av[i], aftab[j].name)) {
+ if (udpsock[j].fd < 0) {
+ a_fail(a, "disabled-address-family", "%s", aftab[j].name, A_END);
+ goto fail;
+ }
+ fam = aftab[j].name;
+ af = aftab[j].af;
+ i++;
+ break;
+ }
+ }
if (ac - i != 1 && ac - i != 2) {
- a_fail(a, "bad-addr-syntax", "[inet] ADDRESS [PORT]", A_END);
+ a_fail(a, "bad-addr-syntax", "[FAMILY] ADDRESS [PORT]", A_END);
goto fail;
}
- r->sa.sin.sin_family = AF_INET;
r->addr = xstrdup(av[i]);
if (!av[i + 1])
pt = TRIPE_PORT;
a_fail(a, "invalid-port", "%lu", pt, A_END);
goto fail;
}
- r->sa.sin.sin_port = htons(pt);
+ r->port = pt;
/* --- Report backgrounding --- *
*
if (a_bgadd(a, &r->bg, tag, a_rescancel))
goto fail;
- T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s'",
- a->seq, BGTAG(r), r->addr); )
+ T( trace(T_ADMIN, "admin: %u, resop %s, hostname `%s', family `%s'",
+ a->seq, BGTAG(r), r->addr, fam); )
/* --- If the name is numeric, do it the easy way --- */
- if (inet_aton(av[i], &r->sa.sin.sin_addr)) {
- T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
- func(r, ARES_OK);
+ aihint.ai_family = af;
+ aihint.ai_socktype = SOCK_DGRAM;
+ aihint.ai_protocol = IPPROTO_UDP;
+ aihint.ai_flags = AI_NUMERICHOST;
+ if (!getaddrinfo(av[i], 0, &aihint, &ailist)) {
+ for (ai = ailist; ai; ai = ai->ai_next) {
+ if ((j = afix(ai->ai_family)) >= 0 && udpsock[j].fd >= 0)
+ break;
+ }
+ if (!ai) {
+ T( trace(T_ADMIN, "admin: resop %s failed: "
+ "no suitable addresses returned", BGTAG(r)); )
+ a_bgfail(&r->bg, "resolve-error", "%s" , r->addr, A_END);
+ func(r, ARES_FAIL);
+ } else {
+ T( trace(T_ADMIN, "admin: resop %s done the easy way", BGTAG(r)); )
+ assert(ai->ai_addrlen <= sizeof(r->sa));
+ memcpy(&r->sa, ai->ai_addr, ai->ai_addrlen);
+ setport(&r->sa, r->port);
+ func(r, ARES_OK);
+ }
+ freeaddrinfo(ailist);
xfree(r->addr);
a_bgrelease(&r->bg);
return;
gettimeofday(&tv, 0);
tv.tv_sec += T_RESOLVE;
sel_addtimer(&sel, &r->t, &tv, a_restimer, r);
+ #ifdef HAVE_LIBADNS
+ qf = adns_qf_search;
+ for (j = 0; j < NADDRFAM; j++) {
+ if ((af == AF_UNSPEC || af == aftab[i].af) && udpsock[j].fd >= 0)
+ qf |= aftab[j].qf;
+ }
+ if ((err = adns_submit(ads, r->addr, adns_r_addr, qf, r, &r->q)) != 0) {
+ T( trace(T_ADMIN, "admin: resop %s adns_submit failed: %s",
+ BGTAG(r), strerror(err)); )
+ a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+ goto fail_release;
+ }
+ #else
+ if (af != AF_UNSPEC && af != AF_INET) {
+ T( trace(T_ADMIN, "admin: resop %s failed: unsupported address family",
+ BGTAG(r)); )
+ a_bgfail(&r->bg, "resolve-error", "%s", r->addr, A_END);
+ goto fail_release;
+ }
+ if (udpsock[AFIX_INET].fd < 0) {
+ a_bgfail(&r->bg, "disabled-address-family", "INET", A_END);
+ goto fail_release;
+ }
bres_byname(&r->r, r->addr, a_resolved, r);
+ #endif
return;
fail:
func(r, ARES_FAIL);
if (r->addr) xfree(r->addr);
xfree(r);
+ return;
+
+ fail_release:
+ func(r, ARES_FAIL);
+ xfree(r->addr);
+ a_bgrelease(&r->bg);
}
/*----- Option parsing ----------------------------------------------------*/
if (add->peer.tag) xfree(add->peer.tag);
if (add->peer.privtag) xfree(add->peer.privtag);
+ if (add->peer.knock) xfree(add->peer.knock);
xfree(add->peer.name);
}
add->peer.name = 0;
add->peer.tag = 0;
add->peer.privtag = 0;
+ add->peer.knock = 0;
add->peer.t_ka = 0;
add->peer.tops = tun_default;
add->peer.f = 0;
})
OPTTIME("-keepalive", t, { add->peer.t_ka = t; })
OPT("-cork", { add->peer.f |= KXF_CORK; })
+ OPT("-ephemeral", { add->peer.f |= PSF_EPHEM; })
OPTARG("-key", arg, {
if (add->peer.tag) xfree(add->peer.tag);
add->peer.tag = xstrdup(arg);
})
- OPT("-mobile", { add->peer.f |= PSF_MOBILE; })
+ OPT("-mobile", { add->peer.f |= PSF_MOBILE | PSF_EPHEM; })
OPTARG("-priv", arg, {
if (add->peer.privtag) xfree(add->peer.privtag);
add->peer.privtag = xstrdup(arg);
})
+ OPTARG("-knock", arg, {
+ if (add->peer.knock) xfree(add->peer.knock);
+ add->peer.knock = xstrdup(arg);
+ add->peer.f |= PSF_EPHEM;
+ })
});
/* --- Make sure someone's not got there already --- */
if (add->peer.name) xfree(add->peer.name);
if (add->peer.tag) xfree(add->peer.tag);
if (add->peer.privtag) xfree(add->peer.privtag);
+ if (add->peer.knock) xfree(add->peer.knock);
xfree(add);
return;
}
{ alertcmd(a, AF_WARN, AF_WARN, "WARN", av); }
static void acmd_port(admin *a, unsigned ac, char *av[])
- { a_info(a, "%u", p_port(), A_END); a_ok(a); }
+ {
+ int i;
+
+ if (ac) {
+ for (i = 0; i < NADDRFAM; i++)
+ if (mystrieq(av[0], aftab[i].name)) goto found;
+ a_fail(a, "unknown-address-family", "%s", av[0], A_END);
+ return;
+ found:
+ if (udpsock[i].fd < 0) {
+ a_fail(a, "disabled-address-family", "%s", aftab[i].name, A_END);
+ return;
+ }
+ } else {
+ for (i = 0; i < NADDRFAM; i++)
+ if (udpsock[i].fd >= 0) goto found;
+ abort();
+ }
+ a_info(a, "%u", p_port(i), A_END);
+ a_ok(a);
+ }
static void acmd_daemon(admin *a, unsigned ac, char *av[])
{
buf b;
buf_init(&b, buf_i, PKBUFSZ);
- c_new(&b);
+ c_new(0, 0, &b);
a_info(a, "?B64", BBASE(&b), (size_t)BLEN(&b), A_END);
a_ok(a);
}
a_fail(a, "bad-base64", "%s", codec_strerror(err), A_END);
else {
buf_init(&b, d.buf, d.len);
- if (c_check(&b) || BBAD(&b) || BLEFT(&b))
+ if (c_check(0, 0, &b) || BBAD(&b) || BLEFT(&b))
a_fail(a, "invalid-challenge", A_END);
else
a_ok(a);
if ((p = a_findpeer(a, av[0])) != 0) {
ad = p_addr(p);
- assert(ad->sa.sa_family == AF_INET);
a_info(a, "?ADDR", ad, A_END);
a_ok(a);
}
if ((p = a_findpeer(a, av[0])) != 0) {
ps = p_spec(p);
a_info(a, "tunnel=%s", ps->tops->name, A_END);
+ if (ps->knock) a_info(a, "knock=%s", ps->knock, A_END);
a_info(a, "key=%s", p_tag(p),
"current-key=%s", p->kx.kpub->tag, A_END);
if ((ptag = p_privtag(p)) == 0) ptag = "(default)";
a_info(a, "keepalive=%lu", ps->t_ka, A_END);
a_info(a, "corked=%s", BOOL(p->kx.f&KXF_CORK),
"mobile=%s", BOOL(ps->f&PSF_MOBILE),
+ "ephemeral=%s", BOOL(ps->f&PSF_EPHEM),
A_END);
a_ok(a);
}
peer *p;
if ((p = a_findpeer(a, av[0])) != 0) {
- p_destroy(p);
+ p_destroy(p, 1);
a_ok(a);
}
}
{ "notify", "MESSAGE ...", 1, 0xffff, acmd_notify },
{ "peerinfo", "PEER", 1, 1, acmd_peerinfo },
{ "ping", "[OPTIONS] PEER", 1, 0xffff, acmd_ping },
- { "port", 0, 0, 0, acmd_port },
+ { "port", "[FAMILY]", 0, 1, acmd_port },
{ "quit", 0, 0, 0, acmd_quit },
{ "reload", 0, 0, 0, acmd_reload },
{ "servinfo", 0, 0, 0, acmd_servinfo },
struct sigaction sa;
size_t sz;
mode_t omask;
+ #ifdef HAVE_LIBADNS
+ int err;
+ #endif
/* --- Create services table --- */
sel_initfile(&sel, &sock, fd, SEL_READ, a_accept, 0);
sel_addfile(&sock);
sockname = name;
+ #ifdef HAVE_LIBADNS
+ if ((err = adns_init(&ads,
+ (adns_if_permit_ipv4 | adns_if_permit_ipv6 |
+ adns_if_noserverwarn | adns_if_nosigpipe |
+ adns_if_noautosys),
+ 0)) != 0)
+ die(EXIT_FAILURE, "failed to initialize ADNS: %s", strerror(errno));
+ sel_addhook(&sel, &hook, before_select, after_select, 0);
+ #else
bres_init(&sel);
+ #endif
T( trace_custom(a_trace, 0);
trace(T_ADMIN, "admin: enabled custom tracing"); )
flags |= F_INIT;
#include "tripe.h"
+ /*----- Global state ------------------------------------------------------*/
+
+ sel_file udpsock[NADDRFAM];
+
/*----- Static variables --------------------------------------------------*/
static sym_table byname;
static addrmap byaddr;
- static sel_file sock;
static unsigned nmobile;
/*----- Tunnel table ------------------------------------------------------*/
{
peer *q;
peer_byaddr *pa, *qa;
+ int ix;
unsigned f;
/* --- Figure out how to proceed --- *
T( trace(T_PEER, "peer: updating address for `%s'", p_name(p)); )
am_remove(&byaddr, p->byaddr);
p->byaddr = pa; p->spec.sa = *a; pa->p = p;
+ p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0);
a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
return (0);
} else {
p_name(p), p_name(q)); )
q->byaddr = qa; qa->p = q; q->spec.sa = p->spec.sa;
p->byaddr = pa; pa->p = p; p->spec.sa = *a;
+ ix = p->afix; p->afix = q->afix; q->afix = ix;
a_notify("NEWADDR", "?PEER", p, "?ADDR", a, A_END);
a_notify("NEWADDR", "?PEER", q, "?ADDR", &q->spec.sa, A_END);
return (0);
ssize_t n;
int ch;
buf b, bb;
+ #ifndef NTRACE
+ int ix = -1;
+ char name[NI_MAXHOST], svc[NI_MAXSERV];
+ #endif
/* --- Read the data --- */
a_warn("PEER", "-", "socket-read-error", "?ERRNO", A_END);
return;
}
+ IF_TRACING(T_PEER, {
+ ix = afix(a.sa.sa_family);
+ getnameinfo(&a.sa, sz, name, sizeof(name), svc, sizeof(svc),
+ NI_NUMERICHOST | NI_NUMERICSERV);
+ })
/* --- If the packet is a greeting, don't check peers --- */
if (n && buf_i[0] == (MSG_MISC | MISC_GREET)) {
IF_TRACING(T_PEER, {
- trace(T_PEER, "peer: greeting received from INET %s %u",
- inet_ntoa(a.sin.sin_addr),
- (unsigned)ntohs(a.sin.sin_port));
+ trace(T_PEER, "peer: greeting received from %s %s %s",
+ aftab[ix].name, name, svc);
trace_block(T_PACKET, "peer: greeting contents", buf_i, n);
})
buf_init(&b, buf_i, n);
buf_getbyte(&b);
- if (c_check(&b) || BLEFT(&b)) {
+ if (c_check(0, 0, &b) || BLEFT(&b)) {
a_warn("PEER", "-", "invalid-greeting", A_END);
return;
}
IF_TRACING(T_PEER, {
if (p) {
trace(T_PEER,
- "peer: packet received from `%s' from address INET %s %d",
- p_name(p), inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+ "peer: packet received from `%s' from address %s %s %s",
+ p_name(p), aftab[ix].name, name, svc);
} else {
- trace(T_PEER, "peer: packet received from unknown address INET %s %d",
- inet_ntoa(a.sin.sin_addr), ntohs(a.sin.sin_port));
+ trace(T_PEER, "peer: packet received from unknown address %s %s %s",
+ aftab[ix].name, name, svc);
}
trace_block(T_PACKET, "peer: packet contents", buf_i, n);
})
}
break;
case MSG_KEYEXCH:
- if (!p) goto unexp;
- p_rxupdstats(p, n);
- kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
+ if (p) p_rxupdstats(p, n);
+ if (kx_message(p ? &p->kx : 0, &a, ch & MSG_TYPEMASK, &b)) goto unexp;
break;
case MSG_MISC:
switch (ch & MSG_TYPEMASK) {
p_ponged(p, MISC_EPONG, &bb);
}
break;
+ case MISC_BYE:
+ buf_init(&bb, buf_t, sizeof(buf_t));
+ if (p_decrypt(&p, &a, n, ch, &b, &bb)) return;
+ if (!(p->spec.f&PSF_EPHEM)) return;
+ if (BOK(&bb)) {
+ buf_flip(&bb);
+ if (BSZ(&bb)) return;
+ p_destroy(p, 0);
+ }
+ break;
}
break;
default:
return (&p->b);
}
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments: @const addr *a@ = recipient address
+ * @const void *p@ = pointer to packet to send
+ * @size_t sz@ = length of packet
+ *
+ * Returns: Zero if successful, nonzero on error.
+ *
+ * Use: Sends a packet to an address which (possibly) isn't a current
+ * peer.
+ */
+
+int p_txaddr(const addr *a, const void *p, size_t sz)
+{
+ socklen_t sasz = addrsz(a);
+
+ IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet", p, sz); )
+ if (sendto(sock.fd, p, sz, 0, &a->sa, sasz) < 0) {
+ a_warn("PEER", "?ADDR", a, "socket-write-error", "?ERRNO", A_END);
+ return (-1);
+ }
+ return (0);
+}
+
/* --- @p_txend@ --- *
*
* Arguments: @peer *p@ = pointer to peer block
}
IF_TRACING(T_PEER, trace_block(T_PACKET, "peer: sending packet",
BBASE(&p->b), BLEN(&p->b)); )
- if (sendto(sock.fd, BBASE(&p->b), BLEN(&p->b),
+ if (sendto(udpsock[p->afix].fd, BBASE(&p->b), BLEN(&p->b),
0, &p->spec.sa.sa, sasz) < 0) {
a_warn("PEER", "?PEER", p, "socket-write-error", "?ERRNO", A_END);
return (0);
/* --- @p_init@ --- *
*
- * Arguments: @struct in_addr addr@ = address to bind to
- * @unsigned port@ = port number to listen to
+ * Arguments: @struct addrinfo *ailist@ = addresses to bind to
*
* Returns: ---
*
* Use: Initializes the peer system; creates the socket.
*/
- void p_init(struct in_addr addr, unsigned port)
+ void p_init(struct addrinfo *ailist)
{
int fd;
- struct sockaddr_in sin;
int len = PKBUFSZ;
+ int yes = 1;
+ int i;
+ struct addrinfo *ai;
+ unsigned port, lastport = 0;
+ addr a;
+ socklen_t sz;
- /* --- Note on socket buffer sizes --- *
- *
- * For some bizarre reason, Linux 2.2 (at least) doubles the socket buffer
- * sizes I pass to @setsockopt@. I'm not putting special-case code here
- * for Linux: BSD (at least TCPv2) does what I tell it rather than second-
- * guessing me.
- */
-
- if ((fd = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
- die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno));
- BURN(sin);
- sin.sin_family = AF_INET;
- sin.sin_addr = addr;
- sin.sin_port = htons(port);
- if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)))
- die(EXIT_FAILURE, "bind failed: %s", strerror(errno));
- if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
- setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
- die(EXIT_FAILURE, "failed to set socket buffer sizes: %s",
- strerror(errno));
+ for (i = 0; i < NADDRFAM; i++) udpsock[i].fd = -1;
+
+ for (ai = ailist; ai; ai = ai->ai_next) {
+ if ((i = afix(ai->ai_family)) < 0) continue;
+ if (udpsock[i].fd != -1) continue;
+
+ /* --- Note on socket buffer sizes --- *
+ *
+ * For some bizarre reason, Linux 2.2 (at least) doubles the socket
+ * buffer sizes I pass to @setsockopt@. I'm not putting special-case
+ * code here for Linux: BSD (at least TCPv2) does what I tell it rather
+ * than second-guessing me.
+ */
+
+ if ((fd = socket(ai->ai_family, SOCK_DGRAM, 0)) < 0)
+ die(EXIT_FAILURE, "socket creation failed: %s", strerror(errno));
+ if (i == AFIX_INET6 &&
+ setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof(yes))) {
+ die(EXIT_FAILURE, "failed to set IPv6-only state: %s",
+ strerror(errno));
+ }
+ assert(ai->ai_addrlen <= sizeof(a));
+ memcpy(&a, ai->ai_addr, ai->ai_addrlen);
+ if ((port = getport(&a)) == 0 && lastport) setport(&a, lastport);
+ if (bind(fd, &a.sa, addrsz(&a)))
+ die(EXIT_FAILURE, "bind failed: %s", strerror(errno));
+ if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &len, sizeof(len)) ||
+ setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &len, sizeof(len))) {
+ die(EXIT_FAILURE, "failed to set socket buffer sizes: %s",
+ strerror(errno));
+ }
+ fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
+ sel_initfile(&sel, &udpsock[i], fd, SEL_READ, p_read, 0);
+ sel_addfile(&udpsock[i]);
+ T( trace(T_PEER, "peer: created %s socket", aftab[i].name); )
+ if (!port) {
+ sz = sizeof(a);
+ if (getsockname(fd, &a.sa, &sz)) {
+ die(EXIT_FAILURE, "failed to read local socket address: %s",
+ strerror(errno));
+ }
+ lastport = getport(&a);
+ }
}
- fdflags(fd, O_NONBLOCK, O_NONBLOCK, FD_CLOEXEC, FD_CLOEXEC);
- sel_initfile(&sel, &sock, fd, SEL_READ, p_read, 0);
- sel_addfile(&sock);
- T( trace(T_PEER, "peer: created socket"); )
sym_create(&byname);
am_create(&byaddr);
/* --- @p_port@ --- *
*
- * Arguments: ---
+ * Arguments: @int i@ = address family index to retrieve
*
* Returns: Port number used for socket.
*/
- unsigned p_port(void)
+ unsigned p_port(int i)
{
addr a;
socklen_t sz = sizeof(addr);
- if (getsockname(sock.fd, &a.sa, &sz))
+ if (getsockname(udpsock[i].fd, &a.sa, &sz))
die(EXIT_FAILURE, "couldn't read port number: %s", strerror(errno));
- assert(a.sa.sa_family == AF_INET);
- return (ntohs(a.sin.sin_port));
+ return (getport(&a));
}
/* --- @p_keepalive@ --- *
p->spec.name = (/*unconst*/ char *)SYM_NAME(p->byname);
if (spec->tag) p->spec.tag = xstrdup(spec->tag);
if (spec->privtag) p->spec.privtag = xstrdup(spec->privtag);
+ if (spec->knock) p->spec.knock = xstrdup(spec->knock);
p->ks = 0;
p->pings = 0;
p->ifname = 0;
+ p->afix = afix(p->spec.sa.sa.sa_family); assert(p->afix >= 0);
memset(&p->st, 0, sizeof(stats));
p->st.t_start = time(0);
if (!(tops->flags & TUNF_PRIVOPEN))
T( trace(T_TUNNEL, "peer: attached interface %s to peer `%s'",
p->ifname, p_name(p)); )
p_setkatimer(p);
- if (kx_init(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK))
+ if (kx_setup(&p->kx, p, &p->ks, p->spec.f & PSF_KXMASK))
goto tidy_4;
a_notify("ADD",
"?PEER", p,
/* --- @p_destroy@ --- *
*
* Arguments: @peer *p@ = pointer to a peer
+ * @int bye@ = say goodbye to the peer?
*
* Returns: ---
*
* Use: Destroys a peer.
*/
-void p_destroy(peer *p)
+void p_destroy(peer *p, int bye)
{
ping *pg, *ppg;
+ buf *b, bb;
T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
+
+ if (bye && (p->spec.f&PSF_EPHEM)) {
+ b = p_txstart(p, MSG_MISC | MISC_BYE);
+ buf_init(&bb, buf_t, sizeof(buf_t));
+ assert(BOK(&bb)); buf_flip(&bb);
+ p_encrypt(p, MSG_MISC | MISC_BYE, &bb, b);
+ p_txend(p);
+ }
+
a_notify("KILL", "%s", p->spec.name, A_END);
ksl_free(&p->ks);
kx_free(&p->kx);
if (p->ifname) xfree(p->ifname);
if (p->spec.tag) xfree(p->spec.tag);
if (p->spec.privtag) xfree(p->spec.privtag);
+ if (p->spec.knock) xfree(p->spec.knock);
p->t->ops->destroy(p->t);
if (p->spec.t_ka) sel_rmtimer(&p->tka);
for (pg = p->pings; pg; pg = ppg) {
return (0);
}
+/*----- Rate limiting -----------------------------------------------------*/
+
+/* --- @ratelim_init@ --- *
+ *
+ * Arguments: @ratelim *r@ = rate-limiting state to fill in
+ * @unsigned persec@ = credit to accumulate per second
+ * @unsigned max@ = maximum credit to retain
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a rate-limiting state.
+ */
+
+void ratelim_init(ratelim *r, unsigned persec, unsigned max)
+{
+ r->n = r->max = max;
+ r->persec = persec;
+ gettimeofday(&r->when, 0);
+}
+
+/* --- @ratelim_withdraw@ --- *
+ *
+ * Arguments: @ratelim *r@ = rate-limiting state
+ * @unsigned n@ = credit to withdraw
+ *
+ * Returns: Zero if successful; @-1@ if there is unsufficient credit
+ *
+ * Use: Updates the state with any accumulated credit. Then, if
+ * there there are more than @n@ credits available, withdraw @n@
+ * and return successfully; otherwise, report failure.
+ */
+
+int ratelim_withdraw(ratelim *r, unsigned n)
+{
+ struct timeval now, delta;
+ unsigned long d;
+
+ gettimeofday(&now, 0);
+ TV_SUB(&delta, &now, &r->when);
+ d = (unsigned long)r->persec*delta.tv_sec +
+ (unsigned long)r->persec*delta.tv_usec/MILLION;
+ if (d < r->max - r->n) r->n += d;
+ else r->n = r->max;
+ r->when = now;
+
+ if (n > r->n) return (-1);
+ else { r->n -= n; return (0); }
+}
+
+/*----- Crypto ------------------------------------------------------------*/
+
+/* --- @ies_encrypt@ --- *
+ *
+ * Arguments: @kdata *kpub@ = recipient's public key
+ * @unsigned ty@ = message type octet
+ * @buf *b@ = input message buffer
+ * @buf *bb@ = output buffer for the ciphertext
+ *
+ * Returns: On error, returns a @KSERR_...@ code or breaks the buffer;
+ * on success, returns zero and the buffer is good.
+ *
+ * Use: Encrypts a message for a recipient, given their public key.
+ * This does not (by itself) provide forward secrecy or sender
+ * authenticity. The ciphertext is self-delimiting (unlike
+ * @ks_encrypt@).
+ */
+
+int ies_encrypt(kdata *kpub, unsigned ty, buf *b, buf *bb)
+{
+ dhgrp *g = kpub->grp;
+ dhsc *u = g->ops->randsc(g);
+ dhge *U = g->ops->mul(g, u, 0), *Z = g->ops->mul(g, u, kpub->K);
+ bulkalgs *algs = kpub->algs.bulk;
+ octet *len;
+ bulkctx *bulk;
+ deriveargs a;
+ size_t n;
+ buf bk;
+ int rc = 0;
+
+ IF_TRACING(T_CRYPTO, {
+ trace(T_CRYPTO,
+ "crypto: encrypting IES message (type 0x%02x) for recipient `%s'",
+ ty, kpub->tag);
+ trace_block(T_CRYPTO, "crypto: plaintext message", BCUR(b), BLEFT(b));
+ })
+
+ a.hc = kpub->algs.h; a.what = "tripe:ecies-"; a.f = DF_OUT;
+ buf_init(&bk, buf_u, sizeof(buf_u)); a.k = BBASE(&bk);
+ g->ops->stge(g, &bk, U, DHFMT_HASH); a.x = a.y = BLEN(&bk);
+ g->ops->stge(g, &bk, Z, DHFMT_HASH); a.z = BLEN(&bk);
+ assert(BOK(&bk));
+ T( trace_block(T_CRYPTO, "crypto: KEM clue", a.k, a.x);
+ trace_block(T_CRYPTO, "crypto: shared secret", a.k + a.y, a.z - a.y); )
+
+ len = BCUR(bb); buf_get(bb, 2);
+ bulk = algs->ops->genkeys(algs, &a);
+ bulk->ops = algs->ops;
+ g->ops->stge(g, bb, U, DHFMT_VAR); if (BBAD(bb)) goto end;
+ rc = bulk->ops->encrypt(bulk, ty, b, bb, 0);
+ if (rc || BBAD(bb)) goto end;
+ n = BCUR(bb) - len - 2; assert(n <= MASK16); STORE16(len, n);
+
+end:
+ bulk->ops->freectx(bulk);
+ g->ops->freesc(g, u);
+ g->ops->freege(g, U);
+ g->ops->freege(g, Z);
+ return (rc);
+}
+
+/* --- @ies_decrypt@ --- *
+ *
+ * Arguments: @kdata *kpub@ = private key key
+ * @unsigned ty@ = message type octet
+ * @buf *b@ = input ciphertext buffer
+ * @buf *bb@ = output buffer for the message
+ *
+ * Returns: On error, returns a @KSERR_...@ code; on success, returns
+ * zero and the buffer is good.
+ *
+ * Use: Decrypts a message encrypted using @ies_encrypt@, given our
+ * private key.
+ */
+
+int ies_decrypt(kdata *kpriv, unsigned ty, buf *b, buf *bb)
+{
+ dhgrp *g = kpriv->grp;
+ bulkalgs *algs = kpriv->algs.bulk;
+ bulkctx *bulk = 0;
+ T( const octet *m; )
+ dhge *U = 0, *Z = 0;
+ deriveargs a;
+ uint32 seq;
+ buf bk, bc;
+ int rc;
+
+ IF_TRACING(T_CRYPTO, {
+ trace(T_CRYPTO,
+ "crypto: decrypting IES message (type 0x%02x) to recipient `%s'",
+ ty, kpriv->tag);
+ trace_block(T_CRYPTO, "crypto: ciphertext message", BCUR(b), BLEFT(b));
+ })
+
+ if (buf_getbuf16(b, &bc) ||
+ (U = g->ops->ldge(g, &bc, DHFMT_VAR)) == 0 ||
+ g->ops->checkge(g, U))
+ { rc = KSERR_MALFORMED; goto end; }
+ Z = g->ops->mul(g, kpriv->k, U);
+
+ a.hc = kpriv->algs.h; a.what = "tripe:ecies-"; a.f = DF_IN;
+ buf_init(&bk, buf_u, sizeof(buf_u)); a.k = BBASE(&bk); a.x = 0;
+ g->ops->stge(g, &bk, U, DHFMT_HASH); a.y = BLEN(&bk);
+ g->ops->stge(g, &bk, Z, DHFMT_HASH); a.z = BLEN(&bk);
+ T( trace_block(T_CRYPTO, "crypto: KEM clue", a.k + a.x, a.y - a.x);
+ trace_block(T_CRYPTO, "crypto: shared secret", a.k + a.y, a.z - a.y); )
+ assert(BOK(&bk));
+
+ bulk = algs->ops->genkeys(algs, &a);
+ bulk->ops = algs->ops;
+ T( m = BCUR(bb); )
+ rc = bulk->ops->decrypt(bulk, ty, &bc, bb, &seq);
+ if (rc) goto end;
+ if (seq) { rc = KSERR_SEQ; goto end; }
+ assert(BOK(bb));
+ T( trace_block(T_CRYPTO, "crypto: decrypted message", m, BCUR(bb) - m); )
+
+end:
+ if (bulk) bulk->ops->freectx(bulk);
+ g->ops->freege(g, U);
+ g->ops->freege(g, Z);
+ return (rc);
+}
+
/*----- Random odds and sods ----------------------------------------------*/
/* --- @timestr@ --- *
}
}
+ /*----- Address handling --------------------------------------------------*/
+
+ const struct addrfam aftab[] = {
+ #ifdef HAVE_LIBADNS
+ # define DEF(af, qf) { AF_##af, #af, adns_qf_##qf },
+ #else
+ # define DEF(af, qf) { AF_##af, #af },
+ #endif
+ ADDRFAM(DEF)
+ #undef DEF
+ };
+
+ /* --- @afix@ --- *
+ *
+ * Arguments: @int af@ = an address family code
+ *
+ * Returns: The index of the address family's record in @aftab@, or @-1@.
+ */
+
+ int afix(int af)
+ {
+ int i;
+
+ for (i = 0; i < NADDRFAM; i++)
+ if (af == aftab[i].af) return (i);
+ return (-1);
+ }
+
/* --- @addrsz@ --- *
*
* Arguments: @const addr *a@ = a network address
{
switch (a->sa.sa_family) {
case AF_INET: return (sizeof(a->sin));
+ case AF_INET6: return (sizeof(a->sin6));
+ default: abort();
+ }
+ }
+
+ /* --- @getport@, @setport@ --- *
+ *
+ * Arguments: @addr *a@ = a network address
+ * @unsigned port@ = port number to set
+ *
+ * Returns: ---
+ *
+ * Use: Retrieves or sets the port number in an address structure.
+ */
+
+ unsigned getport(addr *a)
+ {
+ switch (a->sa.sa_family) {
+ case AF_INET: return (ntohs(a->sin.sin_port)); break;
+ case AF_INET6: return (ntohs(a->sin6.sin6_port)); break;
+ default: abort();
+ }
+ }
+
+ void setport(addr *a, unsigned port)
+ {
+ switch (a->sa.sa_family) {
+ case AF_INET: a->sin.sin_port = htons(port); break;
+ case AF_INET6: a->sin6.sin6_port = htons(port); break;
default: abort();
}
}
Address family tokens are not case-sensitive on input; on output, they
are always in upper-case.
.PP
- At present, only one address family is understood.
+ The following address families are recognized.
+ .TP
+ .BI "ANY " address " \fR[" port \fR]
+ An address and port number for any supported address family. On output,
+ .B tripe
+ never uses this form. On input, the
+ .I address
+ is examined: if it is a numeric address for some recognized address
+ family, then it is interpreted as such; otherwise it is looked up using
+ the DNS (in the background). The background resolver's address-sorting
+ rules apply, and
+ .B tripe
+ simply takes the first address in the returned list which is of a
+ supported address family. Symbolic port numbers are permitted; if
+ omitted, the default port 4070 is used.
.TP
.BI "INET " address " \fR[" port \fR]
An Internet socket, naming an IPv4 address and UDP port. On output, the
- address is always in numeric dotted-quad form, and the port is given as
- a plain number. On input, DNS hostnames and symbolic port names are
- permitted; if omitted, the default port 4070 is used. Name resolution
- does not block the main server, but will block the requesting client,
- unless the command is run in the background.
+ .I address
+ is always in numeric dotted-quad form, and the
+ .I port
+ is given as a plain decimal number. On input, DNS hostnames and
+ symbolic port names are permitted; if omitted, the default port 4070 is
+ used.
+ .TP
+ .BI "INET6 " address " \fR[" port \fR]
+ An Internet socket, naming an IPv6 address and UDP port. On output, the
+ .I address
+ is always in numeric hex-and-colons form, and the
+ .I port
+ is given as a plain decimal number. On input, DNS hostnames and
+ symbolic port names may be permitted, depending on how
+ .B tripe
+ was compiled; if omitted, the default port 4070 is used.
.PP
If, on input, no recognized address family token is found, the following
tokens are assumed to represent an
- .B INET
+ .B ANY
address. Addresses output by the server always have an address family
- token.
+ token, and do not use
+ .BR ANY .
+ .PP
+ Name resolution never blocks the main server, but will block the
+ requesting client, unless the command is run in the background.
.SS "Key-value output"
Some commands (e.g.,
.B STATS
Don't send an immediate challenge to the peer; instead, wait until it
sends us something before responding.
.TP
+.B "\-ephemeral"
+The association with the peer is not intended to persist indefinitely.
+If a peer marked as ephemeral is killed, or the
+.BR tripe (8)
+daemon is shut down, send a
+.B bye
+packet to the peer so that it forgets about us; if a peer marked as
+ephemeral sends us a
+.B bye
+packet then it is killed (but in this case no further
+.B bye
+packet is sent). Peers not marked as ephemeral exhibit neither of these
+behaviours; each peer must have the other marked as ephemeral for the
+association to be fully torn down if either end kills the other.
+.TP
.BI "\-keepalive " time
Send a no-op packet if we've not sent a packet to the peer in the last
.I time
to authenticate the peer. The default is to use the key tagged
.IR peer .
.TP
+.BI "\-knock \fR[" prefix .\fR] tag
+Send the string
+.RI [ prefix\fB. ] tag
+in
+.B token-rq
+and
+.B knock
+messages to the peer during key-exchange. The string as a whole should
+name the local machine to the peer, and
+.I tag
+should name its public key. When such messages are received from a
+currently unknown peer,
+.BR tripe (8)
+emits a
+.B KNOCK
+notification stating the peer's (claimed) name and address. The server
+will already have verified that the sender is using the peer's private
+key by this point. This option implies
+.BR \-ephemeral .
+.TP
.B "\-mobile"
The peer is a mobile device, and is likely to change address rapidly.
If a packet arrives from an unknown address, the server's usual response
and if one succeeds, the server will update its idea of the peer's
address and emit an
.B NEWADDR
-notification.
+notification. This option implies
+.BR \-ephemeral .
.TP
.BI "\-priv " tag
Use the private key
is the MTU of the path to the peer, then the tunnel MTU should be
.IP
.I MTU
- \- 29 \-
+ \-
+ .I header-length
+ \- 9 \-
.I bulk-overhead
.PP
- allowing 20 bytes of IP header, 8 bytes of UDP header, a packet type
- octet, and the bulk-crypto transform overhead (which includes the
- sequence number).
+ allowing
+ .I header-length
+ = 20 (IPv4) or 40 (IPv6) bytes of IP header, 8 bytes of UDP header, a
+ packet type octet, and the bulk-crypto transform overhead (which
+ includes the sequence number).
.RE
.SP
.BI "BGCANCEL " tag
The keepalive interval, in seconds, or zero if no keepalives are to be
sent.
.TP
+.B knock
+If present, the string sent to the peer to set up the association; see
+the
+.B \-knock
+option to
+.BR ADD ,
+and the
+.B KNOCK
+notification.
+.TP
.B key
The (short) key tag being used for the peer, as passed to the
.B ADD
.B nil
depending on whether or not (respectively) the peer is expected to
change its address unpredictably.
+.TP
+.B ephemeral
+Either
+.B t
+or
+.B nil
+depending on whether the association with the peer is expected to be
+temporary or persistent (respectively).
.RE
.SP
.BI "PING \fR[" options "\fR] " peer
.RE
.SP
.B "PORT"
+ .RI [ family ]
Emits an
.B INFO
line containing just the number of the UDP port used by the
.B tripe
- server. If you've allowed your server to allocate a port dynamically,
- this is how to find out which one it chose.
+ server, for the given address
+ .I family
+ (or one chosen arbitrarily if omitted -- though
+ .B tripe
+ tries to use the same port number consistently so this is not a likely
+ problem in practice). If you've allowed your server to allocate a port
+ dynamically, this is how to find out which one it chose.
.SP
.B "RELOAD"
Instructs the server to recheck its keyring files. The server checks
An error occurred during the attempt to become a daemon, as reported by
.IR message .
.SP
+ .BI "disabled-address-family " afam
+ (For
+ .B ADD
+ and
+ .BR PORT .)
+ The address family
+ .I afam
+ is supported, but was disabled using command-line arguments.
+ .SP
.BI "invalid-port " number
(For
.BR ADD .)
.I tag
is already the tag of an outstanding job.
.SP
+ .BI "unknown-address-family " afam
+ (For
+ .BR PORT .)
+ The address family
+ .I afam
+ is unrecognized.
+ .SP
.BI "unknown-command " token
The command
.I token
.I peer
has been killed.
.SP
+.BI "KNOCK " peer " " address
+The currently unknown
+.I peer
+is attempting to connect from
+.IR address .
+.SP
.BI "KXDONE " peer
Key exchange with
.I peer
.I tag
couldn't be found in the keyring.
.SP
+.BI "KEYMGMT " which "-keyring " file " unknown-key-id 0x" keyid
+A key with the given
+.I keyid
+(in hex) was requested but not found.
+.SP
.BI "KEYMGMT " which "-keyring " file " line " line " " message
The contents of the keyring file are invalid. There may well be a bug
in the
.BR challenge ,
.BR reply ,
.BR switch-rq ,
-or
.BR switch-ok .
+.BR token-rq ,
+.BR token ,
+or
+.BR knock .
.SP
.BI "KX " peer " algorithms-mismatch local-private-key " privtag " peer-public-key " pubtag
The algorithms specified in the peer's public key
An error occurred attempting to send a network packet. We lost that
one.
.SP
+.BI "PEER " address\fR... " socket-write-error " ecode " " message
+An error occurred attempting to send a network packet. We lost that
+one.
+.SP
.BI "PEER " peer " unexpected-encrypted-ping 0x" id
The peer sent an encrypted ping response whose id doesn't match any
outstanding ping. Maybe it was delayed for longer than the server was
-u, --usage Display pointless usage message.\n\
--tunnels Display IP tunnel drivers and exit.\n\
\n\
+ -4, --ipv4 Transport over IPv4 only.\n\
+ -6, --ipv6 Transport over IPv6 only.\n\
-D, --daemon Run in the background.\n\
-F, --foreground Quit when stdin reports end-of-file.\n\
-d, --directory=DIR Switch to directory DIR [default " CONFIGDIR "].\n\
int csockmode = 0600;
const char *dir = CONFIGDIR;
const char *p;
- unsigned port = TRIPE_PORT;
- struct in_addr baddr = { INADDR_ANY };
+ const char *bindhost = 0, *bindsvc = STR(TRIPE_PORT);
+ struct addrinfo aihint = { 0 }, *ailist;
unsigned f = 0;
int i;
- int selerr = 0;
+ int err, selerr = 0;
unsigned af;
struct timeval tv;
uid_t u = -1;
if ((p = getenv("TRIPESOCK")) != 0)
csock = p;
tun_default = tunnels[0];
+ aihint.ai_family = AF_UNSPEC;
for (;;) {
static const struct option opts[] = {
{ "usage", 0, 0, 'u' },
{ "tunnels", 0, 0, '0' },
+ { "ipv4", 0, 0, '4' },
+ { "ipv6", 0, 0, '6' },
{ "daemon", 0, 0, 'D' },
{ "foreground", 0, 0, 'F' },
{ "uid", OPTF_ARGREQ, 0, 'U' },
{ 0, 0, 0, 0 }
};
- i = mdwopt(argc, argv, "hvuDFU:G:b:n:p:d:k:K:t:a:m:" T("T:"),
+ i = mdwopt(argc, argv, "hvu46DFU:G:b:n:p:d:k:K:t:a:m:" T("T:"),
opts, 0, 0, 0);
if (i < 0)
break;
usage(stdout);
exit(0);
+ case '4':
+ aihint.ai_family = AF_INET;
+ break;
+ case '6':
+ aihint.ai_family = AF_INET6;
+ break;
case 'D':
f |= f_daemon;
break;
f |= f_foreground;
break;
- case 'b': {
- struct hostent *h = gethostbyname(optarg);
- if (!h)
- die(EXIT_FAILURE, "unknown host name `%s'", optarg);
- memcpy(&baddr, h->h_addr, sizeof(struct in_addr));
- } break;
- case 'p': {
- char *p;
- unsigned long i = strtoul(optarg, &p, 0);
- if (*p) {
- struct servent *s = getservbyname(optarg, "udp");
- if (!s)
- die(EXIT_FAILURE, "unknown service name `%s'", optarg);
- i = ntohs(s->s_port);
- }
- if (i >= 65536)
- die(EXIT_FAILURE, "bad port number %lu", i);
- port = i;
- } break;
+ case 'b':
+ bindhost = optarg;
+ break;
+ case 'p':
+ bindsvc = optarg;
+ break;
case 'n': {
int i;
for (i = 0;; i++) {
if (!(~f & (f_daemon | f_foreground)))
die(EXIT_FAILURE, "foreground operation for a daemon is silly");
+ aihint.ai_protocol = IPPROTO_UDP;
+ aihint.ai_socktype = SOCK_DGRAM;
+ aihint.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+ if ((err = getaddrinfo(bindhost, bindsvc, &aihint, &ailist)) != 0) {
+ die(EXIT_FAILURE, "couldn't resolve hostname %c%s%c, port `%s': %s",
+ bindhost ? '`' : '<',
+ bindhost ? bindhost : "nil",
+ bindhost ? '\'' : '>',
+ bindsvc, gai_strerror(err));
+ }
+
if (chdir(dir)) {
die(EXIT_FAILURE, "can't set current directory to `%s': %s",
dir, strerror(errno));
signal(SIGPIPE, SIG_IGN);
for (i = 0; tunnels[i]; i++)
tunnels[i]->init();
- p_init(baddr, port);
+ p_init(ailist); freeaddrinfo(ailist);
if (!(f & f_daemon)) {
af = AF_WARN;
#ifndef NTRACE
a_init(csock, u, g, csockmode);
u_setugid(u, g);
km_init(kr_priv, kr_pub, tag_priv);
+ kx_init();
if (f & f_daemon) {
if (daemonize())
die(EXIT_FAILURE, "couldn't become a daemon: %s", strerror(errno));
#include <pwd.h>
#include <grp.h>
+ #ifdef HAVE_LIBADNS
+ # define ADNS_FEATURE_MANYAF
+ # include <adns.h>
+ #endif
+
#include <mLib/alloc.h>
#include <mLib/arena.h>
#include <mLib/base64.h>
- #include <mLib/bres.h>
+ #ifndef HAVE_LIBADNS
+ # include <mLib/bres.h>
+ #endif
#include <mLib/codec.h>
#include <mLib/daemonize.h>
#include <mLib/dstr.h>
DHFMT_VAR /* Variable-width-format, mostly a bad idea */
};
+typedef struct deriveargs {
+ const char *what; /* Operation name (hashed) */
+ unsigned f; /* Flags */
+#define DF_IN 1u /* Make incoming key */
+#define DF_OUT 2u /* Make outgoing key */
+ const gchash *hc; /* Hash class */
+ const octet *k; /* Pointer to contributions */
+ size_t x, y, z; /* Markers in contributions */
+} deriveargs;
+
typedef struct bulkalgs {
const struct bulkops *ops;
} bulkalgs;
size_t tagsz;
} bulkchal;
-struct rawkey;
-
typedef struct dhops {
const char *name;
* after which the keys must no longer be used.
*/
- bulkctx *(*genkeys)(const bulkalgs */*a*/, const struct rawkey */*rk*/);
+ bulkctx *(*genkeys)(const bulkalgs */*a*/, const deriveargs */*a*/);
/* Generate session keys and construct and return an appropriate
- * context for using them, by calling @ks_derive@.
+ * context for using them. The offsets @a->x@, @a->y@ and @a->z@
+ * separate the key material into three parts. Between @a->k@ and
+ * @a->k + a->x@ is `my' contribution to the key material; between
+ * @a->k + a->x@ and @a->k + a->y@ is `your' contribution; and
+ * between @a->k + a->y@ and @a->k + a->z@ is a shared value we made
+ * together. These are used to construct (up to) two collections of
+ * symmetric keys: one for outgoing messages, the other for incoming
+ * messages. If @a->x == 0@ (or @a->y == a->x@) then my (or your)
+ * contribution is omitted.
*/
bulkchal *(*genchal)(const bulkalgs */*a*/);
/* Release a bulk encryption context and the resources it holds. */
int (*chaltag)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
- void */*t*/);
- /* Calculate a tag for the challenge in @m@, @msz@, and write it to
- * @t@. Return @-1@ on error, zero on success.
+ uint32 /*seq*/, void */*t*/);
+ /* Calculate a tag for the challenge in @m@, @msz@, with the sequence
+ * number @seq@, and write it to @t@. Return @-1@ on error, zero on
+ * success.
*/
int (*chalvrf)(bulkchal */*bc*/, const void */*m*/, size_t /*msz*/,
- const void */*t*/);
- /* Check the tag @t@ on @m@, @msz@: return zero if the tag is OK,
- * nonzero if it's bad.
+ uint32 /*seq*/, const void */*t*/);
+ /* Check the tag @t@ on @m@, @msz@ and @seq@: return zero if the tag
+ * is OK, nonzero if it's bad.
*/
void (*freechal)(bulkchal */*bc*/);
struct kdata {
unsigned ref; /* Reference counter */
struct knode *kn; /* Pointer to cache entry */
+ uint32 id; /* The underlying key's id */
char *tag; /* Full tag name of the key */
dhgrp *grp; /* The group we work in */
dhsc *k; /* The private key (or null) */
/*----- Data structures ---------------------------------------------------*/
+ /* --- The address-family table --- */
+
+ #define ADDRFAM(_) \
+ _(INET, want_ipv4) \
+ _(INET6, want_ipv6)
+
+ enum {
+ #define ENUM(af, qf) AFIX_##af,
+ ADDRFAM(ENUM)
+ #undef ENUM
+ NADDRFAM
+ };
+
+ extern const struct addrfam {
+ int af;
+ const char *name;
+ #ifdef HAVE_LIBADNS
+ adns_queryflags qf;
+ #endif
+ } aftab[NADDRFAM];
+
/* --- Socket addresses --- *
*
* A magic union of supported socket addresses.
typedef union addr {
struct sockaddr sa;
struct sockaddr_in sin;
+ struct sockaddr_in6 sin6;
} addr;
/* --- Mapping keyed on addresses --- */
char *name; /* Peer's name */
char *privtag; /* Private key tag */
char *tag; /* Public key tag */
+ char *knock; /* Knock string, or null */
const tunnel_ops *tops; /* Tunnel operations */
unsigned long t_ka; /* Keep alive interval */
addr sa; /* Socket address to speak to */
unsigned f; /* Flags for the peer */
#define PSF_KXMASK 255u /* Key-exchange flags to set */
#define PSF_MOBILE 256u /* Address may change rapidly */
+#define PSF_EPHEM 512u /* Association is ephemeral */
} peerspec;
typedef struct peer_byname {
peer_byaddr *byaddr; /* Lookup-by-address block */
struct ping *pings; /* Pings we're waiting for */
peerspec spec; /* Specifications for this peer */
+ int afix; /* Index of address family */
tunnel *t; /* Tunnel for local packets */
char *ifname; /* Interface name for tunnel */
keyset *ks; /* List head for keysets */
typedef struct admin_resop {
admin_bgop bg; /* Background operation header */
char *addr; /* Hostname to be resolved */
+ #ifdef HAVE_LIBADNS
+ adns_query q;
+ #else
bres_client r; /* Background resolver task */
+ #endif
sel_timer t; /* Timer for resolver */
addr sa; /* Socket address */
+ unsigned port; /* Port number chosen */
size_t sasz; /* Socket address size */
void (*func)(struct admin_resop *, int); /* Handler */
} admin_resop;
extern octet buf_i[PKBUFSZ], buf_o[PKBUFSZ], buf_t[PKBUFSZ], buf_u[PKBUFSZ];
extern const tunnel_ops *tunnels[]; /* Table of tunnels (0-term) */
extern const tunnel_ops *tun_default; /* Default tunnel to use */
+ extern sel_file udpsock[NADDRFAM]; /* The master UDP sockets */
extern kdata *master; /* Default private key */
extern const char *tag_priv; /* Default private key tag */
extern kdata *km_findpub(const char */*tag*/);
extern kdata *km_findpriv(const char */*tag*/);
+/* --- @km_findpubbyid@, @km_findprivbyid@ --- *
+ *
+ * Arguments: @uint32 id@ = key id to load
+ *
+ * Returns: Pointer to the kdata object if successful, or null on error.
+ *
+ * Use: Fetches a public or private key from the keyring given its
+ * numeric id.
+ */
+
+extern kdata *km_findpubbyid(uint32 /*id*/);
+extern kdata *km_findprivbyid(uint32 /*id*/);
+
/* --- @km_samealgsp@ --- *
*
* Arguments: @const kdata *kdx, *kdy@ = two key data objects
/* --- @kx_message@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
+ * @const addr *a@ = sender's IP address and port
* @unsigned msg@ = the message code
* @buf *b@ = pointer to buffer containing the packet
*
- * Returns: ---
+ * Returns: Nonzero if the sender's address was unknown.
*
* Use: Reads a packet containing key exchange messages and handles
* it.
*/
-extern void kx_message(keyexch */*kx*/, unsigned /*msg*/, buf */*b*/);
+extern int kx_message(keyexch */*kx*/, const addr */*a*/,
+ unsigned /*msg*/, buf */*b*/);
/* --- @kx_free@ --- *
*
extern void kx_newkeys(keyexch */*kx*/);
-/* --- @kx_init@ --- *
+/* --- @kx_setup@ --- *
*
* Arguments: @keyexch *kx@ = pointer to key exchange context
* @peer *p@ = pointer to peer context
* exchange.
*/
-extern int kx_init(keyexch */*kx*/, peer */*p*/,
- keyset **/*ks*/, unsigned /*f*/);
-
-/*----- Keysets and symmetric cryptography --------------------------------*/
+extern int kx_setup(keyexch */*kx*/, peer */*p*/,
+ keyset **/*ks*/, unsigned /*f*/);
-/* --- @ks_drop@ --- *
+/* --- @kx_init@ --- *
*
- * Arguments: @keyset *ks@ = pointer to a keyset
+ * Arguments: ---
*
* Returns: ---
*
- * Use: Decrements a keyset's reference counter. If the counter hits
- * zero, the keyset is freed.
+ * Use: Initializes the key-exchange logic.
*/
-extern void ks_drop(keyset */*ks*/);
+extern void kx_init(void);
-/* --- @ks_derivekey@ --- *
+/*----- Keysets and symmetric cryptography --------------------------------*/
+
+/* --- @ks_drop@ --- *
*
- * Arguments: @octet *k@ = pointer to an output buffer of at least
- * @MAXHASHSZ@ bytes
- * @size_t ksz@ = actual size wanted (for tracing)
- * @const struct rawkey *rk@ = a raw key, as passed into
- * @genkeys@
- * @int dir@ = direction for the key (@DIR_IN@ or @DIR_OUT@)
- * @const char *what@ = label for the key (input to derivation)
+ * Arguments: @keyset *ks@ = pointer to a keyset
*
* Returns: ---
*
- * Use: Derives a session key, for use on incoming or outgoing data.
- * This function is part of a private protocol between @ks_gen@
- * and the bulk crypto transform @genkeys@ operation.
+ * Use: Decrements a keyset's reference counter. If the counter hits
+ * zero, the keyset is freed.
*/
-extern void ks_derivekey(octet */*k*/, size_t /*ksz*/,
- const struct rawkey */*rk*/,
- int /*dir*/, const char */*what*/);
+extern void ks_drop(keyset */*ks*/);
/* --- @ks_gen@ --- *
*
- * Arguments: @const void *k@ = pointer to key material
- * @size_t x, y, z@ = offsets into key material (see below)
+ * Arguments: @deriveargs *a@ = key derivation parameters (modified)
* @peer *p@ = pointer to peer information
*
* Returns: A pointer to the new keyset.
*
- * Use: Derives a new keyset from the given key material. The
- * offsets @x@, @y@ and @z@ separate the key material into three
- * parts. Between the @k@ and @k + x@ is `my' contribution to
- * the key material; between @k + x@ and @k + y@ is `your'
- * contribution; and between @k + y@ and @k + z@ is a shared
- * value we made together. These are used to construct two
- * collections of symmetric keys: one for outgoing messages, the
- * other for incoming messages.
+ * Use: Derives a new keyset from the given key material. This will
+ * set the @what@, @f@, and @hc@ members in @*a@; other members
+ * must be filled in by the caller.
*
* The new key is marked so that it won't be selected for output
* by @ksl_encrypt@. You can still encrypt data with it by
* calling @ks_encrypt@ directly.
*/
-extern keyset *ks_gen(const void */*k*/,
- size_t /*x*/, size_t /*y*/, size_t /*z*/,
- peer */*p*/);
+extern keyset *ks_gen(deriveargs */*a*/, peer */*p*/);
/* --- @ks_activate@ --- *
*
/* --- @c_new@ --- *
*
- * Arguments: @buf *b@ = where to put the challenge
+ * Arguments: @const void *m@ = pointer to associated message, or null
+ * @size_t msz@ = length of associated message
+ * @buf *b@ = where to put the challenge
*
* Returns: Zero if OK, nonzero on error.
*
* Use: Issues a new challenge.
*/
-extern int c_new(buf */*b*/);
+extern int c_new(const void */*m*/, size_t /*msz*/, buf */*b*/);
/* --- @c_check@ --- *
*
- * Arguments: @buf *b@ = where to find the challenge
+ * Arguments: @const void *m@ = pointer to associated message, or null
+ * @size_t msz@ = length of associated message
+ * @buf *b@ = where to find the challenge
*
* Returns: Zero if OK, nonzero if it didn't work.
*
* Use: Checks a challenge. On failure, the buffer is broken.
*/
-extern int c_check(buf */*b*/);
+extern int c_check(const void */*m*/, size_t /*msz*/, buf */*b*/);
/*----- Administration interface ------------------------------------------*/
extern buf *p_txstart(peer */*p*/, unsigned /*msg*/);
+/* --- @p_txaddr@ --- *
+ *
+ * Arguments: @const addr *a@ = recipient address
+ * @const void *p@ = pointer to packet to send
+ * @size_t sz@ = length of packet
+ *
+ * Returns: Zero if successful, nonzero on error.
+ *
+ * Use: Sends a packet to an address which (possibly) isn't a current
+ * peer.
+ */
+
+extern int p_txaddr(const addr */*a*/, const void */*p*/, size_t /*sz*/);
+
/* --- @p_txend@ --- *
*
* Arguments: @peer *p@ = pointer to peer block
/* --- @p_init@ --- *
*
- * Arguments: @struct in_addr addr@ = address to bind to
- * @unsigned port@ = port number to listen to
+ * Arguments: @struct addrinfo *ailist@ = addresses to bind to
*
* Returns: ---
*
* Use: Initializes the peer system; creates the socket.
*/
- extern void p_init(struct in_addr /*addr*/, unsigned /*port*/);
+ extern void p_init(struct addrinfo */*ailist*/);
/* --- @p_port@ --- *
*
- * Arguments: ---
+ * Arguments: @int i@ = address family index to retrieve
*
* Returns: Port number used for socket.
*/
- unsigned p_port(void);
+ extern unsigned p_port(int /*i*/);
/* --- @p_create@ --- *
*
/* --- @p_destroy@ --- *
*
* Arguments: @peer *p@ = pointer to a peer
+ * @int bye@ = say goodbye to the peer?
*
* Returns: ---
*
* Use: Destroys a peer.
*/
-extern void p_destroy(peer */*p*/);
+extern void p_destroy(peer */*p*/, int /*bye*/);
/* --- @FOREACH_PEER@ --- *
*
extern int mystrieq(const char */*x*/, const char */*y*/);
+ /* --- @afix@ --- *
+ *
+ * Arguments: @int af@ = an address family code
+ *
+ * Returns: The index of the address family's record in @aftab@, or @-1@.
+ */
+
+ extern int afix(int af);
+
/* --- @addrsz@ --- *
*
* Arguments: @const addr *a@ = a network address
extern socklen_t addrsz(const addr */*a*/);
+ /* --- @getport@, @setport@ --- *
+ *
+ * Arguments: @addr *a@ = a network address
+ * @unsigned port@ = port number to set
+ *
+ * Returns: ---
+ *
+ * Use: Retrieves or sets the port number in an address structure.
+ */
+
+ extern unsigned getport(addr */*a*/);
+ extern void setport(addr */*a*/, unsigned /*port*/);
+
/* --- @seq_reset@ --- *
*
* Arguments: @seqwin *s@ = sequence-checking window
extern int seq_check(seqwin */*s*/, uint32 /*q*/, const char */*service*/);
+typedef struct ratelim {
+ unsigned n, max, persec;
+ struct timeval when;
+} ratelim;
+
+/* --- @ratelim_init@ --- *
+ *
+ * Arguments: @ratelim *r@ = rate-limiting state to fill in
+ * @unsigned persec@ = credit to accumulate per second
+ * @unsigned max@ = maximum credit to retain
+ *
+ * Returns: ---
+ *
+ * Use: Initialize a rate-limiting state.
+ */
+
+extern void ratelim_init(ratelim */*r*/,
+ unsigned /*persec*/, unsigned /*max*/);
+
+/* --- @ratelim_withdraw@ --- *
+ *
+ * Arguments: @ratelim *r@ = rate-limiting state
+ * @unsigned n@ = credit to withdraw
+ *
+ * Returns: Zero if successful; @-1@ if there is unsufficient credit
+ *
+ * Use: Updates the state with any accumulated credit. Then, if
+ * there there are more than @n@ credits available, withdraw @n@
+ * and return successfully; otherwise, report failure.
+ */
+
+extern int ratelim_withdraw(ratelim */*r*/, unsigned /*n*/);
+
+/* --- @ies_encrypt@ --- *
+ *
+ * Arguments: @kdata *kpub@ = recipient's public key
+ * @unsigned ty@ = message type octet
+ * @buf *b@ = input message buffer
+ * @buf *bb@ = output buffer for the ciphertext
+ *
+ * Returns: On error, returns a @KSERR_...@ code or breaks the buffer;
+ * on success, returns zero and the buffer is good.
+ *
+ * Use: Encrypts a message for a recipient, given their public key.
+ * This does not (by itself) provide forward secrecy or sender
+ * authenticity. The ciphertext is self-delimiting (unlike
+ * @ks_encrypt@).
+ */
+
+extern int ies_encrypt(kdata */*kpub*/, unsigned /*ty*/,
+ buf */*b*/, buf */*bb*/);
+
+/* --- @ies_decrypt@ --- *
+ *
+ * Arguments: @kdata *kpub@ = private key key
+ * @unsigned ty@ = message type octet
+ * @buf *b@ = input ciphertext buffer
+ * @buf *bb@ = output buffer for the message
+ *
+ * Returns: On error, returns a @KSERR_...@ code; on success, returns
+ * zero and the buffer is good.
+ *
+ * Use: Decrypts a message encrypted using @ies_encrypt@, given our
+ * private key.
+ */
+
+extern int ies_decrypt(kdata */*kpriv*/, unsigned /*ty*/,
+ buf */*b*/, buf */*bb*/);
+
/*----- That's all, folks -------------------------------------------------*/
#ifdef __cplusplus