Initial commit

This commit is contained in:
2020-12-18 17:02:46 +01:00
commit eb951d69e1
169 changed files with 7551 additions and 0 deletions
+120
View File
@@ -0,0 +1,120 @@
package dslab;
import java.io.InputStream;
import java.io.PrintStream;
import dslab.client.IMessageClient;
import dslab.client.MessageClient;
import dslab.mailbox.IMailboxServer;
import dslab.mailbox.MailboxServer;
import dslab.monitoring.IMonitoringServer;
import dslab.monitoring.MonitoringServer;
import dslab.nameserver.INameserver;
import dslab.nameserver.Nameserver;
import dslab.transfer.ITransferServer;
import dslab.transfer.TransferServer;
import dslab.util.Config;
/**
* The component factory provides methods to create the core components of the application. You can edit the method body
* if the component instantiation requires additional logic.
*
* Do not change the existing method signatures!
*/
public final class ComponentFactory {
private ComponentFactory() {
// static utility class
}
/**
* Creates a new {@link IMonitoringServer} instance.
*
* @param componentId the component id
* @param in the input stream used for accepting cli commands
* @param out the output stream to print to
* @return a new MonitoringServer instance
*/
public static IMonitoringServer createMonitoringServer(String componentId, InputStream in, PrintStream out)
throws Exception {
/*
* TODO: Here you can modify the code (if necessary) to instantiate your components
*/
Config config = new Config(componentId);
return new MonitoringServer(componentId, config, in, out);
}
/**
* Creates a new {@link IMailboxServer} instance.
*
* @param componentId the component id
* @param in the input stream used for accepting cli commands
* @param out the output stream to print to
* @return a new MailboxServer instance
*/
public static IMailboxServer createMailboxServer(String componentId, InputStream in, PrintStream out)
throws Exception {
/*
* TODO: Here you can modify the code (if necessary) to instantiate your components
*/
Config config = new Config(componentId);
return new MailboxServer(componentId, config, in, out);
}
/**
* Creates a new {@link ITransferServer} instance.
*
* @param componentId the component id
* @param in the input stream used for accepting cli commands
* @param out the output stream to print to
* @return a new TransferServer instance
*/
public static ITransferServer createTransferServer(String componentId, InputStream in, PrintStream out)
throws Exception {
/*
* TODO: Here you can modify the code (if necessary) to instantiate your components
*/
Config config = new Config(componentId);
return new TransferServer(componentId, config, in, out);
}
/**
* Creates a new {@link INameserver} instance.
*
* @param componentId the component id
* @param in the input stream used for accepting cli commands
* @param out the output stream to print to
* @return a new Nameserver instance
*/
public static INameserver createNameserver(String componentId, InputStream in, PrintStream out)
throws Exception {
/*
* TODO: Here you can modify the code (if necessary) to instantiate your components
*/
Config config = new Config(componentId);
return new Nameserver(componentId, config, in, out);
}
/**
* Creates a new {@link IMessageClient} instance.
*
* @param componentId the component id
* @param in the input stream used for accepting cli commands
* @param out the output stream to print to
* @return a new MessageClient instance
*/
public static IMessageClient createMessageClient(String componentId, InputStream in, PrintStream out)
throws Exception {
/*
* TODO: Here you can modify the code (if necessary) to instantiate your components
*/
Config config = new Config(componentId);
return new MessageClient(componentId, config, in, out);
}
}
@@ -0,0 +1,50 @@
package dslab.client;
/**
* A Message Client application.
*
* Do not change the existing method signatures!
*/
public interface IMessageClient extends Runnable {
/**
* Starts the message client.
*/
@Override
void run();
/**
* Outputs the contents of the user's inbox on the shell.
*/
void inbox();
/**
* Deletes the mail with the given id. Prints 'ok' if the mail was deleted successfully, 'error {explanation}'
* otherwise.
*
* @param id the mail id
*/
void delete(String id);
/**
* Verifies the signature of the message by calculating its hash value using the shared secret. Prints 'ok' if the
* message integrity was successfully verified, or 'error' otherwise.
*
* @param id the message id
*/
void verify(String id);
/**
* Sends a message from the mail client's user to the given recipient(s)
*
* @param to comma separated list of recipients
* @param subject the message subject
* @param data the message data
*/
void msg(String to, String subject, String data);
/**
* Shuts down the application.
*/
void shutdown();
}
@@ -0,0 +1,57 @@
package dslab.client;
import java.io.InputStream;
import java.io.PrintStream;
import dslab.ComponentFactory;
import dslab.util.Config;
public class MessageClient implements IMessageClient, Runnable {
/**
* Creates a new client instance.
*
* @param componentId the id of the component that corresponds to the Config resource
* @param config the component config
* @param in the input stream to read console input from
* @param out the output stream to write console output to
*/
public MessageClient(String componentId, Config config, InputStream in, PrintStream out) {
}
@Override
public void run() {
}
@Override
public void inbox() {
}
@Override
public void delete(String id) {
}
@Override
public void verify(String id) {
}
@Override
public void msg(String to, String subject, String data) {
}
@Override
public void shutdown() {
}
public static void main(String[] args) throws Exception {
IMessageClient client = ComponentFactory.createMessageClient(args[0], System.in, System.out);
client.run();
}
}
@@ -0,0 +1,23 @@
package dslab.mailbox;
/**
* The mailbox server receives mails via DMTP from transfer servers, and makes them available to users via the DMAP
* protocol.
*
* Do not change the existing method signatures!
*/
public interface IMailboxServer extends Runnable {
/**
* Starts the server.
*/
@Override
void run();
/**
* CLI command to shut down the server. After this method, all resources should be closed, and the application
* should terminate.
*/
void shutdown();
}
@@ -0,0 +1,37 @@
package dslab.mailbox;
import java.io.InputStream;
import java.io.PrintStream;
import dslab.ComponentFactory;
import dslab.util.Config;
public class MailboxServer implements IMailboxServer, Runnable {
/**
* Creates a new server instance.
*
* @param componentId the id of the component that corresponds to the Config resource
* @param config the component config
* @param in the input stream to read console input from
* @param out the output stream to write console output to
*/
public MailboxServer(String componentId, Config config, InputStream in, PrintStream out) {
// TODO
}
@Override
public void run() {
// TODO
}
@Override
public void shutdown() {
// TODO
}
public static void main(String[] args) throws Exception {
IMailboxServer server = ComponentFactory.createMailboxServer(args[0], System.in, System.out);
server.run();
}
}
@@ -0,0 +1,33 @@
package dslab.monitoring;
/**
* The monitoring service accepts incoming monitoring packets via UDP. It provides CLI commands to access the
* information.
*
* Do not change the existing method signatures!
*/
public interface IMonitoringServer extends Runnable {
/**
* Starts the server.
*/
@Override
void run();
/**
* CLI command to shut down the server. After this method, all resources should be closed, and the application
* should terminate.
*/
void shutdown();
/**
* CLI command to report usage statistics for transfer servers.
*/
void servers();
/**
* CLI command to report usage statistics for individual senders.
*/
void addresses();
}
@@ -0,0 +1,48 @@
package dslab.monitoring;
import java.io.InputStream;
import java.io.PrintStream;
import dslab.ComponentFactory;
import dslab.util.Config;
public class MonitoringServer implements IMonitoringServer {
/**
* Creates a new server instance.
*
* @param componentId the id of the component that corresponds to the Config resource
* @param config the component config
* @param in the input stream to read console input from
* @param out the output stream to write console output to
*/
public MonitoringServer(String componentId, Config config, InputStream in, PrintStream out) {
// TODO
}
@Override
public void run() {
// TODO
}
@Override
public void addresses() {
// TODO
}
@Override
public void servers() {
// TODO
}
@Override
public void shutdown() {
// TODO
}
public static void main(String[] args) throws Exception {
IMonitoringServer server = ComponentFactory.createMonitoringServer(args[0], System.in, System.out);
server.run();
}
}
@@ -0,0 +1,13 @@
package dslab.nameserver;
public class AlreadyRegisteredException extends Exception {
private static final long serialVersionUID = 1L;
public AlreadyRegisteredException(String message, Throwable cause) {
super(message, cause);
}
public AlreadyRegisteredException(String message) {
super(message);
}
}
@@ -0,0 +1,33 @@
package dslab.nameserver;
/**
* A Nameserver application.
*
* Do not change the existing method signatures!
*/
public interface INameserver extends Runnable {
/**
* Starts the server.
*/
@Override
void run();
/**
* CLI command to report information about each known nameserver (zones) from the perspective of this nameserver.
*/
void nameservers();
/**
* CLI command to report information about the handled mailbox servers, containing mail domain and address
* (IP:port).
*/
void addresses();
/**
* CLI command to shut down the server. After this method, all resources should be closed, and the application
* should terminate.
*/
void shutdown();
}
@@ -0,0 +1,59 @@
package dslab.nameserver;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
* The remote object of a nameserver which can be called via RMI.
*/
public interface INameserverRemote extends Remote {
/**
* Registers a mailbox server with the given address for the given domain. For example, when registering a
* nameserver for the domain 'earth.planet', the new nameserver first calls the root nameserver with the argument
* 'earth.planet'. The root nameserver locates the nameserver for 'planet' via its child-nameserver references, and
* invokes this method with the remainder of the domain (i.e., 'earth'). Because 'earth' is then the leaf zone, the
* current nameserver ('planet') stores the reference in its child-nameserver references.
*
* @param domain the domain
* @param nameserver the nameserver's remote object
* @throws RemoteException RMI exception (declaration required by RMI)
* @throws AlreadyRegisteredException if the given domain is already registered
* @throws InvalidDomainException if the domain is invalid (e.g., due to a syntax error, or a required intermediary
* nameserver was not found)
*/
void registerNameserver(String domain, INameserverRemote nameserver) throws RemoteException,
AlreadyRegisteredException, InvalidDomainException;
/**
* Registers a mailbox server with the given address for the given domain.
*
* @param domain the mail domain, e.g. <code>vienna.earth.planet</code>
* @param address the socket address of the mailbox server's DMTP socket, e.g., <code>127.0.0.1:16503</code>
* @throws RemoteException RMI exception (declaration required by RMI)
* @throws AlreadyRegisteredException if the given domain is already in use
* @throws InvalidDomainException if the domain is invalid (e.g., due to a syntax error, or the responsible
* nameserver was not found)
*/
void registerMailboxServer(String domain, String address) throws RemoteException, AlreadyRegisteredException,
InvalidDomainException;
/**
* Returns a reference to the remote object of the ns for the given zone. For example, if called with the argument
* 'earth' on the remote object of zone 'planet', the call returns the reference to the nameserver of the zone
* 'earth.planet'.
*
* @param zone the child zone, e.g. <code>earth</code>
* @return the remote object reference of the given zone, or <code>null</code> if it does not exist
* @throws RemoteException RMI exception (declaration required by RMI)
*/
INameserverRemote getNameserver(String zone) throws RemoteException;
/**
* @param username
* @return
* @throws RemoteException
*/
String lookup(String username) throws RemoteException;
}
@@ -0,0 +1,13 @@
package dslab.nameserver;
public class InvalidDomainException extends Exception {
private static final long serialVersionUID = 1L;
public InvalidDomainException(String message) {
super(message);
}
public InvalidDomainException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,48 @@
package dslab.nameserver;
import java.io.InputStream;
import java.io.PrintStream;
import dslab.ComponentFactory;
import dslab.util.Config;
public class Nameserver implements INameserver {
/**
* Creates a new server instance.
*
* @param componentId the id of the component that corresponds to the Config resource
* @param config the component config
* @param in the input stream to read console input from
* @param out the output stream to write console output to
*/
public Nameserver(String componentId, Config config, InputStream in, PrintStream out) {
// TODO
}
@Override
public void run() {
// TODO
}
@Override
public void nameservers() {
// TODO
}
@Override
public void addresses() {
// TODO
}
@Override
public void shutdown() {
// TODO
}
public static void main(String[] args) throws Exception {
INameserver component = ComponentFactory.createNameserver(args[0], System.in, System.out);
component.run();
}
}
@@ -0,0 +1,22 @@
package dslab.transfer;
/**
* The transfer server is responsible for accepting mails sent by users, and forward them to mailbox servers via DMTP.
* It also reports usage statistics to the monitoring server.
*
* Do not change the existing method signatures!
*/
public interface ITransferServer extends Runnable {
/**
* Starts the server.
*/
@Override
void run();
/**
* CLI command to shut down the server. After this method, all resources should be closed, and the application
* should terminate.
*/
void shutdown();
}
@@ -0,0 +1,38 @@
package dslab.transfer;
import java.io.InputStream;
import java.io.PrintStream;
import dslab.ComponentFactory;
import dslab.util.Config;
public class TransferServer implements ITransferServer, Runnable {
/**
* Creates a new server instance.
*
* @param componentId the id of the component that corresponds to the Config resource
* @param config the component config
* @param in the input stream to read console input from
* @param out the output stream to write console output to
*/
public TransferServer(String componentId, Config config, InputStream in, PrintStream out) {
// TODO
}
@Override
public void run() {
// TODO
}
@Override
public void shutdown() {
// TODO
}
public static void main(String[] args) throws Exception {
ITransferServer server = ComponentFactory.createTransferServer(args[0], System.in, System.out);
server.run();
}
}
+79
View File
@@ -0,0 +1,79 @@
package dslab.util;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
/**
* Reads the configuration from a {@code .properties} file.
*/
public final class Config {
private final ResourceBundle bundle;
private Map<String, Object> properties = new HashMap<>();
/**
* Creates an instance of Config which reads configuration data form {@code .properties} file with given name found
* in classpath.
*
* @param name the name of the .properties file
*/
public Config(String name) {
if (name.endsWith(".properties")) {
this.bundle = ResourceBundle.getBundle(name.substring(0, name.length() - 11));
} else {
this.bundle = ResourceBundle.getBundle(name);
}
}
/**
* Returns the value as String for the given key.
*
* @param key the property's key
* @return String value of the property
* @see ResourceBundle#getString(String)
*/
public String getString(String key) {
if (properties.containsKey(key)) {
return properties.get(key).toString();
}
return this.bundle.getString(key);
}
/**
* Returns the value as {@code int} for the given key.
*
* @param key the property's key
* @return int value of the property
* @throws NumberFormatException if the String cannot be parsed to an Integer
*/
public int getInt(String key) {
return Integer.parseInt(getString(key));
}
public boolean containsKey(String key) {
return properties.containsKey(key) || bundle.containsKey(key);
}
/**
* Sets the value for the given key.
*
* @param key the property's key
* @param value the value of the property
*/
public void setProperty(String key, Object value) {
properties.put(key, value);
}
/**
* Returns all keys of this configuration.
*
* @return the keys
*/
public Set<String> listKeys() {
Set<String> keys = bundle.keySet();
keys.addAll(properties.keySet());
return keys;
}
}
+132
View File
@@ -0,0 +1,132 @@
package dslab.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Reads encryption keys from the file system.
*/
public final class Keys {
private Keys() {
// util class
}
/**
* Reads an RSA private key from the given file.
* The key files must be in DER format so JAVA can read it.
*
* @param file the file containing the RSA key
* @return a PrivateKey instance
* @throws IOException if an exception occurred while reading the key file
* @throws NullPointerException if encoded key is null
* @throws IllegalStateException if an error occurred in the java security api
*/
public static PrivateKey readPrivateKey(File file) throws IOException, NullPointerException, IllegalStateException {
try {
byte[] fileContent = Files.readAllBytes(file.toPath());
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(fileContent);
return createPrivateKey(spec);
}
catch (IOException e) {
throw new IOException(String.format("Cannot read key file %s.", file.getCanonicalPath()));
}
catch (NullPointerException e) {
throw new NullPointerException(String.format("Key seems to be empty from file %s", file.getCanonicalPath()));
}
}
/**
* Reads an RSA public key from the given file.
* The key files must be in DER format so JAVA can read it.
*
* @param file the file containing the RSA key
* @return a PublicKey instance
* @throws IOException if an exception occurred while reading the key file
* @throws NullPointerException if encoded key is null
* @throws IllegalStateException if an error occurred in the java security api
*/
public static PublicKey readPublicKey(File file) throws IOException, NullPointerException, IllegalStateException {
try {
byte[] fileContent = Files.readAllBytes(file.toPath());
X509EncodedKeySpec spec = new X509EncodedKeySpec(fileContent);
return createPublicKey(spec);
}
catch (IOException e) {
throw new IOException(String.format("Cannot read key file %s.", file.getCanonicalPath()), e);
}
catch (NullPointerException e) {
throw new NullPointerException(String.format("Key seems to be empty from file %s", file.getCanonicalPath()));
}
}
/**
* Reads the {@link SecretKeySpec} from the given location which is expected to contain a HMAC SHA-256 key.
*
* @param file the path to key located in the file system
* @return the secret key
* @throws IOException if an I/O error occurs or the security provider cannot handle the file
*/
public static SecretKeySpec readSecretKey(File file) throws IOException {
try (FileInputStream in = new FileInputStream(file)) {
byte[] keyBytes = new byte[1024];
if (in.read(keyBytes) < 0) {
throw new IOException(String.format("Cannot read key file %s.", file.getCanonicalPath()));
}
byte[] input = hexStringToByteArray(new String(keyBytes));
return new SecretKeySpec(input, "HmacSHA256");
}
}
private static PrivateKey createPrivateKey(KeySpec spec) {
try {
return getRsaKeyFactory().generatePrivate(spec);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("Error creating private key", e);
}
}
private static PublicKey createPublicKey(KeySpec spec) {
try {
return getRsaKeyFactory().generatePublic(spec);
} catch (InvalidKeySpecException e) {
throw new IllegalStateException("Error creating private key", e);
}
}
private static KeyFactory getRsaKeyFactory() throws IllegalStateException {
try {
return KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Error creating RSA key factory with default security provider", e);
}
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
}