diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 66e6c70..94f382d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip diff --git a/src/main/java/ch/dissem/bitmessage/server/Constants.java b/src/main/java/ch/dissem/bitmessage/server/Constants.java new file mode 100644 index 0000000..a25f019 --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/Constants.java @@ -0,0 +1,13 @@ +package ch.dissem.bitmessage.server; + +/** + * Created by chrigu on 22.11.15. + */ +public interface Constants { + String ADMIN_LIST = "admins.conf"; + String CLIENT_LIST = "clients.conf"; + + String WHITELIST = "whitelist.conf"; + String SHORTLIST = "shortlist.conf"; + String BLACKLIST = "blacklist.conf"; +} diff --git a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java index 6dcd8f6..82ee924 100644 --- a/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java +++ b/src/main/java/ch/dissem/bitmessage/server/JabitServerConfig.java @@ -1,6 +1,7 @@ package ch.dissem.bitmessage.server; import ch.dissem.bitmessage.BitmessageContext; +import ch.dissem.bitmessage.entity.BitmessageAddress; import ch.dissem.bitmessage.networking.DefaultNetworkHandler; import ch.dissem.bitmessage.ports.MemoryNodeRegistry; import ch.dissem.bitmessage.repository.JdbcAddressRepository; @@ -16,6 +17,9 @@ import springfox.documentation.spring.web.plugins.Docket; import java.util.Set; +import static ch.dissem.bitmessage.server.Constants.*; +import static java.util.stream.Collectors.toSet; + @Configuration public class JabitServerConfig { public static final int SHORTLIST_SIZE = 5; @@ -36,19 +40,34 @@ public class JabitServerConfig { .messageRepo(new JdbcMessageRepository(config)) .nodeRegistry(new MemoryNodeRegistry()) .networkHandler(new DefaultNetworkHandler()) + .objectListener(new ServerObjectListener(admins(), clients(), whitelist(), shortlist(), blacklist())) .security(new BouncySecurity()) .port(port) .connectionLimit(connectionLimit) .connectionTTL(connectionTTL) - .listener(plaintext -> { - }) .build(); } + @Bean + public Set admins() { + return Utils.readOrCreateList( + ADMIN_LIST, + "# Admins can send commands to the server.\n" + ).stream().map(BitmessageAddress::new).collect(toSet()); + } + + @Bean + public Set clients() { + return Utils.readOrCreateList( + CLIENT_LIST, + "# Clients may send incomplete objects for proof of work.\n" + ).stream().map(BitmessageAddress::new).collect(toSet()); + } + @Bean public Set whitelist() { return Utils.readOrCreateList( - "whitelist.conf", + WHITELIST, "# If there are any Bitmessage addresses in the whitelist, only those will be shown.\n" + "# blacklist.conf will be ignored, but shortlist.conf will be applied to whitelisted addresses.\n" ); @@ -57,7 +76,7 @@ public class JabitServerConfig { @Bean public Set shortlist() { return Utils.readOrCreateList( - "shortlist.conf", + SHORTLIST, "# Broadcasts of these addresses will be restricted to the last " + SHORTLIST_SIZE + " entries.\n\n" + "# Time Service:\n" + "BM-BcbRqcFFSQUUmXFKsPJgVQPSiFA3Xash\n\n" + @@ -69,7 +88,7 @@ public class JabitServerConfig { @Bean public Set blacklist() { return Utils.readOrCreateList( - "blacklist.conf", + BLACKLIST, "# Bitmessage addresses in this file are being ignored and their broadcasts won't be returned.\n" ); } diff --git a/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java new file mode 100644 index 0000000..83a0c99 --- /dev/null +++ b/src/main/java/ch/dissem/bitmessage/server/ServerObjectListener.java @@ -0,0 +1,160 @@ +package ch.dissem.bitmessage.server; + +import ch.dissem.bitmessage.DefaultObjectListener; +import ch.dissem.bitmessage.entity.BitmessageAddress; +import ch.dissem.bitmessage.entity.ObjectMessage; +import ch.dissem.bitmessage.entity.Plaintext; +import ch.dissem.bitmessage.entity.payload.Broadcast; +import ch.dissem.bitmessage.exception.DecryptionFailedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Collection; +import java.util.Scanner; + +import static ch.dissem.bitmessage.factory.Factory.getObjectMessage; +import static ch.dissem.bitmessage.server.Constants.*; +import static ch.dissem.bitmessage.server.Utils.zero; +import static ch.dissem.bitmessage.utils.Singleton.security; + +/** + * @author Christian Basler + */ +public class ServerObjectListener extends DefaultObjectListener { + private final static Logger LOG = LoggerFactory.getLogger(ServerObjectListener.class); + + private final Collection admins; + private final Collection clients; + + private final Collection whitelist; + private final Collection shortlist; + private final Collection blacklist; + + public ServerObjectListener(Collection admins, Collection clients, Collection whitelist, Collection shortlist, Collection blacklist) { + super(p -> { + }); + this.admins = admins; + this.clients = clients; + this.whitelist = whitelist; + this.shortlist = shortlist; + this.blacklist = blacklist; + } + + @Override + protected void receive(ObjectMessage object, Broadcast broadcast) throws IOException { + processCommands(broadcast); + if (zero(object.getNonce())) { + calculateNonceForClient(object, broadcast); + } else { + super.receive(object, broadcast); + } + } + + private void processCommands(Broadcast broadcast) throws IOException { + for (BitmessageAddress admin : admins) { + try { + broadcast.decrypt(admin); + Plaintext message = broadcast.getPlaintext(); + String[] command = message.getSubject().trim().toLowerCase().split("\\s+"); + String data = message.getText(); + if (command.length == 2) { + switch (command[1]) { + case "client": + case "clients": + updateUserList(CLIENT_LIST, clients, command[0], data); + break; + case "admin": + case "admins": + case "administrator": + case "administrators": + updateUserList(ADMIN_LIST, admins, command[0], data); + break; + case "whitelist": + updateList(WHITELIST, whitelist, command[0], data); + break; + case "shortlist": + updateList(WHITELIST, shortlist, command[0], data); + break; + case "blacklist": + updateList(WHITELIST, blacklist, command[0], data); + break; + default: + LOG.trace("ignoring unknown command " + message.getSubject()); + } + } + } catch (DecryptionFailedException ignore) { + } + } + } + + private void calculateNonceForClient(ObjectMessage object, Broadcast broadcast) throws IOException { + for (BitmessageAddress client : clients) { + try { + broadcast.decrypt(client); + byte[] message = broadcast.getPlaintext().getMessage(); + final ObjectMessage toRelay = getObjectMessage(3, new ByteArrayInputStream(message), message.length); + security().doProofOfWork(toRelay, + ctx.getNetworkNonceTrialsPerByte(), ctx.getNetworkExtraBytes(), + (nonce) -> { + toRelay.setNonce(nonce); + ctx.getInventory().storeObject(object); + ctx.getNetworkHandler().offer(object.getInventoryVector()); + } + ); + } catch (DecryptionFailedException ignore) { + } + } + } + + private void updateUserList(String file, Collection list, String command, String data) { + switch (command) { + case "set": + list.clear(); + case "add": + Scanner scanner = new Scanner(data); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + try { + list.add(new BitmessageAddress(line)); + } catch (Exception e) { + LOG.info(command + " " + file + ": ignoring line: " + line); + } + } + Utils.saveList(file, list.stream().map(BitmessageAddress::getAddress)); + break; + case "remove": + list.removeIf(address -> data.contains(address.getAddress())); + Utils.saveList(file, list.stream().map(BitmessageAddress::getAddress)); + break; + default: + LOG.info("unknown command " + command + " on list " + file); + } + } + + private void updateList(String file, Collection list, String command, String data) { + switch (command) { + case "set": + list.clear(); + case "add": + Scanner scanner = new Scanner(data); + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + try { + list.add(new BitmessageAddress(line).getAddress()); + } catch (Exception e) { + LOG.info(command + " " + file + ": ignoring line: " + line); + } + } + Utils.saveList(file, list.stream()); + break; + case "remove": + list.removeIf(data::contains); + Utils.saveList(file, list.stream()); + break; + default: + LOG.info("unknown command " + command + " on list " + file); + } + } +} diff --git a/src/main/java/ch/dissem/bitmessage/server/Utils.java b/src/main/java/ch/dissem/bitmessage/server/Utils.java index 2f9a8d5..d9bcc49 100644 --- a/src/main/java/ch/dissem/bitmessage/server/Utils.java +++ b/src/main/java/ch/dissem/bitmessage/server/Utils.java @@ -4,7 +4,10 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; -import java.util.*; +import java.util.HashSet; +import java.util.Scanner; +import java.util.Set; +import java.util.stream.Stream; public class Utils { public static Set readOrCreateList(String filename, String content) { @@ -23,6 +26,24 @@ public class Utils { } } + public static void saveList(String filename, Stream content) { + try { + File file = new File(filename); + try (FileWriter fw = new FileWriter(file)) { + content.forEach(l -> { + try { + fw.write(l); + fw.write(System.lineSeparator()); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + public static Set readList(File file) { Set result = new HashSet<>(); try { @@ -38,4 +59,11 @@ public class Utils { } return result; } + + public static boolean zero(byte[] nonce) { + for (byte b : nonce) { + if (b != 0) return false; + } + return true; + } }