Compare commits

..

No commits in common. "c1091b56b91976323fedba1556076025ba7b7417" and "9d232bed8189cfcad5daef9c0ead67bd4855d6ec" have entirely different histories.

10 changed files with 337 additions and 435 deletions

View File

@ -25,16 +25,13 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
- target: aarch64-apple-darwin - target: aarch64-apple-darwin
os: macos-latest os: macos-latest
- target: x86_64-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: taiki-e/upload-rust-binary-action@v1 - uses: taiki-e/upload-rust-binary-action@v1
with: with:
bin: contego bin: leightbox
tar: unix tar: unix
include: LICENSE include: LICENSE
checksum: sha256 checksum: sha256
target: ${{ matrix.target }}
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}

633
Cargo.lock generated Normal file → Executable file

File diff suppressed because it is too large Load Diff

11
Cargo.toml Normal file → Executable file
View File

@ -1,17 +1,16 @@
[package] [package]
name = "contego" name = "contego"
version = "0.4.0" description = "CLI tool for file transfer"
authors = ["17ms"] version = "0.3.0"
description = "A small CLI tool for quick file transfers"
repository = "https://github.com/17ms/contego"
license = "MIT"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tokio = { version = "1.28.1", features = ["full"] } tokio = { version = "1.28.1", features = ["full"] }
rand = "0.7.0" rand = "0.7.0"
x25519-dalek = "1.2.0" x25519-dalek = "1.2.0"
aes-gcm = "0.10.3" aes-gcm = "0.10.2"
base64 = "0.21.2" base64 = "0.21.2"
sha256 = "1.1.3" sha256 = "1.1.3"
ureq = "2.6.2" ureq = "2.6.2"

View File

@ -15,35 +15,15 @@
The initial key exchange is performed with elliptic-curve Diffie-Hellman. General data exchange is encrypted with AES-GCM. During regular communication payloads are Base64 encoded before being encrypted to prevent delimiter conflicts. SHA-256 hashes of files are compared to ensure data integrity. The initial key exchange is performed with elliptic-curve Diffie-Hellman. General data exchange is encrypted with AES-GCM. During regular communication payloads are Base64 encoded before being encrypted to prevent delimiter conflicts. SHA-256 hashes of files are compared to ensure data integrity.
## Cellular networks
Most cellular ISP's tend to block port forwarding on CGNAT level, which makes it impossible to create inbound connections to such network without a VPN. Luckily many consumer VPNs and self-hosted solutions make port forwarding a trivial task. This is the main reason why the client must fetch information about the public IP from an external service (https://ipinfo.io/ip for IPv4 and https://ipv6.icanhazip.com for IPv6).
## Usage ## Usage
Check [releases](https://github.com/17ms/contego/releases) for an up-to-date executables or build from source with `cargo build --release`. Work in progress. Will be completed when the current release is finished.
### Server
``` ```shell
Usage: contego host [OPTIONS] --key <KEY> <--source <SOURCE>|--files <FILES>...> cargo build --release
./target/release/contego
Options:
-k, --key <KEY> Access key
-s, --source <SOURCE> Path to a source file (alternative to --files)
-f, --files <FILES>... Paths to shareable files (alternative to --source)
-p, --port <PORT> Host port [default: 8080]
-6, --ipv6 IPv6 instead of IPv4
-c, --chunksize <CHUNKSIZE> Transmit chunksize in bytes [default: 8192]
-l, --local Host locally
-h, --help Print help
```
### Client
```
Usage: contego connect --addr <ADDR> --out <OUT> --key <KEY>
Options:
-a, --addr <ADDR> IP address of the instance
-o, --out <OUT> Path to an output folder
-k, --key <KEY> Access key
-h, --help Print help
``` ```

BIN
docs/contego-dark.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
docs/contego-light.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -6,11 +6,11 @@ use contego::{
client::Client, client::Client,
parser::{addr_parser, dirpath_parser, filepath_parser}, parser::{addr_parser, dirpath_parser, filepath_parser},
server::Server, server::Server,
util::{ascii, filepaths, metadata, Ip}, util::{filepaths, handle_exit, metadata, Ip},
}; };
use env_logger::Env; use env_logger::Env;
use log::{error, info}; use log::error;
use tokio::{signal, sync::mpsc}; use tokio::sync::mpsc;
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
#[command(about, version)] #[command(about, version)]
@ -60,8 +60,6 @@ enum Commands {
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
ascii();
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
let cli = Cli::parse(); let cli = Cli::parse();
@ -79,28 +77,22 @@ async fn main() -> Result<(), Box<dyn Error>> {
let paths = filepaths(source, files)?; let paths = filepaths(source, files)?;
let (metadata, index) = metadata(&paths).await?; let (metadata, index) = metadata(&paths).await?;
let (display_addr, bind_addr) = match (local, ipv6) { let addr = match (local, ipv6) {
(true, _) => Ip::Local.fetch(port)?, (true, _) => Ip::Local.fetch(port)?,
(false, true) => Ip::V6.fetch(port)?, (false, true) => Ip::V6.fetch(port)?,
(false, false) => Ip::V4.fetch(port)?, (false, false) => Ip::V4.fetch(port)?,
}; };
let server = Server::new(display_addr, key, chunksize, metadata, index); let server = Server::new(addr, key, chunksize, metadata, index);
tokio::spawn(async move { tokio::spawn(async move {
match server.start(rx, &bind_addr).await { match server.start(rx).await {
Ok(_) => {} Ok(_) => {}
Err(e) => error!("Error during server execution: {}", e), Err(e) => error!("Error during server execution: {}", e),
}; };
}); });
match signal::ctrl_c().await { handle_exit(tx).await?;
Ok(_) => {
tx.send(()).await?;
info!("Captured Ctrl+C, shutting down");
}
Err(_) => error!("Failed to listen for a Ctrl+C event"),
};
} }
Commands::Connect { addr, out, key } => { Commands::Connect { addr, out, key } => {
let client = Client::new(addr, key, out); let client = Client::new(addr, key, out);

View File

@ -39,19 +39,15 @@ impl Server {
pub async fn start( pub async fn start(
self: Arc<Self>, self: Arc<Self>,
mut kill: mpsc::Receiver<()>, mut kill: mpsc::Receiver<()>,
bind_addr: &SocketAddr,
) -> Result<(), Box<dyn Error + Send + Sync>> { ) -> Result<(), Box<dyn Error + Send + Sync>> {
tokio::select! { tokio::select! {
_ = self.listen(bind_addr) => Ok(()), _ = self.listen() => Ok(()),
_ = kill.recv() => Ok(()), _ = kill.recv() => Ok(()),
} }
} }
async fn listen( async fn listen(self: Arc<Self>) -> Result<(), Box<dyn Error + Send + Sync>> {
self: Arc<Self>, let listener = TcpListener::bind(self.addr).await?;
bind_addr: &SocketAddr,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let listener = TcpListener::bind(bind_addr).await?;
info!("Listening on {} - Access key: {}", self.addr, self.key); info!("Listening on {} - Access key: {}", self.addr, self.key);

View File

@ -1,7 +1,15 @@
use std::{collections::HashMap, env, error::Error, fs, net::SocketAddr, path::PathBuf}; use std::{
collections::HashMap,
env,
error::Error,
fs,
io::{stdin, Read},
net::SocketAddr,
path::PathBuf,
};
use log::{debug, info}; use log::{debug, info};
use tokio::{fs::File, io::BufWriter}; use tokio::{fs::File, io::BufWriter, sync::mpsc::Sender};
use crate::crypto; use crate::crypto;
@ -16,26 +24,24 @@ pub enum Ip {
} }
impl Ip { impl Ip {
pub fn fetch(self, port: u16) -> Result<(SocketAddr, SocketAddr), Box<dyn Error>> { pub fn fetch(self, port: u16) -> Result<SocketAddr, Box<dyn Error>> {
let addr = match self { let addr = match self {
Ip::V4 => PUBLIC_IPV4, Ip::V4 => PUBLIC_IPV4,
Ip::V6 => PUBLIC_IPV6, Ip::V6 => PUBLIC_IPV6,
Ip::Local => { Ip::Local => {
let addr_str = format!("127.0.0.1:{}", port); let addr_str = format!("127.0.0.1:{}", port);
let addr = addr_str.parse::<SocketAddr>()?; return Ok(addr_str.parse::<SocketAddr>()?);
return Ok((addr, addr));
} }
}; };
info!("Fetching IP information from {}", addr); info!("Fetching IP information from {}", addr);
let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port); let res = format!("{}:{}", ureq::get(addr).call()?.into_string()?.trim(), port);
let display_addr = res.parse::<SocketAddr>()?; let addr = res.parse::<SocketAddr>()?;
let bind_addr = format!("0.0.0.0:{}", port).parse::<SocketAddr>()?;
debug!("IP: {}", res); debug!("IP: {}", res);
Ok((display_addr, bind_addr)) Ok(addr)
} }
} }
@ -121,12 +127,20 @@ pub async fn new_file(
Ok((BufWriter::new(handle), path)) Ok((BufWriter::new(handle), path))
} }
pub fn ascii() { pub async fn handle_exit(tx: Sender<()>) -> Result<(), Box<dyn Error>> {
let ascii = " __ let mut stdin = stdin().lock().bytes();
_________ ____ / /____ ____ _____
/ ___/ __ \\/ __ \\/ __/ _ \\/ __ `/ __ \\ loop {
/ /__/ /_/ / / / / /_/ __/ /_/ / /_/ / let k = match stdin.next() {
\\___/\\____/_/ /_/\\__/\\___/\\__, /\\____/ None => continue,
/____/ "; Some(k) => k?,
println!("{}\n", ascii); };
if k == b'q' {
tx.send(()).await?;
break;
}
}
Ok(())
} }

View File

@ -24,13 +24,14 @@ async fn sockets_integration() {
.is_test(true) .is_test(true)
.try_init() .try_init()
.unwrap(); .unwrap();
//env_logger::builder().is_test(true).try_init().unwrap();
debug!("Initializing and starting the test"); debug!("Initializing and starting the test");
let (testdata, paths) = testdata(); let (testdata, paths) = testdata();
let (metadata, index) = metadata(&paths).await.unwrap(); let (metadata, index) = metadata(&paths).await.unwrap();
let (display_addr, bind_addr) = Ip::Local.fetch(8080).unwrap(); let addr = Ip::Local.fetch(8080).unwrap();
let outdir = PathBuf::from("./tests/output/"); let outdir = PathBuf::from("./tests/output/");
let key = String::from("testkey"); let key = String::from("testkey");
let c_key = key.clone(); let c_key = key.clone();
@ -39,14 +40,14 @@ async fn sockets_integration() {
let server_handle = tokio::spawn(async move { let server_handle = tokio::spawn(async move {
debug!("Initializing the asynchronous server task"); debug!("Initializing the asynchronous server task");
let server = Server::new(display_addr, key, 8192, metadata, index); let server = Server::new(addr, key, 8192, metadata, index);
debug!("Starting to listen to incoming connections"); debug!("Starting to listen to incoming connections");
server.start(rx, &bind_addr).await.unwrap(); server.start(rx).await.unwrap();
}); });
let client_handle = tokio::spawn(async move { let client_handle = tokio::spawn(async move {
debug!("Initializing the asynchronous client task"); debug!("Initializing the asynchronous client task");
let client = Client::new(display_addr, c_key, outdir); let client = Client::new(addr, c_key, outdir);
debug!("Connecting to the server"); debug!("Connecting to the server");
client.connection().await.unwrap(); client.connection().await.unwrap();
}); });