Rust implementation of NextGraph, a Decentralized and local-first web 3.0 ecosystem
https://nextgraph.org
byzantine-fault-tolerancecrdtsdappsdecentralizede2eeeventual-consistencyjson-ldlocal-firstmarkdownocapoffline-firstp2pp2p-networkprivacy-protectionrdfrich-text-editorself-hostedsemantic-websparqlweb3collaboration
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1049 lines
39 KiB
1049 lines
39 KiB
// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers
|
|
// All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0
|
|
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0>
|
|
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
|
|
// at your option. All files in the project carrying such
|
|
// notice may not be copied, modified, or distributed except
|
|
// according to those terms.
|
|
|
|
pub mod types;
|
|
|
|
mod cli;
|
|
|
|
use crate::cli::*;
|
|
use crate::types::*;
|
|
use clap::Parser;
|
|
use ng_broker::interfaces::*;
|
|
use ng_broker::server_ws::run_server_v0;
|
|
use ng_broker::types::*;
|
|
use ng_broker::utils::*;
|
|
use ng_net::types::*;
|
|
use ng_net::utils::is_private_ip;
|
|
use ng_net::utils::is_public_ip;
|
|
use ng_net::utils::is_public_ipv4;
|
|
use ng_net::utils::is_public_ipv6;
|
|
use ng_net::utils::{
|
|
gen_dh_keys, is_ipv4_global, is_ipv4_private, is_ipv6_global, is_ipv6_private,
|
|
};
|
|
use ng_net::{WS_PORT, WS_PORT_REVERSE_PROXY};
|
|
use ng_repo::log::*;
|
|
use ng_repo::types::Sig;
|
|
use ng_repo::types::SymKey;
|
|
use ng_repo::utils::ed_keypair_from_priv_bytes;
|
|
use ng_repo::{
|
|
types::{PrivKey, PubKey},
|
|
utils::{decode_key, generate_keypair, sign, verify},
|
|
};
|
|
use serde_json::{from_str, to_string_pretty};
|
|
use std::fs::{read_to_string, write};
|
|
use std::io::ErrorKind;
|
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
|
|
use std::path::PathBuf;
|
|
use zeroize::Zeroize;
|
|
|
|
use addr::parser::DnsName;
|
|
use addr::psl::List;
|
|
use lazy_static::lazy_static;
|
|
use regex::Regex;
|
|
|
|
//For windows: {846EE342-7039-11DE-9D20-806E6F6E6963}
|
|
//For the other OSes: en0 lo ...
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
lazy_static! {
|
|
#[doc(hidden)]
|
|
static ref RE_INTERFACE: Regex = Regex::new(r"^([0-9a-z]{2,16})(\:\d{1,5})?$").unwrap();
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
lazy_static! {
|
|
#[doc(hidden)]
|
|
static ref RE_INTERFACE: Regex = Regex::new(
|
|
r"^(\{[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}\})(\:\d{1,5})?$"
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
lazy_static! {
|
|
#[doc(hidden)]
|
|
static ref RE_IPV6_WITH_PORT: Regex =
|
|
Regex::new(r"^\[([0-9a-fA-F:]{3,39})\](\:\d{1,5})?$").unwrap();
|
|
}
|
|
|
|
pub static DEFAULT_PORT: u16 = WS_PORT;
|
|
|
|
pub static DEFAULT_TLS_PORT: u16 = 443;
|
|
|
|
fn parse_interface_and_port_for(
|
|
string: &String,
|
|
for_option: &str,
|
|
default_port: u16,
|
|
) -> Result<(String, u16), ()> {
|
|
let c = RE_INTERFACE.captures(string);
|
|
|
|
if c.is_some() && c.as_ref().unwrap().get(1).is_some() {
|
|
let cap = c.unwrap();
|
|
let interface = cap.get(1).unwrap().as_str();
|
|
let port = match cap.get(2) {
|
|
None => default_port,
|
|
Some(p) => {
|
|
let mut chars = p.as_str().chars();
|
|
chars.next();
|
|
match from_str::<u16>(chars.as_str()) {
|
|
Err(_) => default_port,
|
|
Ok(p) => {
|
|
if p == 0 {
|
|
default_port
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
Ok((interface.to_string(), port))
|
|
} else {
|
|
log_err!(
|
|
"The <INTERFACE:PORT> value submitted for the {} option is invalid. It should be the name of an interface found with --list-interfaces, with an optional port suffix of the form :123. cannot start",
|
|
for_option
|
|
);
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
fn parse_ipv6_for(string: String, for_option: &str) -> Result<Ipv6Addr, ()> {
|
|
string.parse::<Ipv6Addr>().map_err(|_| {
|
|
log_err!(
|
|
"The <IPv6> value submitted for the {} option is invalid. cannot start",
|
|
for_option
|
|
)
|
|
})
|
|
}
|
|
|
|
fn parse_ipv4_and_port_for(
|
|
string: String,
|
|
for_option: &str,
|
|
default_port: u16,
|
|
) -> Result<(Ipv4Addr, u16), ()> {
|
|
let parts: Vec<&str> = string.split(":").collect();
|
|
let ipv4 = parts[0].parse::<Ipv4Addr>().map_err(|_| {
|
|
log_err!(
|
|
"The <IPv4:PORT> value submitted for the {} option is invalid. cannot start",
|
|
for_option
|
|
)
|
|
})?;
|
|
|
|
let port;
|
|
if parts.len() > 1 {
|
|
port = match from_str::<u16>(parts[1]) {
|
|
Err(_) => default_port,
|
|
Ok(p) => {
|
|
if p == 0 {
|
|
default_port
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
};
|
|
} else {
|
|
port = default_port;
|
|
}
|
|
return Ok((ipv4, port));
|
|
}
|
|
|
|
fn parse_ip_and_port_for(string: String, for_option: &str) -> Result<(IpAddr, u16), ()> {
|
|
let c = RE_IPV6_WITH_PORT.captures(&string);
|
|
let ipv6;
|
|
let port;
|
|
if c.is_some() && c.as_ref().unwrap().get(1).is_some() {
|
|
let cap = c.unwrap();
|
|
let ipv6_str = cap.get(1).unwrap().as_str();
|
|
port = match cap.get(2) {
|
|
None => DEFAULT_PORT,
|
|
Some(p) => {
|
|
let mut chars = p.as_str().chars();
|
|
chars.next();
|
|
match from_str::<u16>(chars.as_str()) {
|
|
Err(_) => DEFAULT_PORT,
|
|
Ok(p) => {
|
|
if p == 0 {
|
|
DEFAULT_PORT
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let ipv6 = ipv6_str.parse::<Ipv6Addr>().map_err(|_| {
|
|
log_err!(
|
|
"The <[IPv6]:PORT> value submitted for the {} option is invalid. cannot start",
|
|
for_option
|
|
)
|
|
})?;
|
|
return Ok((IpAddr::V6(ipv6), port));
|
|
} else {
|
|
// we try just an IPV6 without port
|
|
let ipv6_res = string.parse::<Ipv6Addr>();
|
|
if ipv6_res.is_err() {
|
|
// let's try IPv4
|
|
|
|
return parse_ipv4_and_port_for(string, for_option, DEFAULT_PORT)
|
|
.map(|ipv4| (IpAddr::V4(ipv4.0), ipv4.1));
|
|
} else {
|
|
ipv6 = ipv6_res.unwrap();
|
|
port = DEFAULT_PORT;
|
|
return Ok((IpAddr::V6(ipv6), port));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_triple_interface_and_port_for(
|
|
string: &String,
|
|
for_option: &str,
|
|
) -> Result<((String, u16), (Option<Ipv6Addr>, (Ipv4Addr, u16))), ()> {
|
|
let parts: Vec<&str> = string.split(',').collect();
|
|
if parts.len() < 2 {
|
|
log_err!(
|
|
"The <PRIVATE_INTERFACE:PORT,[PUBLIC_IPV6,]PUBLIC_IPV4:PORT> value submitted for the {} option is invalid. It should be composed of at least 2 parts separated by a comma. cannot start",
|
|
for_option
|
|
);
|
|
return Err(());
|
|
}
|
|
let first_part = parse_interface_and_port_for(
|
|
&parts[0].to_string(),
|
|
&format!("private interface+PORT (left) part of the {}", for_option),
|
|
DEFAULT_PORT,
|
|
);
|
|
if first_part.is_err() {
|
|
return Err(());
|
|
}
|
|
|
|
let mut middle_part = None;
|
|
if parts.len() == 3 {
|
|
let middle_part_res = parse_ipv6_for(
|
|
parts[1].to_string(),
|
|
&format!("public IPv6 (middle) part of the {}", for_option),
|
|
);
|
|
if middle_part_res.is_err() {
|
|
return Err(());
|
|
}
|
|
middle_part = middle_part_res.ok();
|
|
}
|
|
|
|
let last_part = parse_ipv4_and_port_for(
|
|
parts[parts.len() - 1].to_string(),
|
|
&format!("public IPv4+PORT (right) part of the {}", for_option),
|
|
DEFAULT_PORT,
|
|
);
|
|
if last_part.is_err() {
|
|
return Err(());
|
|
}
|
|
|
|
Ok((first_part.unwrap(), (middle_part, last_part.unwrap())))
|
|
}
|
|
|
|
fn parse_domain_and_port(
|
|
domain_string: &String,
|
|
option: &str,
|
|
default_port: u16,
|
|
) -> Result<(String, String, u16), ()> {
|
|
let parts: Vec<&str> = domain_string.split(':').collect();
|
|
|
|
// check validity of domain name
|
|
let valid_domain = List.parse_dns_name(parts[0]);
|
|
match valid_domain {
|
|
Err(e) => {
|
|
log_err!(
|
|
"The domain name provided for option {} is invalid. {}. cannot start",
|
|
option,
|
|
e.to_string()
|
|
);
|
|
return Err(());
|
|
}
|
|
Ok(name) => {
|
|
if !name.has_known_suffix() {
|
|
log_err!(
|
|
"The domain name provided for option {} is invalid. Unknown suffix in public list. cannot start", option
|
|
);
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
|
|
let port = if parts.len() > 1 {
|
|
match from_str::<u16>(parts[1]) {
|
|
Err(_) => default_port,
|
|
Ok(p) => {
|
|
if p == 0 {
|
|
default_port
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
default_port
|
|
};
|
|
let mut domain_with_port = parts[0].clone().to_string();
|
|
if port != default_port {
|
|
domain_with_port.push_str(&format!(":{}", port));
|
|
}
|
|
Ok((parts[0].to_string(), domain_with_port, port))
|
|
}
|
|
|
|
fn prepare_accept_forward_for_domain(
|
|
domain: String,
|
|
args: &mut Cli,
|
|
) -> Result<AcceptForwardForV0, ()> {
|
|
if args.domain_peer.is_some() {
|
|
let key = decode_key(args.domain_peer.as_ref().unwrap().as_str())?;
|
|
args.domain_peer.as_mut().unwrap().zeroize();
|
|
|
|
Ok(AcceptForwardForV0::PublicDomainPeer((
|
|
domain,
|
|
PrivKey::Ed25519PrivKey(key),
|
|
"".to_string(),
|
|
)))
|
|
} else {
|
|
Ok(AcceptForwardForV0::PublicDomain((domain, "".to_string())))
|
|
}
|
|
}
|
|
|
|
#[async_std::main]
|
|
async fn main() -> std::io::Result<()> {
|
|
main_inner()
|
|
.await
|
|
.map_err(|_| ErrorKind::InvalidInput.into())
|
|
}
|
|
|
|
async fn main_inner() -> Result<(), ()> {
|
|
let mut args = Cli::parse();
|
|
|
|
if args.list_interfaces {
|
|
println!("list of network interfaces");
|
|
print_interfaces();
|
|
return Ok(());
|
|
}
|
|
|
|
if std::env::var("RUST_LOG").is_err() {
|
|
if args.verbose == 0 {
|
|
std::env::set_var("RUST_LOG", "warn");
|
|
} else if args.verbose == 1 {
|
|
std::env::set_var("RUST_LOG", "info");
|
|
} else if args.verbose == 2 {
|
|
std::env::set_var("RUST_LOG", "debug");
|
|
} else if args.verbose >= 3 {
|
|
std::env::set_var("RUST_LOG", "trace");
|
|
}
|
|
}
|
|
env_logger::init();
|
|
|
|
log_info!("Starting NextGraph daemon (ngd)");
|
|
|
|
log_debug!("base {:?}", args.base);
|
|
|
|
let mut path = PathBuf::from(&args.base);
|
|
path.push("server");
|
|
if !path.is_absolute() {
|
|
path = std::env::current_dir().unwrap().join(path);
|
|
}
|
|
|
|
log_debug!("cur {}", std::env::current_dir().unwrap().display());
|
|
log_debug!("home {}", path.to_str().unwrap());
|
|
std::fs::create_dir_all(path.clone()).unwrap();
|
|
|
|
// reading key from file, if any
|
|
let mut key_path = path.clone();
|
|
key_path.push("key");
|
|
let key_from_file: Option<[u8; 32]>;
|
|
let res = |key_path| -> Result<[u8; 32], &str> {
|
|
let mut file = read_to_string(key_path).map_err(|_| "")?;
|
|
let first_line = file.lines().nth(0).ok_or("empty file")?;
|
|
let res = decode_key(first_line.trim()).map_err(|_| "invalid file");
|
|
file.zeroize();
|
|
res
|
|
}(&key_path);
|
|
|
|
if res.is_err() && res.unwrap_err().len() > 0 {
|
|
log_err!(
|
|
"provided key file is incorrect. {}. cannot start",
|
|
res.unwrap_err()
|
|
);
|
|
return Err(());
|
|
}
|
|
key_from_file = res.ok();
|
|
|
|
let mut keys: [[u8; 32]; 4] = match &args.key {
|
|
Some(key_string) => {
|
|
if key_from_file.is_some() {
|
|
log_err!("provided --key option will not be used as a key file is already present");
|
|
args.key.as_mut().unwrap().zeroize();
|
|
gen_broker_keys(key_from_file)
|
|
} else {
|
|
let res = decode_key(key_string.as_str())
|
|
.map_err(|_| log_err!("provided key is invalid. cannot start"))?;
|
|
if args.save_key {
|
|
let mut master_key = base64_url::encode(&res);
|
|
write(key_path.clone(), &master_key).map_err(|e| {
|
|
log_err!("cannot save key to file. {}.cannot start", e.to_string())
|
|
})?;
|
|
master_key.zeroize();
|
|
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
|
|
}
|
|
args.key.as_mut().unwrap().zeroize();
|
|
gen_broker_keys(Some(res))
|
|
}
|
|
}
|
|
None => {
|
|
if key_from_file.is_some() {
|
|
gen_broker_keys(key_from_file)
|
|
} else {
|
|
let res = gen_broker_keys(None);
|
|
let mut master_key = base64_url::encode(&res[0]);
|
|
if args.save_key {
|
|
write(key_path.clone(), &master_key).map_err(|e| {
|
|
log_err!("cannot save key to file. {}.cannot start", e.to_string())
|
|
})?;
|
|
log_info!("The key has been saved to {}", key_path.to_str().unwrap());
|
|
} else {
|
|
// on purpose we don't log the key, just print it out to stdout, as it should not be saved in logger's files
|
|
println!("YOUR GENERATED KEY IS: {}", master_key);
|
|
log_err!("At your request, the key wasn't saved. If you want to save it to disk, use ---save-key");
|
|
log_err!("provide it again to the next start of ngd with --key option or NG_SERVER_KEY env variable");
|
|
}
|
|
master_key.zeroize();
|
|
res
|
|
}
|
|
}
|
|
};
|
|
|
|
key_from_file.and_then(|mut key| {
|
|
key.zeroize();
|
|
None::<()>
|
|
});
|
|
|
|
let mut sign_path = path.clone();
|
|
sign_path.push("sign");
|
|
let sign_from_file: Option<[u8; 32]>;
|
|
let res = |sign_path| -> Result<(), &str> {
|
|
let file = std::fs::read(sign_path).map_err(|_| "")?;
|
|
let sig: Sig = serde_bare::from_slice(&file).map_err(|_| "invalid serialization")?;
|
|
let privkey: PrivKey = keys[3].into();
|
|
let pubkey = privkey.to_pub();
|
|
verify(&vec![110u8, 103u8, 100u8], sig, pubkey).map_err(|_| "invalid signature")?;
|
|
Ok(())
|
|
}(&sign_path);
|
|
|
|
if res.is_err() {
|
|
if res.unwrap_err().len() > 0 {
|
|
log_err!(
|
|
"provided key is invalid. {}. cannot start",
|
|
res.unwrap_err()
|
|
);
|
|
return Err(());
|
|
} else {
|
|
// time to save the signature
|
|
let privkey: PrivKey = keys[3].into();
|
|
let pubkey = privkey.to_pub();
|
|
let sig = sign(&privkey, &pubkey, &vec![110u8, 103u8, 100u8]);
|
|
if sig.is_err() {
|
|
log_err!("cannot save signature. cannot start");
|
|
return Err(());
|
|
}
|
|
let sig_ser = serde_bare::to_vec(&sig.unwrap()).unwrap();
|
|
let res = std::fs::write(sign_path, sig_ser);
|
|
if res.is_err() {
|
|
log_err!("cannot save signature. {}. cannot start", res.unwrap_err());
|
|
return Err(());
|
|
}
|
|
}
|
|
}
|
|
|
|
// DEALING WITH CONFIG
|
|
|
|
// reading config from file, if any
|
|
let mut config_path = path.clone();
|
|
config_path.push("config.json");
|
|
let mut config: Option<DaemonConfig>;
|
|
let res = |config_path| -> Result<DaemonConfig, String> {
|
|
let file = read_to_string(config_path).map_err(|_| "".to_string())?;
|
|
from_str(&file).map_err(|e| e.to_string())
|
|
}(&config_path);
|
|
|
|
if res.is_err() && res.as_ref().unwrap_err().len() > 0 {
|
|
log_err!(
|
|
"provided config file is incorrect. {}. cannot start",
|
|
res.unwrap_err()
|
|
);
|
|
return Err(());
|
|
}
|
|
config = res.ok();
|
|
|
|
if config.is_some() && args.save_config {
|
|
log_err!("A config file is present. We cannot override it with Quick config options. cannot start");
|
|
return Err(());
|
|
}
|
|
|
|
if args.local.is_some()
|
|
|| args.forward.is_some()
|
|
|| args.core.is_some()
|
|
|| args.private.is_some()
|
|
|| args.public.is_some()
|
|
|| args.dynamic.is_some()
|
|
|| args.domain.is_some()
|
|
|| args.domain_private.is_some()
|
|
{
|
|
// QUICK CONFIG
|
|
|
|
if config.is_some() && !args.print_config {
|
|
log_err!(
|
|
"A config file is present. You cannot use the Quick config options on the command-line. In order to use them, delete your config file first. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
|
|
if args.domain_peer.is_some() && args.domain_private.is_none() && args.domain.is_none() {
|
|
log_err!(
|
|
"The --domain-peer option can only be set when the --domain or --domain-private option is also present on the command line. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
|
|
let mut listeners: Vec<ListenerV0> = vec![];
|
|
let mut overlays_config: BrokerOverlayConfigV0 = BrokerOverlayConfigV0::new();
|
|
|
|
let interfaces = get_interface();
|
|
|
|
//// --domain
|
|
|
|
if args.domain.is_some() {
|
|
let domain_string = args.domain.as_ref().unwrap();
|
|
let parts: Vec<&str> = domain_string.split(',').collect();
|
|
let local_port;
|
|
let (_, domain, _) = if parts.len() == 1 {
|
|
local_port = WS_PORT_REVERSE_PROXY;
|
|
parse_domain_and_port(domain_string, "--domain", DEFAULT_TLS_PORT)?
|
|
} else {
|
|
local_port = match from_str::<u16>(parts[1]) {
|
|
Err(_) => WS_PORT_REVERSE_PROXY,
|
|
Ok(p) => {
|
|
if p == 0 {
|
|
WS_PORT_REVERSE_PROXY
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
};
|
|
parse_domain_and_port(&parts[0].to_string(), "--domain", DEFAULT_TLS_PORT)?
|
|
};
|
|
|
|
match find_first(&interfaces, InterfaceType::Loopback) {
|
|
None => {
|
|
log_err!(
|
|
"That's pretty unusual, but no loopback interface could be found on your host. --domain option failed for that reason. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(loopback) => {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
let mut listener = ListenerV0::new_direct(loopback, !args.no_ipv6, local_port);
|
|
listener.accept_direct = false;
|
|
let res = prepare_accept_forward_for_domain(domain, &mut args).map_err(|_| {
|
|
log_err!("The --domain-peer option has an invalid key. it must be a base64_url encoded serialization of a PrivKey. cannot start")
|
|
})?;
|
|
listener.accept_forward_for = res;
|
|
listeners.push(listener);
|
|
}
|
|
}
|
|
}
|
|
|
|
//// --local
|
|
|
|
if args.local.is_some() {
|
|
match find_first(&interfaces, InterfaceType::Loopback) {
|
|
None => {
|
|
log_err!(
|
|
"That's pretty unusual, but no loopback interface could be found on your host. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(loopback) => {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
|
|
if listeners.last().is_some()
|
|
&& listeners.last().unwrap().interface_name == loopback.name
|
|
&& listeners.last().unwrap().port == args.local.unwrap()
|
|
{
|
|
if args.domain_peer.is_some() {
|
|
log_err!(
|
|
"--local is not allowed if --domain-peer is selected, as they both use the same port. change the port of one of them. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
let r = listeners.last_mut().unwrap();
|
|
r.accept_direct = true;
|
|
r.ipv6 = !args.no_ipv6;
|
|
} else {
|
|
listeners.push(ListenerV0::new_direct(
|
|
loopback,
|
|
!args.no_ipv6,
|
|
args.local.unwrap(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// --core
|
|
// core listeners always come after the domain ones, which is good as the first bootstrap in the list should be the domain (if there is also a core_with_clients that generates a Public bootstrap)
|
|
if args.core.is_some() {
|
|
let arg_value =
|
|
parse_interface_and_port_for(args.core.as_ref().unwrap(), "--core", DEFAULT_PORT)?;
|
|
|
|
let if_name = &arg_value.0;
|
|
match find_first_or_name(&interfaces, InterfaceType::Public, &if_name) {
|
|
None => {
|
|
log_err!(
|
|
"{}",
|
|
if if_name == "default" {
|
|
"We could not find a public IP interface on your host. If you are setting up a server behind a reverse proxy, enter the config manually in the config file. cannot start".to_string()
|
|
} else {
|
|
format!(
|
|
"We could not find a public IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
|
|
if_name
|
|
)
|
|
}
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(public) => {
|
|
overlays_config.core = BrokerOverlayPermission::AllRegisteredUser;
|
|
let mut listener = ListenerV0::new_direct(public, !args.no_ipv6, arg_value.1);
|
|
if args.core_with_clients {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
} else {
|
|
listener.refuse_clients = true;
|
|
}
|
|
listener.serve_app = false;
|
|
listeners.push(listener);
|
|
}
|
|
}
|
|
}
|
|
|
|
//// --public
|
|
|
|
if args.public.is_some() {
|
|
let arg_value =
|
|
parse_triple_interface_and_port_for(args.public.as_ref().unwrap(), "--public")?;
|
|
|
|
let public_part = &arg_value.1;
|
|
let private_part = &arg_value.0;
|
|
let private_interface;
|
|
let if_name = &private_part.0;
|
|
match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) {
|
|
None => {
|
|
log_err!(
|
|
"{}",
|
|
if if_name == "default" {
|
|
"We could not find a private IP interface on your host for --public option. cannot start"
|
|
.to_string()
|
|
} else {
|
|
format!(
|
|
"We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
|
|
if_name
|
|
)
|
|
}
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(inter) => {
|
|
private_interface = inter;
|
|
}
|
|
}
|
|
|
|
if !is_public_ipv4(&public_part.1 .0)
|
|
|| public_part.0.is_some() && !is_public_ipv6(public_part.0.as_ref().unwrap())
|
|
{
|
|
log_err!("The provided IPs are not public. cannot start");
|
|
return Err(());
|
|
}
|
|
|
|
if args.no_ipv6 && public_part.0.is_some() {
|
|
log_err!(
|
|
"The public IP is IPv6 but you selected the --no-ipv6 option. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
|
|
overlays_config.core = BrokerOverlayPermission::AllRegisteredUser;
|
|
if !args.public_without_clients {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
}
|
|
|
|
let ipv6 = public_part.0.map(|ipv6| BindAddress {
|
|
port: public_part.1 .1,
|
|
ip: (&IpAddr::V6(ipv6)).into(),
|
|
});
|
|
|
|
listeners.push(ListenerV0 {
|
|
interface_name: private_interface.name,
|
|
if_type: private_interface.if_type,
|
|
ipv6: public_part.0.is_some(),
|
|
interface_refresh: 0,
|
|
port: private_part.1,
|
|
private_core: false,
|
|
discoverable: false,
|
|
refuse_clients: args.public_without_clients,
|
|
serve_app: false,
|
|
accept_direct: false,
|
|
bind_public_ipv6: ipv6.is_some() && args.bind_public_ipv6,
|
|
accept_forward_for: AcceptForwardForV0::PublicStatic((
|
|
BindAddress {
|
|
port: public_part.1 .1,
|
|
ip: (&IpAddr::V4(public_part.1 .0)).into(),
|
|
},
|
|
ipv6,
|
|
"".to_string(),
|
|
)),
|
|
});
|
|
}
|
|
|
|
//// --dynamic
|
|
|
|
if args.dynamic.is_some() {
|
|
let dynamic_string = args.dynamic.as_ref().unwrap();
|
|
let parts: Vec<&str> = dynamic_string.split(',').collect();
|
|
|
|
let arg_value =
|
|
parse_interface_and_port_for(&parts[0].to_string(), "--dynamic", DEFAULT_PORT)?;
|
|
|
|
let public_port = if parts.len() == 2 {
|
|
match from_str::<u16>(parts[1]) {
|
|
Err(_) => DEFAULT_PORT,
|
|
Ok(p) => {
|
|
if p == 0 {
|
|
DEFAULT_PORT
|
|
} else {
|
|
p
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
DEFAULT_PORT
|
|
};
|
|
|
|
let if_name = &arg_value.0;
|
|
|
|
match find_first_or_name(&interfaces, InterfaceType::Private, if_name) {
|
|
None => {
|
|
log_err!(
|
|
"{}",
|
|
if if_name == "default" {
|
|
"We could not find a private IP interface on your host for --dynamic option. cannot start"
|
|
.to_string()
|
|
} else {
|
|
format!(
|
|
"We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
|
|
if_name
|
|
)
|
|
}
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(inter) => {
|
|
overlays_config.core = BrokerOverlayPermission::AllRegisteredUser;
|
|
if !args.public_without_clients {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
}
|
|
|
|
if listeners.last().is_some()
|
|
&& listeners.last().unwrap().interface_name == inter.name
|
|
&& listeners.last().unwrap().port == arg_value.1
|
|
{
|
|
let r = listeners.last_mut().unwrap();
|
|
if r.accept_forward_for != AcceptForwardForV0::No {
|
|
log_err!("The same private interface is already forwarding with a different setting, probably because of a --public option conflicting with a --dynamic option. Changing the port on one of the interfaces can help. cannot start");
|
|
return Err(());
|
|
}
|
|
panic!("this should never happen. --dynamic created after a --private");
|
|
//r.ipv6 = !args.no_ipv6;
|
|
//r.accept_forward_for = AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string()));
|
|
} else {
|
|
let mut listener =
|
|
ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1);
|
|
listener.accept_direct = false;
|
|
listener.refuse_clients = args.public_without_clients;
|
|
listener.serve_app = false;
|
|
listener.accept_forward_for =
|
|
AcceptForwardForV0::PublicDyn((public_port, 60, "".to_string()));
|
|
listeners.push(listener);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//// --domain-private
|
|
|
|
if args.domain_private.is_some() {
|
|
let domain_string = args.domain_private.as_ref().unwrap();
|
|
let parts: Vec<&str> = domain_string.split(',').collect();
|
|
|
|
let (_, domain, _) =
|
|
parse_domain_and_port(&parts[0].to_string(), "--domain-private", DEFAULT_TLS_PORT)?;
|
|
|
|
let bind_string = if parts.len() > 1 { parts[1] } else { "default" };
|
|
|
|
let arg_value = parse_interface_and_port_for(
|
|
&bind_string.to_string(),
|
|
"--domain-private",
|
|
WS_PORT_REVERSE_PROXY,
|
|
)?;
|
|
|
|
let if_name = &arg_value.0;
|
|
match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) {
|
|
None => {
|
|
log_err!(
|
|
"{}",
|
|
if if_name == "default" {
|
|
"We could not find a private IP interface on your host for --domain-private option. cannot start"
|
|
.to_string()
|
|
} else {
|
|
format!(
|
|
"We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
|
|
if_name
|
|
)
|
|
}
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(inter) => {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
|
|
let res = prepare_accept_forward_for_domain(domain, &mut args).map_err(|_| {
|
|
log_err!("The --domain-peer option has an invalid key. it must be a base64_url encoded serialization of a PrivKey. cannot start")})?;
|
|
|
|
if listeners.last().is_some()
|
|
&& listeners.last().unwrap().interface_name == inter.name
|
|
&& listeners.last().unwrap().port == arg_value.1
|
|
{
|
|
let r = listeners.last_mut().unwrap();
|
|
if r.accept_forward_for != AcceptForwardForV0::No {
|
|
log_err!("The same private interface is already forwarding with a different setting, probably because of a --public or --dynamic option conflicting with the --domain-private option. Changing the port on one of the interfaces can help. cannot start");
|
|
return Err(());
|
|
}
|
|
panic!(
|
|
"this should never happen. --domain-private created after a --private"
|
|
);
|
|
//r.ipv6 = !args.no_ipv6;
|
|
//r.accept_forward_for = res;
|
|
} else {
|
|
let mut listener =
|
|
ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1);
|
|
listener.accept_direct = false;
|
|
listener.accept_forward_for = res;
|
|
|
|
listeners.push(listener);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//// --private
|
|
|
|
if args.private.is_some() {
|
|
let arg_value = parse_interface_and_port_for(
|
|
args.private.as_ref().unwrap(),
|
|
"--private",
|
|
DEFAULT_PORT,
|
|
)?;
|
|
|
|
let if_name = &arg_value.0;
|
|
match find_first_or_name(&interfaces, InterfaceType::Private, &if_name) {
|
|
None => {
|
|
log_err!(
|
|
"{}",
|
|
if if_name == "default" {
|
|
"We could not find a private IP interface on your host. cannot start"
|
|
.to_string()
|
|
} else {
|
|
format!(
|
|
"We could not find a private IP interface named {} on your host. use --list-interfaces to find the available interfaces on your host. cannot start",
|
|
if_name
|
|
)
|
|
}
|
|
);
|
|
return Err(());
|
|
}
|
|
Some(inter) => {
|
|
overlays_config.server = BrokerOverlayPermission::AllRegisteredUser;
|
|
|
|
if listeners.last().is_some()
|
|
&& listeners.last().unwrap().interface_name == inter.name
|
|
&& listeners.last().unwrap().port == arg_value.1
|
|
{
|
|
if args.domain_peer.is_some() {
|
|
log_err!(
|
|
"--private is not allowed if --domain-peer is selected, and they both use the same port. change the port of one of them. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
let r = listeners.last_mut().unwrap();
|
|
r.accept_direct = true;
|
|
r.serve_app = true;
|
|
r.ipv6 = !args.no_ipv6;
|
|
} else {
|
|
listeners.push(ListenerV0::new_direct(inter, !args.no_ipv6, arg_value.1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//// --forward
|
|
|
|
if args.forward.is_some() {
|
|
//"[DOMAIN/IP:PORT]@PEERID"
|
|
let forward_string = args.forward.as_ref().unwrap();
|
|
let parts: Vec<&str> = forward_string.split('@').collect();
|
|
|
|
if parts.len() != 2 {
|
|
log_err!(
|
|
"The option --forward is invalid. It must contain two parts separated by a @ character. cannot start"
|
|
);
|
|
return Err(());
|
|
}
|
|
let pub_key_array = decode_key(parts[1])
|
|
.map_err(|_| log_err!("The PEER_ID provided in the --forward option is invalid"))?;
|
|
let peer_id = PubKey::Ed25519PubKey(pub_key_array);
|
|
|
|
let server_type = if parts[0].len() > 0 {
|
|
let first_char = parts[0].chars().next().unwrap();
|
|
|
|
if first_char == '[' || first_char.is_numeric() {
|
|
// an IPv6 or IPv4
|
|
let bind = parse_ip_and_port_for(parts[0].to_string(), "--forward")?;
|
|
let bind_addr = BindAddress {
|
|
ip: (&bind.0).into(),
|
|
port: bind.1,
|
|
};
|
|
if is_private_ip(&bind.0) {
|
|
BrokerServerTypeV0::BoxPrivate(vec![bind_addr])
|
|
} else if is_public_ip(&bind.0) {
|
|
BrokerServerTypeV0::Public(vec![bind_addr])
|
|
} else {
|
|
log_err!("Invalid IP address given for --forward option. cannot start");
|
|
return Err(());
|
|
}
|
|
} else {
|
|
// a domain name
|
|
let (_, domain, _) = parse_domain_and_port(
|
|
&parts[0].to_string(),
|
|
"--forward",
|
|
DEFAULT_TLS_PORT,
|
|
)?;
|
|
BrokerServerTypeV0::Domain(domain)
|
|
}
|
|
} else {
|
|
BrokerServerTypeV0::BoxPublicDyn(vec![])
|
|
};
|
|
overlays_config.forward = vec![BrokerServerV0 {
|
|
server_type,
|
|
can_verify: false,
|
|
peer_id,
|
|
}];
|
|
}
|
|
|
|
let registration = if args.registration_off {
|
|
RegistrationConfig::Closed
|
|
} else if args.registration_open {
|
|
RegistrationConfig::Open
|
|
} else {
|
|
RegistrationConfig::Invitation
|
|
};
|
|
|
|
let admin_user = if args.admin.is_some() {
|
|
args.admin
|
|
.unwrap()
|
|
.as_str()
|
|
.try_into()
|
|
.map_err(|e| {
|
|
log_warn!("The admin UserId supplied is invalid. no admin user configured.");
|
|
})
|
|
.ok()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
config = Some(DaemonConfig::V0(DaemonConfigV0 {
|
|
listeners,
|
|
overlays_configs: vec![overlays_config],
|
|
registration,
|
|
admin_user,
|
|
registration_url: args.registration_url,
|
|
}));
|
|
|
|
if args.print_config {
|
|
let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap();
|
|
println!("The Quick config would be:\n{}", json_string);
|
|
return Ok(());
|
|
}
|
|
|
|
if args.save_config {
|
|
// saves the config to file
|
|
let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap();
|
|
write(config_path.clone(), json_string).map_err(|e| {
|
|
log_err!(
|
|
"cannot save config to file. {}. cannot start",
|
|
e.to_string()
|
|
)
|
|
})?;
|
|
log_info!(
|
|
"The config file has been saved to {}",
|
|
config_path.to_str().unwrap()
|
|
);
|
|
log_info!(
|
|
"You will not be able to use any Quick config options anymore on the command line at the next command-line start of the server. But you can go to modify the config file directly, or delete it.",
|
|
);
|
|
}
|
|
} else {
|
|
if config.is_none() {
|
|
log_err!(
|
|
"No Quick config option passed, neither is a config file present. We cannot start the server. Choose at least one Quick config option. see --help for details"
|
|
);
|
|
return Err(());
|
|
}
|
|
if args.print_config {
|
|
let json_string = to_string_pretty(config.as_ref().unwrap()).unwrap();
|
|
println!("The saved config is:\n{}", json_string);
|
|
return Ok(());
|
|
}
|
|
}
|
|
|
|
let (privkey, pubkey) = ed_keypair_from_priv_bytes(keys[1]);
|
|
keys[1].zeroize();
|
|
keys[0].zeroize();
|
|
|
|
log_info!("PeerId of node: {}", pubkey);
|
|
|
|
//debug_println!("Private key of peer: {}", privkey.to_string());
|
|
|
|
//let x_from_ed = pubkey.to_dh_from_ed();
|
|
//log_info!("du Pubkey from ed: {}", x_from_ed);
|
|
|
|
match config.unwrap() {
|
|
DaemonConfig::V0(v0) => {
|
|
run_server_v0(
|
|
privkey,
|
|
pubkey,
|
|
SymKey::from_array(keys[2]),
|
|
v0,
|
|
path,
|
|
args.invite_admin,
|
|
)
|
|
.await?
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|