-/* -*-c-*-
- *
- * $Id$
- *
- * Communication with the peer
- *
- * (c) 2001 Straylight/Edgeware
- */
-
-/*----- Licensing notice --------------------------------------------------*
- *
- * This file is part of Trivial IP Encryption (TrIPE).
- *
- * TrIPE is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * TrIPE is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with TrIPE; if not, write to the Free Software Foundation,
- * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- */
-
-/*----- Header files ------------------------------------------------------*/
-
-#include "tripe.h"
-
-/*----- Static variables --------------------------------------------------*/
-
-static peer *peers = 0;
-static sel_file sock;
-
-/*----- Tunnel table ------------------------------------------------------*/
-
-const tunnel_ops *tunnels[] = {
-#ifdef TUN_LINUX
- &tun_linux,
-#endif
-#ifdef TUN_BSD
- &tun_bsd,
-#endif
-#ifdef TUN_UNET
- &tun_unet,
-#endif
- &tun_slip,
- 0
-}, *tun_default;
-
-/*----- Main code ---------------------------------------------------------*/
-
-/* --- @p_pingtype@ --- *
- *
- * Arguments: @unsigned msg@ = message type
- *
- * Returns: String to describe the message.
- */
-
-static const char *p_pingtype(unsigned msg)
-{
- switch (msg & MSG_TYPEMASK) {
- case MISC_PING:
- case MISC_PONG:
- return "transport-ping";
- case MISC_EPING:
- case MISC_EPONG:
- return "encrypted-ping";
- default:
- abort();
- }
-}
-
-/* --- @p_ponged@ --- *
- *
- * Arguments: @peer *p@ = peer packet arrived from
- * @unsigned msg@ = message type
- * @buf *b@ = buffer containing payload
- *
- * Returns: ---
- *
- * Use: Processes a ping response.
- */
-
-static void p_ponged(peer *p, unsigned msg, buf *b)
-{
- uint32 id;
- const octet *magic;
- ping *pg;
-
- IF_TRACING(T_PEER, {
- trace(T_PEER, "peer: received %s reply from %s",
- p_pingtype(msg), p->spec.name);
- trace_block(T_PACKET, "peer: ping contents", BBASE(b), BSZ(b));
- })
-
- if (buf_getu32(b, &id) ||
- (magic = buf_get(b, sizeof(pg->magic))) == 0 ||
- BLEFT(b)) {
- a_warn("PEER", "?PEER", p, "malformed-%s", p_pingtype(msg), A_END);
- return;
- }
-
- for (pg = p->pings; pg; pg = pg->next) {
- if (pg->id == id)
- goto found;
- }
- a_warn("PEER",
- "?PEER", p,
- "unexpected-%s", p_pingtype(msg),
- "0x%08lx", (unsigned long)id,
- A_END);
- return;
-
-found:
- if (memcmp(magic, pg->magic, sizeof(pg->magic)) != 0) {
- a_warn("PEER", "?PEER", p, "corrupt-%s", p_pingtype(msg), A_END);
- return;
- }
- p_pingdone(pg, PING_OK);
-}
-
-/* --- @p_read@ --- *
- *
- * Arguments: @int fd@ = file descriptor to read from
- * @unsigned mode@ = what happened
- * @void *v@ = an uninteresting pointer
- *
- * Returns: ---
- *
- * Use: Reads a packet from somewhere.
- */
-
-static void p_read(int fd, unsigned mode, void *v)
-{
- peer *p = 0;
- addr a;
- size_t sz;
- ssize_t n;
- int ch;
- buf b, bb;
-
- /* --- Read the data --- */
-
- TIMER;
- sz = sizeof(addr);
- n = recvfrom(fd, buf_i, sizeof(buf_i), 0, &a.sa, &sz);
- if (n < 0) {
- a_warn("PEER", "-", "socket-read-error", "?ERRNO", A_END);
- return;
- }
-
- /* --- 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_block(T_PACKET, "peer: greeting contents", buf_i, n);
- })
- buf_init(&b, buf_i, n);
- buf_getbyte(&b);
- if (c_check(&b) || BLEFT(&b)) {
- a_warn("PEER", "-", "invalid-greeting", A_END);
- return;
- }
- a_notify("GREET",
- "?B64", buf_i + 1, (size_t)(n - 1),
- "?ADDR", &a,
- A_END);
- return;
- }
-
- /* --- Find the appropriate peer --- */
-
- assert(a.sa.sa_family == AF_INET);
- for (p = peers; p; p = p->next) {
- if (p->spec.sa.sin.sin_addr.s_addr == a.sin.sin_addr.s_addr &&
- p->spec.sa.sin.sin_port == a.sin.sin_port)
- goto found;
- }
- a_warn("PEER", "-", "unexpected-source", "?ADDR", &a, A_END);
- return;
-
-found:
- IF_TRACING(T_PEER, {
- trace(T_PEER, "peer: packet received from `%s'", p->spec.name);
- trace_block(T_PACKET, "peer: packet contents", buf_i, n);
- })
-
- /* --- Pick the packet apart --- */
-
- p->st.t_last = time(0);
- p->st.n_in++;
- p->st.sz_in += n;
- buf_init(&b, buf_i, n);
- if ((ch = buf_getbyte(&b)) < 0) {
- a_warn("PEER", "?PEER", p, "bad-packet", "no-type", A_END);
- return;
- }
- switch (ch & MSG_CATMASK) {
- case MSG_PACKET:
- if (ch & MSG_TYPEMASK) {
- a_warn("PEER",
- "?PEER", p,
- "bad-packet",
- "unknown-type", "0x%02x", ch,
- A_END);
- p->st.n_reject++;
- return;
- }
- buf_init(&bb, buf_o, sizeof(buf_o));
- if (ksl_decrypt(&p->ks, MSG_PACKET, &b, &bb)) {
- p->st.n_reject++;
- a_warn("PEER", "?PEER", p, "decrypt-failed", A_END);
- return;
- }
- if (BOK(&bb)) {
- p->st.n_ipin++;
- p->st.sz_ipin += BSZ(&b);
- p->t->ops->inject(p->t, &bb);
- } else {
- p->st.n_reject++;
- a_warn("PEER", "?PEER", p, "packet-build-failed", A_END);
- }
- break;
- case MSG_KEYEXCH:
- kx_message(&p->kx, ch & MSG_TYPEMASK, &b);
- break;
- case MSG_MISC:
- switch (ch & MSG_TYPEMASK) {
- case MISC_NOP:
- T( trace(T_PEER, "peer: received NOP packet"); )
- break;
- case MISC_PING:
- buf_put(p_txstart(p, MSG_MISC | MISC_PONG), BCUR(&b), BLEFT(&b));
- p_txend(p);
- break;
- case MISC_PONG:
- p_ponged(p, MISC_PONG, &b);
- break;
- case MISC_EPING:
- buf_init(&bb, buf_t, sizeof(buf_t));
- if (ksl_decrypt(&p->ks, ch, &b, &bb)) {
- p->st.n_reject++;
- a_warn("PEER", "?PEER", "decrypt-failed", A_END);
- return;
- }
- if (BOK(&bb)) {
- buf_flip(&bb);
- if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPONG, &bb,
- p_txstart(p, MSG_MISC | MISC_EPONG)))
- kx_start(&p->kx, 0);
- p_txend(p);
- }
- break;
- case MISC_EPONG:
- buf_init(&bb, buf_t, sizeof(buf_t));
- if (ksl_decrypt(&p->ks, ch, &b, &bb)) {
- p->st.n_reject++;
- a_warn("PEER", "?PEER", p, "decrypt-failed", A_END);
- return;
- }
- if (BOK(&bb)) {
- buf_flip(&bb);
- p_ponged(p, MISC_EPONG, &bb);
- }
- break;
- }
- break;
- default:
- p->st.n_reject++;
- a_warn("PEER",
- "?PEER", p,
- "bad-packet",
- "unknown-category" "0x%02x", ch,
- A_END);
- break;
- }
-}
-
-/* --- @p_txstart@ --- *
- *
- * Arguments: @peer *p@ = pointer to peer block
- * @unsigned msg@ = message type code
- *
- * Returns: A pointer to a buffer to write to.
- *
- * Use: Starts sending to a peer. Only one send can happen at a
- * time.
- */
-
-buf *p_txstart(peer *p, unsigned msg)
-{
- buf_init(&p->b, buf_o, sizeof(buf_o));
- buf_putbyte(&p->b, msg);
- return (&p->b);
-}
-
-/* --- @p_txend@ --- *
- *
- * Arguments: @peer *p@ = pointer to peer block
- *
- * Returns: ---
- *
- * Use: Sends a packet to the peer.
- */
-
-static void p_setkatimer(peer *);
-
-static int p_dotxend(peer *p)
-{
- if (!BOK(&p->b)) {
- a_warn("PEER", "?PEER", p, "packet-build-failed", A_END);
- return (0);
- }
- 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),
- 0, &p->spec.sa.sa, p->spec.sasz) < 0) {
- a_warn("PEER", "?PEER", p, "socket-write-error", "?ERRNO", A_END);
- return (0);
- } else {
- p->st.n_out++;
- p->st.sz_out += BLEN(&p->b);
- return (1);
- }
-}
-
-void p_txend(peer *p)
-{
- if (p_dotxend(p) && p->spec.t_ka) {
- sel_rmtimer(&p->tka);
- p_setkatimer(p);
- }
-}
-
-/* --- @p_pingwrite@ --- *
- *
- * Arguments: @ping *p@ = ping structure
- * @buf *b@ = buffer to write in
- *
- * Returns: ---
- *
- * Use: Fills in a ping structure and writes the packet payload.
- */
-
-static void p_pingwrite(ping *p, buf *b)
-{
- static uint32 seq = 0;
-
- p->id = U32(seq++);
- GR_FILL(&rand_global, p->magic, sizeof(p->magic));
- buf_putu32(b, p->id);
- buf_put(b, p->magic, sizeof(p->magic));
-}
-
-/* --- @p_pingdone@ --- *
- *
- * Arguments: @ping *p@ = ping structure
- * @int rc@ = return code to pass on
- *
- * Returns: ---
- *
- * Use: Disposes of a ping structure, maybe sending a notification.
- */
-
-void p_pingdone(ping *p, int rc)
-{
- 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);
- T( trace(T_PEER, "peer: ping 0x%08lx done (rc = %d)",
- (unsigned long)p->id, rc); )
- if (rc >= 0) p->func(rc, p->arg);
-}
-
-/* --- @p_pingtimeout@ --- *
- *
- * Arguments: @struct timeval *now@ = the time now
- * @void *pv@ = pointer to ping block
- *
- * Returns: ---
- *
- * Use: Called when a ping times out.
- */
-
-static void p_pingtimeout(struct timeval *now, void *pv)
-{
- ping *p = pv;
-
- T( trace(T_PEER, "peer: ping 0x%08lx timed out", (unsigned long)p->id); )
- p_pingdone(p, PING_TIMEOUT);
-}
-
-/* --- @p_pingsend@ --- *
- *
- * Arguments: @peer *p@ = destination peer
- * @ping *pg@ = structure to fill in
- * @unsigned type@ = message type
- * @unsigned long timeout@ = how long to wait before giving up
- * @void (*func)(int, void *)@ = callback function
- * @void *arg@ = argument for callback
- *
- * Returns: Zero if successful, nonzero if it failed.
- *
- * Use: Sends a ping to a peer. Call @func@ with a nonzero argument
- * if we get an answer within the timeout, or zero if no answer.
- */
-
-int p_pingsend(peer *p, ping *pg, unsigned type,
- unsigned long timeout,
- void (*func)(int, void *), void *arg)
-{
- buf *b, bb;
- struct timeval tv;
-
- switch (type) {
- case MISC_PING:
- pg->msg = MISC_PONG;
- b = p_txstart(p, MSG_MISC | MISC_PING);
- p_pingwrite(pg, b);
- p_txend(p);
- break;
- case MISC_EPING:
- pg->msg = MISC_EPONG;
- b = p_txstart(p, MSG_MISC | MISC_EPING);
- buf_init(&bb, buf_t, sizeof(buf_t));
- p_pingwrite(pg, &bb);
- buf_flip(&bb);
- if (ksl_encrypt(&p->ks, MSG_MISC | MISC_EPING, &bb, b))
- kx_start(&p->kx, 0);
- if (!BOK(b))
- return (-1);
- p_txend(p);
- break;
- default:
- abort();
- break;
- }
-
- pg->next = p->pings;
- pg->prev = 0;
- 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);
- T( trace(T_PEER, "peer: send %s 0x%08lx to %s",
- p_pingtype(type), (unsigned long)pg->id, p->spec.name); )
- return (0);
-}
-
-/* --- @p_greet@ --- *
- *
- * Arguments: @peer *p@ = peer to send to
- * @const void *c@ = pointer to challenge
- * @size_t sz@ = size of challenge
- *
- * Returns: ---
- *
- * Use: Sends a greeting packet.
- */
-
-void p_greet(peer *p, const void *c, size_t sz)
-{
- buf *b = p_txstart(p, MSG_MISC | MISC_GREET);
- buf_put(b, c, sz);
- p_txend(p);
-}
-
-/* --- @p_tun@ --- *
- *
- * Arguments: @peer *p@ = pointer to peer block
- * @buf *b@ = buffer containing incoming packet
- *
- * Returns: ---
- *
- * Use: Handles a packet which needs to be sent to a peer.
- */
-
-void p_tun(peer *p, buf *b)
-{
- buf *bb = p_txstart(p, MSG_PACKET);
-
- TIMER;
- if (ksl_encrypt(&p->ks, MSG_PACKET, b, bb))
- kx_start(&p->kx, 0);
- if (BOK(bb) && BLEN(bb)) {
- p->st.n_ipout++;
- p->st.sz_ipout += BLEN(bb);
- p_txend(p);
- }
-}
-
-/* --- @p_keyreload@ --- *
- *
- * Arguments: ---
- *
- * Returns: ---
- *
- * Use: Forces a check of the daemon's keyring files.
- */
-
-void p_keyreload(void)
-{
- peer *p;
-
- if (km_reload()) {
- for (p = peers; p; p = p->next)
- kx_newkeys(&p->kx);
- }
-}
-
-/* --- @p_interval@ --- *
- *
- * Arguments: ---
- *
- * Returns: ---
- *
- * Use: Called periodically to do tidying.
- */
-
-void p_interval(void)
-{
- peer *p;
-
- p_keyreload();
- for (p = peers; p; p = p->next)
- ksl_prune(&p->ks);
-}
-
-/* --- @p_stats@ --- *
- *
- * Arguments: @peer *p@ = pointer to a peer block
- *
- * Returns: A pointer to the peer's statistics.
- */
-
-stats *p_stats(peer *p) { return (&p->st); }
-
-/* --- @p_ifname@ --- *
- *
- * Arguments: @peer *p@ = pointer to a peer block
- *
- * Returns: A pointer to the peer's interface name.
- */
-
-const char *p_ifname(peer *p) { return (p->t->ops->ifname(p->t)); }
-
-/* --- @p_addr@ --- *
- *
- * Arguments: @peer *p@ = pointer to a peer block
- *
- * Returns: A pointer to the peer's address.
- */
-
-const addr *p_addr(peer *p) { return (&p->spec.sa); }
-
-/* --- @p_init@ --- *
- *
- * Arguments: @struct in_addr addr@ = address to bind to
- * @unsigned port@ = port number to listen to
- *
- * Returns: ---
- *
- * Use: Initializes the peer system; creates the socket.
- */
-
-void p_init(struct in_addr addr, unsigned port)
-{
- int fd;
- struct sockaddr_in sin;
- int len = PKBUFSZ;
-
- /* --- 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));
- }
- 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"); )
-}
-
-/* --- @p_port@ --- *
- *
- * Arguments: ---
- *
- * Returns: Port number used for socket.
- */
-
-unsigned p_port(void)
-{
- addr a;
- size_t sz = sizeof(addr);
-
- if (getsockname(sock.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));
-}
-
-/* --- @p_keepalive@ --- *
- *
- * Arguments: @struct timeval *now@ = the current time
- * @void *pv@ = peer to wake up
- *
- * Returns: ---
- *
- * Use: Sends a keepalive ping message to its peer.
- */
-
-static void p_keepalive(struct timeval *now, void *pv)
-{
- peer *p = pv;
- p_txstart(p, MSG_MISC | MISC_NOP); p_dotxend(p);
- T( trace(T_PEER, "peer: sent keepalive to %s", p->spec.name); )
- p_setkatimer(p);
-}
-
-/* --- @p_setkatimer@ --- *
- *
- * Arguments: @peer *p@ = peer to set
- *
- * Returns: ---
- *
- * Use: Resets the keepalive timer thing.
- */
-
-static void p_setkatimer(peer *p)
-{
- struct timeval tv;
-
- if (!p->spec.t_ka)
- return;
- gettimeofday(&tv, 0);
- tv.tv_sec += p->spec.t_ka;
- sel_addtimer(&sel, &p->tka, &tv, p_keepalive, p);
-}
-
-/* --- @p_create@ --- *
- *
- * Arguments: @peerspec *spec@ = information about this peer
- *
- * Returns: Pointer to the peer block, or null if it failed.
- *
- * Use: Creates a new named peer block. No peer is actually attached
- * by this point.
- */
-
-peer *p_create(peerspec *spec)
-{
- peer *p = CREATE(peer);
-
- T( trace(T_PEER, "peer: creating new peer `%s'", spec->name); )
- p->spec = *spec;
- p->spec.name = xstrdup(spec->name);
- p->ks = 0;
- p->prev = 0;
- p->pings = 0;
- memset(&p->st, 0, sizeof(stats));
- p->st.t_start = time(0);
- if ((p->t = spec->tops->create(p)) == 0)
- goto tidy_0;
- p_setkatimer(p);
- if (kx_init(&p->kx, p, &p->ks))
- goto tidy_1;
- p->next = peers;
- if (peers)
- peers->prev = p;
- peers = p;
- a_notify("ADD",
- "?PEER", p,
- "%s", p->t->ops->ifname(p->t),
- "?ADDR", &p->spec.sa,
- A_END);
- a_notify("KXSTART", "?PEER", p, A_END);
- /* Couldn't tell anyone before */
- return (p);
-
-tidy_1:
- if (spec->t_ka)
- sel_rmtimer(&p->tka);
- p->t->ops->destroy(p->t);
-tidy_0:
- xfree(p->spec.name);
- DESTROY(p);
- return (0);
-}
-
-/* --- @p_name@ --- *
- *
- * Arguments: @peer *p@ = pointer to a peer block
- *
- * Returns: A pointer to the peer's name.
- */
-
-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
- *
- * Returns: Pointer to the peer block, or null if not found.
- *
- * Use: Finds a peer by name.
- */
-
-peer *p_find(const char *name)
-{
- peer *p;
- for (p = peers; p; p = p->next) {
- if (strcmp(name, p->spec.name) == 0)
- return (p);
- }
- return (0);
-}
-
-/* --- @p_destroy@ --- *
- *
- * Arguments: @peer *p@ = pointer to a peer
- *
- * Returns: ---
- *
- * Use: Destroys a peer.
- */
-
-void p_destroy(peer *p)
-{
- ping *pg, *ppg;
-
- T( trace(T_PEER, "peer: destroying peer `%s'", p->spec.name); )
- a_notify("KILL", "%s", p->spec.name, A_END);
- ksl_free(&p->ks);
- kx_free(&p->kx);
- p->t->ops->destroy(p->t);
- if (p->spec.t_ka)
- sel_rmtimer(&p->tka);
- xfree(p->spec.name);
- for (pg = p->pings; pg; pg = ppg) {
- ppg = pg->next;
- p_pingdone(pg, PING_PEERDIED);
- }
- if (p->next)
- p->next->prev = p->prev;
- if (p->prev)
- p->prev->next = p->next;
- else
- peers = p->next;
- DESTROY(p);
-}
-
-/* --- @p_first@, @p_next@ --- *
- *
- * Arguments: @peer *p@ = a peer block
- *
- * Returns: @peer_first@ returns the first peer in some ordering;
- * @peer_next@ returns the peer following a given one in the
- * same ordering. Null is returned for the end of the list.
- */
-
-peer *p_first(void) { return (peers); }
-peer *p_next(peer *p) { return (p->next); }
-
-/*----- That's all, folks -------------------------------------------------*/