add the 'ead' package (emergency access daemon), which can provide remote access...
[openwrt/openwrt.git] / package / ead / src / ead.c
diff --git a/package/ead/src/ead.c b/package/ead/src/ead.c
new file mode 100644 (file)
index 0000000..7367c38
--- /dev/null
@@ -0,0 +1,931 @@
+/*
+ * Emergency Access Daemon
+ * Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/select.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <pcap.h>
+#include <pcap-bpf.h>
+#include <t_pwd.h>
+#include <t_read.h>
+#include <t_sha.h>
+#include <t_defines.h>
+#include <t_server.h>
+
+#include "list.h"
+#include "ead.h"
+#include "ead-pcap.h"
+#include "ead-crypt.h"
+
+#include "filter.c"
+
+#ifdef linux
+#include "libbridge_init.c"
+#endif
+
+#define PASSWD_FILE    "/etc/passwd"
+
+#ifndef DEFAULT_IFNAME
+#define DEFAULT_IFNAME "eth0"
+#endif
+
+#ifndef DEFAULT_DEVNAME
+#define DEFAULT_DEVNAME "Unknown"
+#endif
+
+#define PCAP_MRU               1600
+#define PCAP_TIMEOUT   200
+
+#if EAD_DEBUGLEVEL >= 1
+#define DEBUG(n, format, ...) do { \
+       if (EAD_DEBUGLEVEL >= n) \
+               fprintf(stderr, format, ##__VA_ARGS__); \
+} while (0);
+
+#else
+#define DEBUG(n, format, ...) do {} while(0)
+#endif
+
+static char ethmac[6] = "\x00\x13\x37\x00\x00\x00"; /* last 3 bytes will be randomized */
+static pcap_t *pcap_fp = NULL;
+static pcap_t *pcap_fp_rx = NULL;
+static const char *ifname = DEFAULT_IFNAME;
+static char pktbuf_b[PCAP_MRU];
+static struct ead_packet *pktbuf = (struct ead_packet *)pktbuf_b;
+static u16_t nid = 0xffff; /* node id */
+static char username[32] = "";
+static int state = EAD_TYPE_SET_USERNAME;
+static const char *passwd_file = PASSWD_FILE;
+static const char password[MAXPARAMLEN];
+static bool child_pending = false;
+
+static unsigned char abuf[MAXPARAMLEN + 1];
+static unsigned char pwbuf[MAXPARAMLEN];
+static unsigned char saltbuf[MAXSALTLEN];
+static unsigned char pw_saltbuf[MAXSALTLEN];
+static struct list_head instances;
+static const char *dev_name = DEFAULT_DEVNAME;
+static bool nonfork = false;
+
+#ifdef linux
+static const char *brname = NULL;
+#endif
+
+struct ead_instance {
+       struct list_head list;
+       char name[16];
+       int pid;
+#ifdef linux
+       char bridge[16];
+       bool br_check;
+#endif
+};
+
+static struct t_pwent tpe = {
+       .name = username,
+       .index = 1,
+       .password.data = pwbuf,
+       .password.len = 0,
+       .salt.data = saltbuf,
+       .salt.len = 0,
+};
+struct t_confent *tce = NULL;
+static struct t_server *ts = NULL;
+static struct t_num A, *B = NULL;
+unsigned char *skey;
+
+static bool
+prepare_password(void)
+{
+       static char lbuf[1024];
+       unsigned char dig[SHA_DIGESTSIZE];
+       BigInteger x, v, n, g;
+       SHA1_CTX ctxt;
+       int ulen = strlen(username);
+       FILE *f;
+
+       lbuf[sizeof(lbuf) - 1] = 0;
+
+       f = fopen(passwd_file, "r");
+       if (!f)
+               return false;
+
+       while (fgets(lbuf, sizeof(lbuf) - 1, f) != NULL) {
+               char *str, *s2;
+
+               if (strncmp(lbuf, username, ulen) != 0)
+                       continue;
+
+               if (lbuf[ulen] != ':')
+                       continue;
+
+               str = &lbuf[ulen + 1];
+
+               if (strncmp(str, "$1$", 3) != 0)
+                       continue;
+
+               s2 = strchr(str + 3, '$');
+               if (!s2)
+                       continue;
+
+               if (s2 - str >= MAXSALTLEN)
+                       continue;
+
+               strncpy((char *) pw_saltbuf, str, s2 - str);
+               pw_saltbuf[s2 - str] = 0;
+
+               s2 = strchr(s2, ':');
+               if (!s2)
+                       continue;
+
+               *s2 = 0;
+               if (s2 - str >= MAXPARAMLEN)
+                       continue;
+
+               strncpy((char *)password, str, MAXPARAMLEN);
+               fclose(f);
+               goto hash_password;
+       }
+
+       /* not found */
+       fclose(f);
+       return false;
+
+hash_password:
+       tce = gettcid(tpe.index);
+       do {
+               t_random(tpe.password.data, SALTLEN);
+       } while (memcmp(saltbuf, (char *)dig, sizeof(saltbuf)) == 0);
+       if (saltbuf[0] == 0)
+               saltbuf[0] = 0xff;
+
+       n = BigIntegerFromBytes(tce->modulus.data, tce->modulus.len);
+       g = BigIntegerFromBytes(tce->generator.data, tce->generator.len);
+       v = BigIntegerFromInt(0);
+
+       SHA1Init(&ctxt);
+       SHA1Update(&ctxt, (unsigned char *) username, strlen(username));
+       SHA1Update(&ctxt, (unsigned char *) ":", 1);
+       SHA1Update(&ctxt, (unsigned char *) password, strlen(password));
+       SHA1Final(dig, &ctxt);
+
+       SHA1Init(&ctxt);
+       SHA1Update(&ctxt, saltbuf, tpe.salt.len);
+       SHA1Update(&ctxt, dig, sizeof(dig));
+       SHA1Final(dig, &ctxt);
+
+       /* x = H(s, H(u, ':', p)) */
+       x = BigIntegerFromBytes(dig, sizeof(dig));
+
+       BigIntegerModExp(v, g, x, n);
+       tpe.password.len = BigIntegerToBytes(v, (unsigned char *)pwbuf);
+
+       BigIntegerFree(v);
+       BigIntegerFree(x);
+       BigIntegerFree(g);
+       BigIntegerFree(n);
+       return true;
+}
+
+static u16_t
+chksum(u16_t sum, const u8_t *data, u16_t len)
+{
+       u16_t t;
+       const u8_t *dataptr;
+       const u8_t *last_byte;
+
+       dataptr = data;
+       last_byte = data + len - 1;
+
+       while(dataptr < last_byte) {    /* At least two more bytes */
+               t = (dataptr[0] << 8) + dataptr[1];
+               sum += t;
+               if(sum < t) {
+                       sum++;          /* carry */
+               }
+               dataptr += 2;
+       }
+
+       if(dataptr == last_byte) {
+               t = (dataptr[0] << 8) + 0;
+               sum += t;
+               if(sum < t) {
+                       sum++;          /* carry */
+               }
+       }
+
+       /* Return sum in host byte order. */
+       return sum;
+}
+
+static void
+ead_send_packet_clone(struct ead_packet *pkt)
+{
+       u16_t len, sum;
+
+       memcpy(pktbuf, pkt, offsetof(struct ead_packet, msg));
+       memcpy(pktbuf->eh.ether_shost, ethmac, 6);
+       memcpy(pktbuf->eh.ether_dhost, pkt->eh.ether_shost, 6);
+
+       /* ip header */
+       len = sizeof(struct ead_packet) - sizeof(struct ether_header) + ntohl(pktbuf->msg.len);
+       pktbuf->len[0] = len >> 8;
+       pktbuf->len[1] = len & 0xff;
+       memcpy(pktbuf->srcipaddr, pkt->destipaddr, 4);
+       memcpy(pktbuf->destipaddr, pkt->srcipaddr, 4);
+
+       /* ip checksum */
+       pktbuf->ipchksum = 0;
+       sum = chksum(0, (void *) &pktbuf->vhl, UIP_IPH_LEN);
+       if (sum == 0)
+               sum = 0xffff;
+       pktbuf->ipchksum = htons(~sum);
+
+       /* udp header */
+       pktbuf->srcport = pkt->destport;
+       pktbuf->destport = pkt->srcport;
+
+       /* udp checksum */
+       len -= UIP_IPH_LEN;
+       pktbuf->udplen = htons(len);
+       pktbuf->udpchksum = 0;
+       sum = len + UIP_PROTO_UDP;
+       sum = chksum(sum, (void *) &pktbuf->srcipaddr[0], 8); /* src, dest ip */
+       sum = chksum(sum, (void *) &pktbuf->srcport, len);
+       if (sum == 0)
+               sum = 0xffff;
+       pktbuf->udpchksum = htons(~sum);
+       pcap_sendpacket(pcap_fp, (void *) pktbuf, sizeof(struct ead_packet) + ntohl(pktbuf->msg.len));
+}
+
+static void
+set_state(int nstate)
+{
+       if (state == nstate)
+               return;
+
+       if (nstate < state) {
+               if ((nstate < EAD_TYPE_GET_PRIME) &&
+                       (state >= EAD_TYPE_GET_PRIME)) {
+                       t_serverclose(ts);
+                       ts = NULL;
+               }
+               goto done;
+       }
+
+       switch(state) {
+       case EAD_TYPE_SET_USERNAME:
+               if (!prepare_password())
+                       goto error;
+               ts = t_serveropenraw(&tpe, tce);
+               if (!ts)
+                       goto error;
+               break;
+       case EAD_TYPE_GET_PRIME:
+               B = t_servergenexp(ts);
+               break;
+       case EAD_TYPE_SEND_A:
+               skey = t_servergetkey(ts, &A);
+               if (!skey)
+                       goto error;
+
+               ead_set_key(skey);
+               break;
+       }
+done:
+       state = nstate;
+error:
+       return;
+}
+
+static bool
+handle_ping(struct ead_packet *pkt, int len, int *nstate)
+{
+       struct ead_msg *msg = &pktbuf->msg;
+       struct ead_msg_pong *pong = EAD_DATA(msg, pong);
+       int slen;
+
+       slen = strlen(dev_name);
+       if (slen > 1024)
+               slen = 1024;
+
+       msg->len = htonl(sizeof(struct ead_msg_pong) + slen);
+       strncpy(pong->name, dev_name, slen);
+       pong->name[len] = 0;
+       pong->auth_type = htons(EAD_AUTH_MD5);
+
+       return true;
+}
+
+static bool
+handle_set_username(struct ead_packet *pkt, int len, int *nstate)
+{
+       struct ead_msg *msg = &pkt->msg;
+       struct ead_msg_user *user = EAD_DATA(msg, user);
+
+       set_state(EAD_TYPE_SET_USERNAME); /* clear old state */
+       strncpy(username, user->username, sizeof(username));
+       username[sizeof(username)] = 0;
+
+       msg = &pktbuf->msg;
+       msg->len = 0;
+
+       *nstate = EAD_TYPE_GET_PRIME;
+       return true;
+}
+
+static bool
+handle_get_prime(struct ead_packet *pkt, int len, int *nstate)
+{
+       struct ead_msg *msg = &pktbuf->msg;
+       struct ead_msg_salt *salt = EAD_DATA(msg, salt);
+
+       msg->len = htonl(sizeof(struct ead_msg_salt));
+       salt->prime = tce->index - 1;
+       salt->len = ts->s.len;
+       memcpy(salt->salt, ts->s.data, ts->s.len);
+       memcpy(salt->ext_salt, pw_saltbuf, MAXSALTLEN);
+
+       *nstate = EAD_TYPE_SEND_A;
+       return true;
+}
+
+static bool
+handle_send_a(struct ead_packet *pkt, int len, int *nstate)
+{
+       struct ead_msg *msg = &pkt->msg;
+       struct ead_msg_number *number = EAD_DATA(msg, number);
+       len = ntohl(msg->len) - sizeof(struct ead_msg_number);
+
+       if (len > MAXPARAMLEN + 1)
+               return false;
+
+       A.len = len;
+       A.data = abuf;
+       memcpy(A.data, number->data, len);
+
+       msg = &pktbuf->msg;
+       number = EAD_DATA(msg, number);
+       msg->len = htonl(sizeof(struct ead_msg_number) + B->len);
+       memcpy(number->data, B->data, B->len);
+
+       *nstate = EAD_TYPE_SEND_AUTH;
+       return true;
+}
+
+static bool
+handle_send_auth(struct ead_packet *pkt, int len, int *nstate)
+{
+       struct ead_msg *msg = &pkt->msg;
+       struct ead_msg_auth *auth = EAD_DATA(msg, auth);
+
+       if (t_serververify(ts, auth->data) != 0) {
+               DEBUG(2, "Client authentication failed\n");
+               *nstate = EAD_TYPE_SET_USERNAME;
+               return false;
+       }
+
+       msg = &pktbuf->msg;
+       auth = EAD_DATA(msg, auth);
+       msg->len = htonl(sizeof(struct ead_msg_auth));
+
+       DEBUG(2, "Client authentication successful\n");
+       memcpy(auth->data, t_serverresponse(ts), sizeof(auth->data));
+
+       *nstate = EAD_TYPE_SEND_CMD;
+       return true;
+}
+
+static bool
+handle_send_cmd(struct ead_packet *pkt, int len, int *nstate)
+{
+       struct ead_msg *msg = &pkt->msg;
+       struct ead_msg_cmd *cmd = EAD_ENC_DATA(msg, cmd);
+       struct ead_msg_cmd_data *cmddata;
+       struct timeval tv, to, tn;
+       int pfd[2], fd;
+       fd_set fds;
+       pid_t pid;
+       bool stream = false;
+       int timeout;
+       int type;
+       int datalen;
+
+       datalen = ead_decrypt_message(msg) - sizeof(struct ead_msg_cmd);
+       if (datalen <= 0)
+               return false;
+
+       type = ntohs(cmd->type);
+       timeout = ntohs(cmd->timeout);
+
+       FD_ZERO(&fds);
+       cmd->data[datalen] = 0;
+       switch(type) {
+       case EAD_CMD_NORMAL:
+               if (pipe(pfd) < 0)
+                       return false;
+
+               fcntl(pfd[0], F_SETFL, O_NONBLOCK | fcntl(pfd[0], F_GETFL));
+               child_pending = true;
+               pid = fork();
+               if (pid == 0) {
+                       close(pfd[0]);
+                       fd = open("/dev/null", O_RDWR);
+                       if (fd > 0) {
+                               dup2(fd, 0);
+                               dup2(pfd[1], 1);
+                               dup2(pfd[1], 2);
+                       }
+                       system((char *)cmd->data);
+                       exit(0);
+               } else if (pid > 0) {
+                       close(pfd[1]);
+                       if (!timeout)
+                               timeout = EAD_CMD_TIMEOUT;
+
+                       stream = true;
+                       break;
+               }
+               return false;
+       case EAD_CMD_BACKGROUND:
+               pid = fork();
+               if (pid == 0) {
+                       /* close stdin, stdout, stderr, replace with fd to /dev/null */
+                       fd = open("/dev/null", O_RDWR);
+                       if (fd > 0) {
+                               dup2(fd, 0);
+                               dup2(fd, 1);
+                               dup2(fd, 2);
+                       }
+                       system((char *)cmd->data);
+                       exit(0);
+               } else if (pid > 0) {
+                       break;
+               }
+               return false;
+       default:
+               return false;
+       }
+
+       msg = &pktbuf->msg;
+       cmddata = EAD_ENC_DATA(msg, cmd_data);
+
+       if (stream) {
+               int nfds, bytes;
+
+               /* send keepalive packets every 200 ms so that the client doesn't timeout */
+               gettimeofday(&to, NULL);
+               memcpy(&tn, &to, sizeof(tn));
+               tv.tv_usec = PCAP_TIMEOUT * 1000;
+               tv.tv_sec = 0;
+               do {
+                       cmddata->done = 0;
+                       FD_SET(pfd[0], &fds);
+                       nfds = select(pfd[0] + 1, &fds, NULL, NULL, &tv);
+                       bytes = 0;
+                       if (nfds > 0) {
+                               bytes = read(pfd[0], cmddata->data, 1024);
+                               if (bytes < 0)
+                                       bytes = 0;
+                       }
+                       if (!bytes && !child_pending)
+                               break;
+                       DEBUG(3, "Sending %d bytes of console data, type=%d, timeout=%d\n", bytes, ntohl(msg->type), timeout);
+                       ead_encrypt_message(msg, sizeof(struct ead_msg_cmd_data) + bytes);
+                       ead_send_packet_clone(pkt);
+                       gettimeofday(&tn, NULL);
+               } while (tn.tv_sec < to.tv_sec + timeout);
+               if (child_pending) {
+                       kill(pid, SIGKILL);
+                       return false;
+               }
+       }
+       cmddata->done = 1;
+       ead_encrypt_message(msg, sizeof(struct ead_msg_cmd_data));
+
+       return true;
+}
+
+
+
+static void
+parse_message(struct ead_packet *pkt, int len)
+{
+       bool (*handler)(struct ead_packet *pkt, int len, int *nstate);
+       int min_len = sizeof(struct ead_packet);
+       int nstate = state;
+       int type = ntohl(pkt->msg.type);
+
+       if ((type >= EAD_TYPE_GET_PRIME) &&
+               (state != type))
+               return;
+
+       switch(type) {
+       case EAD_TYPE_PING:
+               handler = handle_ping;
+               break;
+       case EAD_TYPE_SET_USERNAME:
+               handler = handle_set_username;
+               min_len += sizeof(struct ead_msg_user);
+               break;
+       case EAD_TYPE_GET_PRIME:
+               handler = handle_get_prime;
+               break;
+       case EAD_TYPE_SEND_A:
+               handler = handle_send_a;
+               min_len += sizeof(struct ead_msg_number);
+               break;
+       case EAD_TYPE_SEND_AUTH:
+               handler = handle_send_auth;
+               min_len += sizeof(struct ead_msg_auth);
+               break;
+       case EAD_TYPE_SEND_CMD:
+               handler = handle_send_cmd;
+               min_len += sizeof(struct ead_msg_cmd) + sizeof(struct ead_msg_encrypted);
+               break;
+       default:
+               return;
+       }
+
+       if (len < min_len) {
+               DEBUG(2, "discarding packet: message too small\n");
+               return;
+       }
+
+       pktbuf->msg.magic = htonl(EAD_MAGIC);
+       pktbuf->msg.type = htonl(type + 1);
+       pktbuf->msg.nid = htons(nid);
+       pktbuf->msg.len = 0;
+
+       if (handler(pkt, len, &nstate)) {
+               DEBUG(2, "sending response to packet type %d: %d\n", type + 1, ntohl(pktbuf->msg.len));
+               /* format response packet */
+               ead_send_packet_clone(pkt);
+       }
+       set_state(nstate);
+}
+
+static void
+handle_packet(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes)
+{
+       struct ead_packet *pkt = (struct ead_packet *) bytes;
+
+       if (h->len < sizeof(struct ead_packet))
+               return;
+
+       if (pkt->eh.ether_type != htons(ETHERTYPE_IP))
+               return;
+
+       if (memcmp(pkt->eh.ether_dhost, "\xff\xff\xff\xff\xff\xff", 6) != 0)
+               return;
+
+       if (pkt->proto != UIP_PROTO_UDP)
+               return;
+
+       if (pkt->destport != htons(EAD_PORT))
+               return;
+
+       if (pkt->msg.magic != htonl(EAD_MAGIC))
+               return;
+
+       if (h->len < sizeof(struct ead_packet) + ntohl(pkt->msg.len))
+               return;
+
+       if ((pkt->msg.nid != 0xffff) &&
+               (pkt->msg.nid != htons(nid)))
+               return;
+
+       parse_message(pkt, h->len);
+}
+
+static void
+ead_pcap_reopen(bool first)
+{
+       static char errbuf[PCAP_ERRBUF_SIZE] = "";
+
+       if (pcap_fp_rx != pcap_fp)
+               pcap_close(pcap_fp_rx);
+
+       if (pcap_fp)
+               pcap_close(pcap_fp);
+
+       pcap_fp_rx = pcap_fp;
+       do {
+               pcap_fp = pcap_open_live(ifname, PCAP_MRU, 1, PCAP_TIMEOUT, errbuf);
+#ifdef linux
+               if (brname) {
+                       pcap_fp_rx = pcap_open_live(brname, PCAP_MRU, 1, PCAP_TIMEOUT, errbuf);
+                       if (!pcap_fp_rx)
+                               pcap_fp_rx = pcap_fp;
+               }
+#endif
+               pcap_setfilter(pcap_fp_rx, &pktfilter);
+               if (first && !pcap_fp) {
+                       DEBUG(1, "WARNING: unable to open interface '%s'\n", ifname);
+                       first = false;
+               }
+               if (!pcap_fp)
+                       sleep(1);
+       } while (!pcap_fp);
+}
+
+
+static void
+ead_pktloop(void)
+{
+       while (1) {
+               if (pcap_dispatch(pcap_fp_rx, 1, handle_packet, NULL) < 0) {
+                       ead_pcap_reopen(false);
+                       continue;
+               }
+       }
+}
+
+
+static int
+usage(const char *prog)
+{
+       fprintf(stderr, "Usage: %s [<options>]\n"
+               "Options:\n"
+               "\t-B             Run in background mode\n"
+               "\t-d <device>    Set the device to listen on\n"
+               "\t-D <name>      Set the name of the device visible to clients\n"
+               "\t-p <file>      Set the password file for authenticating\n"
+               "\t-P <file>      Write a pidfile\n"
+               "\n", prog);
+       return -1;
+}
+
+static void
+server_handle_sigchld(int sig)
+{
+       struct ead_instance *in;
+       struct list_head *p;
+       int pid = 0;
+       wait(&pid);
+
+       list_for_each(p, &instances) {
+               in = list_entry(p, struct ead_instance, list);
+               if (pid != in->pid)
+                       continue;
+
+               in->pid = 0;
+               break;
+       }
+}
+
+static void
+instance_handle_sigchld(int sig)
+{
+       int pid = 0;
+       wait(&pid);
+       child_pending = false;
+}
+
+static void
+start_server(struct ead_instance *i)
+{
+       if (!nonfork) {
+               i->pid = fork();
+               if (i->pid != 0) {
+                       if (i->pid < 0)
+                               i->pid = 0;
+                       return;
+               }
+       }
+
+       signal(SIGCHLD, instance_handle_sigchld);
+       ifname = i->name;
+#ifdef linux
+       if (i->bridge[0])
+               brname = i->bridge;
+#endif
+       ead_pcap_reopen(true);
+       ead_pktloop();
+       pcap_close(pcap_fp);
+       if (pcap_fp_rx != pcap_fp)
+               pcap_close(pcap_fp_rx);
+
+       exit(0);
+}
+
+
+static void
+start_servers(bool restart)
+{
+       struct ead_instance *in;
+       struct list_head *p;
+
+       list_for_each(p, &instances) {
+               in = list_entry(p, struct ead_instance, list);
+               if (in->pid > 0)
+                       continue;
+
+               sleep(1);
+               start_server(in);
+       }
+}
+
+static void
+stop_server(struct ead_instance *in, bool do_free)
+{
+       if (in->pid > 0)
+               kill(in->pid, SIGKILL);
+       in->pid = 0;
+       if (do_free) {
+               list_del(&in->list);
+               free(in);
+       }
+}
+
+static void
+server_handle_sigint(int sig)
+{
+       struct ead_instance *in;
+       struct list_head *p, *tmp;
+
+       list_for_each_safe(p, tmp, &instances) {
+               in = list_entry(p, struct ead_instance, list);
+               stop_server(in, true);
+       }
+       exit(1);
+}
+
+#ifdef linux
+static int
+check_bridge_port(const char *br, const char *port, void *arg)
+{
+       struct ead_instance *in;
+       struct list_head *p, *tmp;
+
+       list_for_each(p, &instances) {
+               in = list_entry(p, struct ead_instance, list);
+
+               if (strcmp(in->name, port) != 0)
+                       continue;
+
+               in->br_check = true;
+               if (strcmp(in->bridge, br) == 0)
+                       break;
+
+               strncpy(in->bridge, br, sizeof(in->bridge));
+               DEBUG(2, "assigning port %s to bridge %s\n", in->name, in->bridge);
+               stop_server(in, false);
+       }
+       return 0;
+}
+
+static int
+check_bridge(const char *name, void *arg)
+{
+       br_foreach_port(name, check_bridge_port, arg);
+       return 0;
+}
+#endif
+
+static void
+check_all_interfaces(void)
+{
+#ifdef linux
+       struct ead_instance *in;
+       struct list_head *p, *tmp;
+
+       br_foreach_bridge(check_bridge, NULL);
+
+       /* look for interfaces that are no longer part of a bridge */
+       list_for_each(p, &instances) {
+               in = list_entry(p, struct ead_instance, list);
+
+               if (in->br_check) {
+                       in->br_check = false;
+               } else if (in->bridge[0]) {
+                       DEBUG(2, "removing port %s from bridge %s\n", in->name, in->bridge);
+                       in->bridge[0] = 0;
+                       stop_server(in, false);
+               }
+       }
+#endif
+}
+
+
+int main(int argc, char **argv)
+{
+       struct ead_instance *in;
+       struct timeval tv;
+       int fd, ch;
+       const char *pidfile = NULL;
+       bool background = false;
+       int n_iface = 0;
+
+       if (argc == 1)
+               return usage(argv[0]);
+
+       INIT_LIST_HEAD(&instances);
+       while ((ch = getopt(argc, argv, "Bd:D:fhp:P:")) != -1) {
+               switch(ch) {
+               case 'B':
+                       background = true;
+                       break;
+               case 'f':
+                       nonfork = true;
+                       break;
+               case 'h':
+                       return usage(argv[0]);
+               case 'd':
+                       in = malloc(sizeof(struct ead_instance));
+                       memset(in, 0, sizeof(struct ead_instance));
+                       INIT_LIST_HEAD(&in->list);
+                       strncpy(in->name, optarg, sizeof(in->name) - 1);
+                       list_add(&in->list, &instances);
+                       n_iface++;
+                       break;
+               case 'D':
+                       dev_name = optarg;
+                       break;
+               case 'p':
+                       passwd_file = optarg;
+                       break;
+               case 'P':
+                       pidfile = optarg;
+                       break;
+               }
+       }
+       signal(SIGCHLD, server_handle_sigchld);
+       signal(SIGINT, server_handle_sigint);
+       signal(SIGTERM, server_handle_sigint);
+       signal(SIGKILL, server_handle_sigint);
+
+       if (!n_iface) {
+               fprintf(stderr, "Error: ead needs at least one interface\n");
+               return -1;
+       }
+
+       if (background) {
+               if (fork() > 0)
+                       exit(0);
+
+               fd = open("/dev/null", O_RDWR);
+               dup2(fd, 0);
+               dup2(fd, 1);
+               dup2(fd, 2);
+       }
+
+       if (pidfile) {
+               char pid[8];
+               int len;
+
+               unlink(pidfile);
+               fd = open(pidfile, O_CREAT|O_WRONLY|O_EXCL, 0644);
+               if (fd > 0) {
+                       len = sprintf(pid, "%d\n", getpid());
+                       write(fd, pid, len);
+                       close(fd);
+               }
+       }
+
+       /* randomize the mac address */
+       fd = open("/dev/urandom", O_RDONLY);
+       if (fd < 0) {
+               perror("open");
+               exit(1);
+       }
+       read(fd, ethmac + 3, 3);
+       close(fd);
+       nid = *(((u16_t *) ethmac) + 2);
+
+       start_servers(false);
+#ifdef linux
+       br_init();
+#endif
+       tv.tv_sec = 1;
+       tv.tv_usec = 0;
+       while (1) {
+               check_all_interfaces();
+               start_servers(true);
+               sleep(1);
+       }
+#ifdef linux
+       br_shutdown();
+#endif
+
+       return 0;
+}