briar-headless: Add command line arguments

parent 5783c1df
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.ConfigurationManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.io.File;
import java.io.IOException;
import java.nio.file.attribute.PosixFilePermission;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.nio.file.Files.setPosixFilePermissions;
import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE;
import static java.nio.file.attribute.PosixFilePermission.OWNER_READ;
import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
class JavaConfigurationManager implements ConfigurationManager {
private static final Logger LOG =
Logger.getLogger(JavaConfigurationManager.class.getName());
@Inject
public JavaConfigurationManager() {
try {
ensurePermissions(getAppDir());
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
@Override
public File getAppDir() {
String home = System.getProperty("user.home");
return new File(home + File.separator + ".briar");
}
private void ensurePermissions(File file)throws IOException {
if (!file.exists()) {
if (!file.mkdirs()) {
throw new IOException("Could not create directory");
}
}
Set<PosixFilePermission> perms = new HashSet<>();
perms.add(OWNER_READ);
perms.add(OWNER_WRITE);
perms.add(OWNER_EXECUTE);
setPosixFilePermissions(file.toPath(), perms);
}
}
package org.briarproject.bramble.system;
import org.briarproject.bramble.api.ConfigurationManager;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
......@@ -12,13 +11,6 @@ import dagger.Provides;
@Module
public class JavaSystemModule {
@Provides
@Singleton
ConfigurationManager provideConfigurationManager(
JavaConfigurationManager configurationManager) {
return configurationManager;
}
@Provides
@Singleton
LocationUtils provideLocationUtils(JavaLocationUtils locationUtils) {
......
plugins {
id 'java'
id 'net.ltgt.apt'
id 'idea'
id "org.jetbrains.kotlin.jvm" version "1.2.61"
id 'org.jetbrains.kotlin.jvm' version '1.2.61'
id "org.jetbrains.kotlin.kapt" version "1.2.61"
id 'witness'
}
apply from: 'witness.gradle'
......@@ -17,8 +17,8 @@ dependencies {
implementation 'io.javalin:javalin:2.1.0'
implementation 'org.slf4j:slf4j-simple:1.7.25'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.6'
implementation 'com.github.ajalt:clikt:1.5.0'
apt 'com.google.dagger:dagger-compiler:2.0.2'
kapt 'com.google.dagger:dagger-compiler:2.0.2'
testImplementation project(path: ':bramble-api', configuration: 'testOutput')
......@@ -34,7 +34,7 @@ dependencies {
jar {
manifest {
attributes(
'Main-Class': 'org.briarproject.briar.headless.Main'
'Main-Class': 'org.briarproject.briar.headless.MainKt'
)
}
from {
......@@ -42,6 +42,15 @@ jar {
}
}
// At the moment for non-Android projects we need to explicitly mark the code generated by kapt
// as 'generated source code' for correct highlighting and resolve in IDE.
idea {
module {
sourceDirs += file('build/generated/source/kapt/main')
generatedSourceDirs += file('build/generated/source/kapt/main')
}
}
test {
useJUnitPlatform()
testLogging {
......
package org.briarproject.briar.headless;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import java.io.Console;
import java.util.Scanner;
import java.util.logging.Logger;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.inject.Singleton;
import static java.lang.System.console;
import static java.lang.System.err;
import static java.lang.System.exit;
import static java.lang.System.out;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
@Immutable
@Singleton
@MethodsNotNullByDefault
@ParametersAreNonnullByDefault
public class BriarService {
private final static Logger LOG = getLogger(BriarService.class.getName());
private final AccountManager accountManager;
private final LifecycleManager lifecycleManager;
@Inject
public BriarService(AccountManager accountManager,
LifecycleManager lifecycleManager) {
this.accountManager = accountManager;
this.lifecycleManager = lifecycleManager;
}
public void start() {
Console console = console();
out.println("Welcome to Briar!\n");
if (!accountManager.accountExists()) {
if (console == null) {
LOG.warning("No account found.");
LOG.warning("Please start in terminal to set one up.");
exit(1);
}
console.printf("No account found. Let's create one!\n\n");
String nickname = createNickname(console);
String password = createPassword(console);
accountManager.createAccount(nickname, password);
} else {
out.print("Password: ");
String password;
if (console == null) {
Scanner scanner = new Scanner(System.in);
password = scanner.nextLine();
} else {
password = new String(console.readPassword());
}
if (!accountManager.signIn(password)) {
err.println("Error: Password invalid");
exit(1);
}
}
assert accountManager.getDatabaseKey() != null;
lifecycleManager.startServices(accountManager.getDatabaseKey());
}
private String createNickname(Console console) {
String nickname;
boolean error;
do {
nickname = console.readLine("Nickname: ");
if (nickname.length() == 0) {
console.printf("Please enter a nickname!\n");
error = true;
} else if (nickname.length() > MAX_AUTHOR_NAME_LENGTH) {
console.printf("Please choose a shorter nickname!\n");
error = true;
} else {
error = false;
}
} while (error);
return nickname;
}
private String createPassword(Console console) {
String password;
boolean error;
do {
password = new String(console.readPassword("Password: "));
if (password.length() < 4) {
console.printf(
"Please enter a password with at least 4 characters!\n");
error = true;
// TODO enforce stronger password
} else {
error = false;
}
} while (error);
return password;
}
public void stop() {
lifecycleManager.stopServices();
}
}
package org.briarproject.briar.headless
import com.github.ajalt.clikt.core.UsageError
import com.github.ajalt.clikt.output.TermUi.echo
import com.github.ajalt.clikt.output.TermUi.prompt
import org.briarproject.bramble.api.account.AccountManager
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK
import org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH
import org.briarproject.bramble.api.lifecycle.LifecycleManager
import java.lang.System.exit
import javax.annotation.concurrent.Immutable
import javax.inject.Inject
import javax.inject.Singleton
@Immutable
@Singleton
class BriarService @Inject
constructor(
private val accountManager: AccountManager,
private val lifecycleManager: LifecycleManager,
private val passwordStrengthEstimator: PasswordStrengthEstimator
) {
fun start() {
if (!accountManager.accountExists()) {
createAccount()
} else {
val password = prompt("Password")
?: throw UsageError("Could not get password. Is STDIN connected?")
if (!accountManager.signIn(password)) {
echo("Error: Password invalid")
exit(1)
}
}
val dbKey = accountManager.databaseKey ?: throw AssertionError()
lifecycleManager.startServices(dbKey)
}
fun stop() {
lifecycleManager.stopServices()
lifecycleManager.waitForShutdown()
}
private fun createAccount() {
echo("No account found. Let's create one!\n\n")
val nickname = prompt("Nickname") { nickname ->
if (nickname.length > MAX_AUTHOR_NAME_LENGTH)
throw UsageError("Please choose a shorter nickname!")
nickname
}
val password = prompt("Password") { password ->
if (passwordStrengthEstimator.estimateStrength(password) < QUITE_WEAK)
throw UsageError("Please enter a stronger password!")
password
}
if (nickname == null || password == null)
throw UsageError("Could not get account information. Is STDIN connected?")
accountManager.createAccount(nickname, password)
}
}
package org.briarproject.briar.headless;
import org.briarproject.bramble.api.ConfigurationManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reporting.DevConfig;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.network.JavaNetworkModule;
import org.briarproject.bramble.plugin.tor.CircumventionModule;
import org.briarproject.bramble.plugin.tor.CircumventionProvider;
import org.briarproject.bramble.plugin.tor.LinuxTorPluginFactory;
import org.briarproject.bramble.system.JavaSystemModule;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.headless.messaging.MessagingModule;
import java.io.File;
import java.security.GeneralSecurityException;
import java.util.Collection;
import java.util.concurrent.Executor;
import javax.inject.Singleton;
import javax.net.SocketFactory;
import dagger.Module;
import dagger.Provides;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS;
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
@Module(includes = {
JavaNetworkModule.class,
JavaSystemModule.class,
CircumventionModule.class,
MessagingModule.class
})
public class HeadlessModule {
@Provides
@Singleton
DatabaseConfig provideDatabaseConfig(
ConfigurationManager configurationManager) {
File dbDir = appDir(configurationManager, "db");
File keyDir = appDir(configurationManager, "key");
return new HeadlessDatabaseConfig(dbDir, keyDir);
}
@Provides
PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor,
SocketFactory torSocketFactory, BackoffFactory backoffFactory,
NetworkManager networkManager, LocationUtils locationUtils,
EventBus eventBus, ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider, Clock clock,
ConfigurationManager configurationManager) {
File torDirectory = appDir(configurationManager, "tor");
DuplexPluginFactory tor = new LinuxTorPluginFactory(ioExecutor,
networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, circumventionProvider, clock,
torDirectory);
Collection<DuplexPluginFactory> duplex = singletonList(tor);
@NotNullByDefault
PluginConfig pluginConfig = new PluginConfig() {
@Override
public Collection<DuplexPluginFactory> getDuplexFactories() {
return duplex;
}
@Override
public Collection<SimplexPluginFactory> getSimplexFactories() {
return emptyList();
}
@Override
public boolean shouldPoll() {
return true;
}
};
return pluginConfig;
}
@Provides
@Singleton
DevConfig provideDevConfig(CryptoComponent crypto,
ConfigurationManager configurationManager) {
@NotNullByDefault
DevConfig devConfig = new DevConfig() {
@Override
public PublicKey getDevPublicKey() {
try {
return crypto.getMessageKeyParser().parsePublicKey(
StringUtils.fromHexString(DEV_PUBLIC_KEY_HEX));
} catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}
@Override
public String getDevOnionAddress() {
return DEV_ONION_ADDRESS;
}
@Override
public File getReportDir() {
return appDir(configurationManager, "reportDir");
}
};
return devConfig;
}
@Provides
@Singleton
WebSocketController provideWebSocketHandler(
WebSocketControllerImpl webSocketController) {
return webSocketController;
}
private File appDir(ConfigurationManager configurationManager,
String file) {
return new File(configurationManager.getAppDir(), file);
}
}
package org.briarproject.briar.headless
import dagger.Module
import dagger.Provides
import org.briarproject.bramble.api.crypto.CryptoComponent
import org.briarproject.bramble.api.crypto.PublicKey
import org.briarproject.bramble.api.db.DatabaseConfig
import org.briarproject.bramble.api.event.EventBus
import org.briarproject.bramble.api.lifecycle.IoExecutor
import org.briarproject.bramble.api.network.NetworkManager
import org.briarproject.bramble.api.plugin.BackoffFactory
import org.briarproject.bramble.api.plugin.PluginConfig
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory
import org.briarproject.bramble.api.reporting.DevConfig
import org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS
import org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX
import org.briarproject.bramble.api.system.Clock
import org.briarproject.bramble.api.system.LocationUtils
import org.briarproject.bramble.api.system.ResourceProvider
import org.briarproject.bramble.network.JavaNetworkModule
import org.briarproject.bramble.plugin.tor.CircumventionModule
import org.briarproject.bramble.plugin.tor.CircumventionProvider
import org.briarproject.bramble.plugin.tor.LinuxTorPluginFactory
import org.briarproject.bramble.system.JavaSystemModule
import org.briarproject.bramble.util.StringUtils.fromHexString
import org.briarproject.briar.headless.messaging.MessagingModule
import java.io.File
import java.security.GeneralSecurityException
import java.util.Collections.emptyList
import java.util.concurrent.Executor
import javax.inject.Singleton
import javax.net.SocketFactory
@Module(
includes = [
JavaNetworkModule::class,
JavaSystemModule::class,
CircumventionModule::class,
MessagingModule::class
]
)
class HeadlessModule(private val appDir: File) {
@Provides
@Singleton
internal fun provideDatabaseConfig(): DatabaseConfig {
val dbDir = File(appDir, "db")
val keyDir = File(appDir, "key")
return HeadlessDatabaseConfig(dbDir, keyDir)
}
@Provides
internal fun providePluginConfig(
@IoExecutor ioExecutor: Executor, torSocketFactory: SocketFactory,
backoffFactory: BackoffFactory, networkManager: NetworkManager,
locationUtils: LocationUtils, eventBus: EventBus,
resourceProvider: ResourceProvider,
circumventionProvider: CircumventionProvider, clock: Clock
): PluginConfig {
val torDirectory = File(appDir, "tor")
val tor = LinuxTorPluginFactory(
ioExecutor,
networkManager, locationUtils, eventBus, torSocketFactory,
backoffFactory, resourceProvider, circumventionProvider, clock,
torDirectory
)
val duplex = listOf<DuplexPluginFactory>(tor)
return object : PluginConfig {
override fun getDuplexFactories(): Collection<DuplexPluginFactory> {
return duplex
}
override fun getSimplexFactories(): Collection<SimplexPluginFactory> {
return emptyList()
}
override fun shouldPoll(): Boolean {
return true
}
}
}
@Provides
@Singleton
internal fun provideDevConfig(crypto: CryptoComponent): DevConfig {
return object : DevConfig {
override fun getDevPublicKey(): PublicKey {
try {
return crypto.messageKeyParser
.parsePublicKey(fromHexString(DEV_PUBLIC_KEY_HEX))
} catch (e: GeneralSecurityException) {
throw RuntimeException(e)
}
}
override fun getDevOnionAddress(): String {
return DEV_ONION_ADDRESS
}
override fun getReportDir(): File {
return File(appDir, "reportDir")
}
}
}
@Provides
@Singleton
internal fun provideWebSocketHandler(
webSocketController: WebSocketControllerImpl): WebSocketController {
return webSocketController
}
}
package org.briarproject.briar.headless;
import org.briarproject.bramble.BrambleCoreModule;
import org.briarproject.briar.BriarCoreModule;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
public class Main {
public static void main(String[] args) {
Logger rootLogger = LogManager.getLogManager().getLogger("");
rootLogger.setLevel(Level.WARNING);
for (String arg : args) {
if (arg.equals("-v")) {
rootLogger.setLevel(Level.INFO);
}
}
BriarHeadlessApp app = DaggerBriarHeadlessApp.builder()
.headlessModule(new HeadlessModule()).build();
// We need to load the eager singletons directly after making the
// dependency graphs
BrambleCoreModule.initEagerSingletons(app);
BriarCoreModule.initEagerSingletons(app);
app.router().start();
}
}
package org.briarproject.briar.headless
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.counted
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.types.int
import org.briarproject.bramble.BrambleCoreModule
import org.briarproject.briar.BriarCoreModule
import org.slf4j.impl.SimpleLogger.DEFAULT_LOG_LEVEL_KEY
import java.io.File
import java.io.File.separator
import java.io.IOException
import java.lang.System.getProperty
import java.lang.System.setProperty
import java.nio.file.Files.setPosixFilePermissions
import java.nio.file.attribute.PosixFilePermission
import java.nio.file.attribute.PosixFilePermission.*
import java.util.logging.Level.*
import java.util.logging.LogManager