$ git clone https://socialnetwork.ion.nu/socialnetwork-web.git
commit bd42696cbf942aef538fe9056c70221670c95a83
Author: Alicia <...>
Date:   Tue Jun 13 23:35:47 2017 +0200

    Added lists of verified and quarantined addresses to prevent abuse of the webpeer service for DDoS attempts. The verified-list also doubles as a list of bootstrap peers.

diff --git a/Makefile b/Makefile
index 7d4533f..1c1b200 100644
--- a/Makefile
+++ b/Makefile
@@ -11,7 +11,7 @@ JSCFLAGS=$(shell PKG_CONFIG_PATH=toolchain/usr/lib/pkgconfig pkg-config --cflags
 
 all: webpeer libsocial.js
 
-webpeer: daemon.o webpeer.o
+webpeer: daemon.o webpeer.o addrlist.o
  $(CC) $^ $(LIBS) -o $@
 
 toolchain/usr/bin/emcc: emscripten-$(EMSCRIPTVERSION).tar.gz emscripten-fastcomp-$(EMSCRIPTVERSION).tar.gz emscripten-fastcomp-clang-$(EMSCRIPTVERSION).tar.gz
diff --git a/addrlist.c b/addrlist.c
new file mode 100644
index 0000000..bd0d5d7
--- /dev/null
+++ b/addrlist.c
@@ -0,0 +1,154 @@
+/*
+    webpeer, a websocket-udpstream proxy for socialnetwork-web
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <unistd.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include "addrlist.h"
+
+struct addrlist verified={0,0,0};
+static struct addrlist quarantine={0,0,0};
+
+static int getlock(const char* path)
+{
+  int f;
+  while((f=open(path, O_CREAT|O_EXCL|O_WRONLY, 0600))<0)
+  {
+    usleep(100);
+  }
+  return f;
+}
+
+static void list_update(struct addrlist* list, const char* file)
+{
+  list->updated=time(0);
+  struct stat st;
+  if(stat(file, &st)){return;}
+  int f=open(file, O_RDONLY);
+  if(f<0){return;}
+  free(list->addr);
+  list->count=st.st_size/sizeof(struct sockaddr_storage);
+  list->addr=malloc(list->count*sizeof(struct sockaddr_storage));
+  read(f, list->addr, list->count*sizeof(struct sockaddr_storage));
+  close(f);
+}
+
+// Check if addresses match, with or without port numbers
+static char addrcmp(struct sockaddr_storage* a, struct sockaddr_storage* b, char port)
+{
+  if(a->ss_family!=b->ss_family){return 0;}
+  #define checkmember(st,x) if(memcmp(&((st*)a)->x, &((st*)b)->x, sizeof(((st*)a)->x))){return 0;}
+  switch(a->ss_family)
+  {
+  case AF_INET: checkmember(struct sockaddr_in, sin_addr); if(port){checkmember(struct sockaddr_in, sin_port);} break;
+  case AF_INET6: checkmember(struct sockaddr_in6, sin6_addr); if(port){checkmember(struct sockaddr_in6, sin6_port);} break;
+  default: return 0; // Unhandled address families
+  }
+  return 1;
+}
+
+static char list_find(struct addrlist* list, struct sockaddr_storage* addr, char port)
+{
+  unsigned int i;
+  for(i=0; i<list->count; ++i)
+  {
+    if(addrcmp(&list->addr[i], addr, port)){return 1;}
+  }
+  return 0;
+}
+
+static void list_add(struct addrlist* list, struct sockaddr_storage* addr, socklen_t addrlen)
+{
+  ++list->count;
+  list->addr=realloc(list->addr, list->count*sizeof(struct sockaddr_storage));
+  memset(&list->addr[list->count-1], 0, sizeof(struct sockaddr_storage));
+  memcpy(&list->addr[list->count-1], addr, addrlen);
+}
+
+#define list_save(list,f) write(f, (list)->addr, (list)->count*sizeof(struct sockaddr_storage));
+
+static char checkaddr(struct sockaddr_storage* addr, socklen_t addrlen)
+{
+  if(addrlen<=sizeof(sa_family_t)){return 0;}
+  if(addrlen>sizeof(struct sockaddr_storage)){return 0;} // Too big
+  switch(addr->ss_family)
+  {
+    case AF_INET: return addrlen>=sizeof(struct sockaddr_in);
+    case AF_INET6: return addrlen>=sizeof(struct sockaddr_in6);
+  }
+  return 0; // Unhandled address families
+}
+
+// Check if we can connect to this address given the rules that only one connection attempt at a time may be done to a host, with the exception of already verified host/port combinations
+char addrlist_check(struct sockaddr_storage* addr, socklen_t addrlen)
+{
+  // Check that addrlen makes sense (don't accept 0-byte addresses and try to access non-0-byte data within)
+  if(!checkaddr(addr, addrlen)){return 0;}
+  // Check if it's already verified (keep in-memory cache and update based on timestamp?)
+  if(list_find(&verified, addr, 1)){return 1;}
+  time_t now=time(0);
+  if(verified.updated+5<now) // Update and check again
+  {
+    list_update(&verified, "verified.addr");
+    if(list_find(&verified, addr, 1)){return 1;}
+  }
+  // Check if it's quarantined. if it isn't, obtain a lock, update and check again
+  if(list_find(&quarantine, addr, 0)){return 0;}
+  int f=getlock("quarantine.addr.lock");
+  list_update(&quarantine, "quarantine.addr");
+  if(list_find(&quarantine, addr, 0)){close(f); unlink("quarantine.addr.lock"); return 0;}
+  // Not verified and not quarantined, quarantine it and allow this one connection attempt for verification
+  list_add(&quarantine, addr, addrlen);
+  list_save(&quarantine, f);
+  close(f);
+  rename("quarantine.addr.lock", "quarantine.addr");
+  return 1;
+}
+
+// Remove from quarantine and add to verified
+void addrlist_verify(struct sockaddr_storage* addr, socklen_t addrlen)
+{
+  if(!checkaddr(addr, addrlen)){return;} // Invalid address
+  if(!list_find(&quarantine, addr, 1)){return;} // Wasn't quarantined
+  int f=getlock("quarantine.addr.lock");
+  list_update(&quarantine, "quarantine.addr"); // Update so we don't accidentally remove a recently added host
+  unsigned int i;
+  for(i=0; i<quarantine.count; ++i)
+  {
+    if(addrcmp(&quarantine.addr[i], addr, 1)){break;}
+  }
+  if(i==quarantine.count){close(f); unlink("quarantine.addr.lock"); return;} // Wasn't quarantined after all (this shouldn't happen, or maybe it should? remote peer making contact with another session first)
+  // Remove from quarantine
+  --quarantine.count;
+  memmove(&quarantine.addr[i], &quarantine.addr[i+1], (quarantine.count-i)*sizeof(struct sockaddr_storage));
+  list_save(&quarantine, f);
+  close(f);
+  rename("quarantine.addr.lock", "quarantine.addr");
+  // Add to verified
+  f=getlock("verified.addr.lock");
+  list_update(&verified, "verified.addr"); // Update so we don't accidentally remove a recently added host
+  list_add(&verified, addr, addrlen);
+  list_save(&verified, f);
+  close(f);
+  rename("verified.addr.lock", "verified.addr");
+}
diff --git a/addrlist.h b/addrlist.h
new file mode 100644
index 0000000..ac3da67
--- /dev/null
+++ b/addrlist.h
@@ -0,0 +1,28 @@
+/*
+    webpeer, a websocket-udpstream proxy for socialnetwork-web
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <sys/socket.h>
+
+struct addrlist
+{
+  struct sockaddr_storage* addr;
+  unsigned int count;
+  time_t updated;
+};
+extern struct addrlist verified;
+
+extern char addrlist_check(struct sockaddr_storage* addr, socklen_t addrlen);
+extern void addrlist_verify(struct sockaddr_storage* addr, socklen_t addrlen);
diff --git a/libsocialjs.js b/libsocialjs.js
index f30807e..0bb0e8a 100644
--- a/libsocialjs.js
+++ b/libsocialjs.js
@@ -33,12 +33,17 @@ function handlenet(data)
   var f=new FileReader();
   f.onload=function()
   {
-    data=new Int8Array(f.result);
+    data=new Uint8Array(f.result);
     if(firstpacket) // Bootstrap
     {
       firstpacket=false;
-      // TODO: Read lengths and pass subarrays until we reach the end of the array. length=data[pos]*256+data[pos+1]
-      peer_new_unique(-1, data, data.length);
+      // Read sockaddr lengths and pass subarrays until we reach the end of the array
+      while(data.length>2)
+      {
+        var len=data[0]*256+data[1];
+        peer_new_unique(-1, data.subarray(2, 2+len), len);
+        data=data.subarray(2+len);
+      }
       return;
     }
     if(websockproxy_to)
diff --git a/webpeer.c b/webpeer.c
index 6ccad71..73a129a 100644
--- a/webpeer.c
+++ b/webpeer.c
@@ -20,7 +20,8 @@
 #include <netdb.h>
 #include <netinet/in.h>
 #include <libsocial/udpstream.h>
-#include "libwebsocket/websock.h"
+#include <libwebsocket/websock.h>
+#include "addrlist.h"
 
 const char* verifyproto(const char* path, const char* host, char* protocol, const char* origin)
 {
@@ -35,14 +36,41 @@ void webpeer(int sock)
   websock_conn* conn=websock_new(sock, 1, "cert.pem", "priv.pem");
   if(!websock_handshake_server(conn, verifyproto, 0)){printf("Handshake failed\n"); return;}
   int udpsock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
-// TODO: First send a list of bootstrap peers, should this be binary too?
-//  websock_write(conn, "127.0.0.1:4000", 14, WEBSOCK_TEXT);
-  struct sockaddr_in bootstrapaddr={
-    .sin_family=AF_INET,
-    .sin_addr.s_addr=0x100007f,
-    .sin_port=htons(4000)
-  };
-  websock_write(conn, &bootstrapaddr, sizeof(bootstrapaddr), WEBSOCK_BINARY);
+  // First send a list of bootstrap peers
+  if(!verified.count) // No verified peers yet, suggest localhost:4000 as a starting point
+  {
+    struct sockaddr_in bootstrapaddr={
+      .sin_family=AF_INET,
+      .sin_addr.s_addr=htonl(0x7f000001),
+      .sin_port=htons(4000)
+    };
+    uint16_t len=htons(sizeof(bootstrapaddr));
+    char buf[sizeof(len)+sizeof(bootstrapaddr)];
+    memcpy(buf, &len, sizeof(len));
+    memcpy(buf+sizeof(len), &bootstrapaddr, sizeof(bootstrapaddr));
+    websock_write(conn, buf, sizeof(len)+sizeof(bootstrapaddr), WEBSOCK_BINARY);
+  }else{
+    // Construct a single message containing all peers
+    unsigned int size=verified.count*(sizeof(uint16_t)+sizeof(struct sockaddr_storage));
+    char buf[size];
+    void* ptr=buf;
+    unsigned int i;
+    for(i=0; i<verified.count; ++i)
+    {
+      uint16_t len=0;
+      switch(verified.addr[i].ss_family)
+      {
+        case AF_INET: len=htons(sizeof(struct sockaddr_in)); break;
+        case AF_INET6: len=htons(sizeof(struct sockaddr_in6)); break;
+      }
+      if(!len){continue;}
+      memcpy(ptr, &len, sizeof(len));
+      ptr+=sizeof(len);
+      len=ntohs(len);
+      memcpy(ptr, &verified.addr[i], len);
+      ptr+=len;
+    }
+  }
   // Listen to client and udpstream socket and pass messages back and forth (client handles the TLS within the udpstream)
   struct pollfd pfd[]={{.fd=sock, .events=POLLIN, .revents=0}, {.fd=udpsock, .events=POLLIN, .revents=0}};
   while(1)
@@ -62,10 +90,11 @@ printf("Handling websocket input...\n");
       if(!websock_readhead(conn, &head)){break;}
       char buf[head.length];
       while(!websock_readcontent(conn, buf, &head));
-      struct udpstream* stream=udpstream_find((struct sockaddr*)addr, addrlen);
+      struct udpstream* stream=udpstream_find((struct sockaddr_storage*)addr, addrlen);
       if(!stream) // TODO: Check against blocklist (don't let people use this service to flood others over UDP), also add a brief block for the entire host until we know this connection is ok
       {
-        stream=udpstream_new(udpsock, (struct sockaddr*)addr, addrlen);
+        if(!addrlist_check((struct sockaddr_storage*)addr, addrlen)){continue;}
+        stream=udpstream_new(udpsock, (struct sockaddr_storage*)addr, addrlen);
       }
 // printf("Writing %u bytes\n", head.length);
       udpstream_write(stream, buf, head.length);
@@ -81,9 +110,10 @@ printf("Handling udpsocket input...\n");
         char buf[2048];
         ssize_t len=udpstream_read(stream, buf, 2048);
         if(len<1){udpstream_close(stream); continue;}
-        struct sockaddr addr;
+        struct sockaddr_storage addr;
         socklen_t addrlen=sizeof(addr);
         udpstream_getaddr(stream, &addr, &addrlen);
+        addrlist_verify(&addr, addrlen);
         websock_write(conn, &addr, addrlen, WEBSOCK_BINARY);
         websock_write(conn, buf, len, WEBSOCK_BINARY);
       }