aboutsummaryrefslogtreecommitdiffstats
path: root/src/server.rs
blob: dee6e623387d46201f8674038ff74c0e2d56ced6 (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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186

extern crate daemonize;

use super::common;

use std::str::{self, FromStr};
use std::env;
use std::net;
use std::io;
use std::error::Error;
use std::env::home_dir;
use std::process::exit;
use getopts::Options;
use udt::{self, UdtSocket, UdtStatus};
use crypto::{SecretStream, key2string, string2key, nonce2string, string2nonce};
use udt_extras::{UdtStream};
use sodiumoxide::crypto::secretbox;

pub fn get_local_ip() -> Result<net::IpAddr, String> {
    let ip_str = match env::var("SSH_CONNECTION") {
        Ok(val) => {
            match val.split(' ').nth(2) {
                Some(x) => x.to_string(),
                None => { return Err(format!("Failed to parse $SSH_CONNECTION: {}", val.to_string())); },
            }
        },
        Err(_) => {
            println!("Can't find $SSH_CONNECTION; running locally? Falling back to 127.0.0.1");
            "127.0.0.1".to_string()
        },
    };

    // First try IPv4
    match net::Ipv4Addr::from_str(&ip_str) {
        Ok(x) => { return Ok(net::IpAddr::V4(x)) },
        Err(_) => (),
    };
    // Then IPv6
    match net::Ipv6Addr::from_str(&ip_str) {
        Ok(x) => { return Ok(net::IpAddr::V6(x)) },
        Err(e) => { return Err(e.description().to_string()); },
    };
}


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

    // Connect to an hypothetical local server running on port 61000
    let listen_addr = get_local_ip().unwrap();

    // Actually accept connection from anybody
    let all_addr = net::IpAddr::V4(net::Ipv4Addr::from_str("0.0.0.0").unwrap());

    let listener = UdtSocket::new(udt::SocketFamily::AFInet, udt::SocketType::Stream).unwrap();

    for port in 61000..62000 {
        match listener.bind(net::SocketAddr::new(all_addr, port)) {
            Ok(_) => { break; },
            Err(e) => (),
        }
    }

    if listener.getstate() != UdtStatus::OPENED {
        println!("{:?}", listener.getstate());
        println!("Couldn't bind to *any* valid port");
    }

    listener.listen(1).unwrap();

    let listen_port = listener.getsockname().unwrap().port();

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

    /* XXX: DEBUG:
    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.getpeername().unwrap());
    let mut stream: UdtStream = UdtStream::new(socket);

    if !no_crypto {
        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);
        }
    } else {
        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");
    opts.optflag("", "no-crypto", "sends data in the clear (no crypto or verification)");

    assert!(args.len() >= 2 && args[1] == "server");
    let matches = match opts.parse(&args[2..]) {
        Ok(m) => { m }
        Err(f) => { println!("{}", f.to_string()); 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");
    let no_crypto: bool = matches.opt_present("no-crypto");

    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, no_crypto);
    }
    if matches.opt_present("t") {
        run_server(&matches.opt_str("t").unwrap(), true, dir_mode, daemonize, no_crypto);
    }
}