aboutsummaryrefslogtreecommitdiffstats
path: root/src/server.rs
blob: 508bb36700a6062918903e9fe351d0725bc593da (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133

extern crate daemonize;

use super::common;

use std::str;
use std::env;
use std::env::home_dir;
use std::process::exit;
use getopts::Options;
use utp::{UtpSocket, UtpStream, UtpListener};
use crypto::{SecretStream, key2string, string2key, nonce2string, string2nonce};
use sodiumoxide::crypto::secretbox;

fn run_server(path: &str, is_recv: bool, recursive: bool, daemonize: bool) {

    // TODO: try to detect the address the SSH connection came in on via the SSH_CONNECTION env
    // variable.

    // Connect to an hypothetical local server running on port 61000
    let addr = "127.0.0.1:61000";

    // Accept connection from anybody
    let listener = UtpListener::bind(addr).expect("Error binding to local port");

    let listen_port = listener.local_addr().unwrap().port();
    let listen_addr = listener.local_addr().unwrap().ip();

    let secret_key = secretbox::gen_key();
    let read_nonce = secretbox::gen_nonce();
    let write_nonce = secretbox::gen_nonce();

    // XXX:
    assert!(secret_key == string2key(&key2string(&secret_key)).unwrap());
    assert!(read_nonce == string2nonce(&nonce2string(&read_nonce)).unwrap());
    let read_nonce = secretbox::Nonce::from_slice(&[0; secretbox::NONCEBYTES]).unwrap();
    let write_nonce = secretbox::Nonce::from_slice(&[0; secretbox::NONCEBYTES]).unwrap();

    // Send back details so client can connect
    println!("UCP CONNECT {} {} {} {} {}",
        listen_addr,
        listen_port,
        key2string(&secret_key),
        nonce2string(&read_nonce),
        nonce2string(&write_nonce));


    // TODO: maybe wait for an ACK of some sort here before daemonizing?

    if daemonize {
        // At this point we partially daemonize (fork and redirect terminal), so that SSH will drop us.
        // But, don't clobber working dir.
        let working_dir = match env::home_dir() {
            Some(path) => path,
            None => env::current_dir().unwrap(),
        };
        // XXX: should maybe use log/syslog from here on?
        let daemonizer = daemonize::Daemonize::new().working_directory(working_dir);

        match daemonizer.start() {
            Ok(_) => println!("Success, daemonized"),
            Err(e) => println!("{}", e),
        }
    } else {
        println!("Not daemonizing (DEBUG MODE)");
    }

    let (mut socket, _src) = listener.accept().unwrap();
    println!("Got connection from {}", socket.peer_addr().unwrap());
    let mut stream: UtpStream = socket.into();
    let mut stream = SecretStream::new(stream);
    stream.key = secret_key;
    stream.read_nonce = read_nonce;
    stream.write_nonce = write_nonce;

    if is_recv {
        common::sink_files(&mut stream, path, recursive);
    } else {
        common::source_files(&mut stream, path, recursive);
    }
    // XXX: does Drop do this well enough?
    //stream.close().unwrap();
}

fn usage_server(opts: Options) {
    let brief = "usage:\tucp server ..."; // XXX:
    println!("");
    println!("IMPORTANT: this is the server mode of ucp. Unless you are developing/debugging, you probably want the 'regular' one (from the 'server' from you command)");
    print!("{}", opts.usage(&brief));
}

pub fn main_server() {

    let args: Vec<String> = env::args().collect();

    let mut opts = Options::new();
    opts.optflag("h", "help", "print this help menu");
    //opts.optflag("v", "verbose", "more debugging messages");
    opts.optflag("d", "dir-mode", "read/write a dir instead of file (server side)");
    opts.optflag("", "no-daemonize", "don't daemonize (for debuggign)");
    opts.optopt("f", "from", "file or dir to read from (server side)", "FILE");
    opts.optopt("t", "to", "file or dir to write to (server side)", "FILE");

    assert!(args.len() >= 2 && args[1] == "server");
    let matches = match opts.parse(&args[2..]) {
        Ok(m) => { m }
        Err(f) => { println!("Error parsing args!"); usage_server(opts); exit(-1); }
    };

    if matches.opt_present("h") {
        usage_server(opts);
        return;
    }

    //let verbose: bool = matches.opt_present("v");
    let dir_mode: bool = matches.opt_present("d");
    let daemonize: bool = !matches.opt_present("no-daemonize");

    match (matches.opt_present("f"), matches.opt_present("t")) {
        (true, true) | (false, false) => {
            println!("Must be either 'from' or 'to', but not both");
            exit(-1);
            },
        _ => {},
    }

    if matches.opt_present("f") {
        run_server(&matches.opt_str("f").unwrap(), false, dir_mode, daemonize);
    }
    if matches.opt_present("t") {
        run_server(&matches.opt_str("t").unwrap(), true, dir_mode, daemonize);
    }
}