diff options
| author | stuebinm | 2021-04-27 17:40:40 +0200 | 
|---|---|---|
| committer | stuebinm | 2021-04-27 17:40:40 +0200 | 
| commit | e0c6f2cd7a2ef02fb9b7947816b54f3a43ef522d (patch) | |
| tree | 8e5f1829d40e159086ac9aa4c2f248e8d633e110 | |
| parent | 445c3f73d77fe957c1663666df4fde852fd4de14 (diff) | |
server: add config options, better outfile namesrustserver
e.g.
 - ability to log ip addresses of respondents
 - configurable bind address
 - reasonably sane defaults (hopefully)
 - survey name as part of output file names
| -rw-r--r-- | server/Cargo.lock | 90 | ||||
| -rw-r--r-- | server/Cargo.toml | 1 | ||||
| -rw-r--r-- | server/src/main.rs | 121 | ||||
| -rw-r--r-- | todo.org | 5 | 
4 files changed, 188 insertions, 29 deletions
diff --git a/server/Cargo.lock b/server/Cargo.lock index 5800be3..a7a7799 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -34,6 +34,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"  [[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + +[[package]]  name = "cpuid-bool"  version = "0.1.2"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -182,6 +193,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"  [[package]] +name = "heck" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +dependencies = [ + "unicode-segmentation", +] + +[[package]]  name = "hermit-abi"  version = "0.1.18"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -426,6 +446,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"  [[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]]  name = "proc-macro-hack"  version = "0.5.19"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -478,6 +522,7 @@ dependencies = [   "hex",   "hyper",   "sha2", + "structopt",   "tokio",  ] @@ -526,6 +571,30 @@ dependencies = [  ]  [[package]] +name = "structopt" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]]  name = "syn"  version = "1.0.70"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -537,6 +606,15 @@ dependencies = [  ]  [[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]]  name = "tokio"  version = "1.5.0"  source = "registry+https://github.com/rust-lang/crates.io-index" @@ -620,6 +698,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"  checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"  [[package]] +name = "unicode-segmentation" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]]  name = "unicode-xid"  version = "0.2.1"  source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/server/Cargo.toml b/server/Cargo.toml index 82b4f20..8fbbea2 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -12,3 +12,4 @@ tokio = { version = "1", features = ["full"] }  futures = "0.3"  sha2 = "0.9.3"  hex = "0.4.3" +structopt = { version = "0.3", default-features = false } diff --git a/server/src/main.rs b/server/src/main.rs index ec2c77c..b718d57 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,36 +1,92 @@  use hyper::service::{make_service_fn, service_fn};  use hyper::{Body, Method, Request, Response, Server, StatusCode}; +use hyper::server::conn::AddrStream;  use sha2::{Sha256, Digest};  use std::io::Write; +use std::convert::Infallible; +use std::net::SocketAddr; +use std::path::PathBuf; +use structopt::StructOpt; + + + +#[derive(Debug, StructOpt, Clone)] +#[structopt(about = "simple server that eats slightly better surveys.")] +struct Settings { +    #[structopt(short, long, help="log ip addresses of answers as part of their filename")] +    log_ips: bool, +    #[structopt(default_value="127.0.0.1:8000", help="address which the server should listen on")] +    bind_address: SocketAddr, +    #[structopt(parse(from_os_str), default_value=".", help="where to save incoming survey answers")] +    out_path: PathBuf +} + + +async fn handle_survey(req: Request<Body>, remote_addr: &SocketAddr, settings: &Settings) -> Option<Response<Body>> { +    let (parts, rawbody) = req.into_parts(); +    let body = hyper::body::to_bytes(rawbody).await.ok()?; +    let survey = parts.headers.get("survey")?; + +    let hash = { +        // we can use a hash of the content as filename; +        // age ensures we won't have collisions even if +        // two people handed in the same thing twice. +        let mut hasher = Sha256::new(); +        hasher.update(body.clone()); +        hasher.finalize() +    }; + +    let mut filepath = settings.out_path.clone(); +    filepath.set_file_name(match settings.log_ips { +        true => { +            let filename = format!( +                "answers-{}-{}-{:.20}.age", +                survey.to_str().ok()?, +                remote_addr.ip(), +                hex::encode(hash) +            ); +            println!( +                "received survey response for survey {:?} from {}, saving to {}", +                survey, +                remote_addr.ip(), +                filename +            ); +            filename +        }, +        false => { +            let filename = format!( +                "answers-{}-{:.20}.age", +                survey.to_str().ok()?, +                hex::encode(hash) +            ); +            println!( +                "received survey response for survey {:?}, saving to {}", +                survey, +                filename +            ); +            filename +        } +    }); + +    let mut file = std::fs::File::create(filepath).ok()?; +    file.write_all(&body).unwrap(); + +    Some(Response::new( +        "thanks for handing in these answers!\n".into() +    )) +}  /// This is our service handler. It receives a Request, routes on its  /// path, and returns a Future of a Response. -async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { +async fn server(req: Request<Body>, remote_addr: &SocketAddr, settings: &Settings) -> Result<Response<Body>, Infallible> {      match (req.method(), req.uri().path()) { -        // Serve some instructions at / -        (&Method::GET, "/") => Ok(Response::new(Body::from( -            "Try POSTing data to /echo such as: `curl localhost:8000/echo -XPOST -d 'hello world'`", -        ))), - -        // Simply echo the body back to the client. -        (&Method::POST, "/echo") => Ok(Response::new(req.into_body())),          (&Method::POST, "/survey") => { -            let full_body = hyper::body::to_bytes(req.into_body()).await?; -            let mut hasher = Sha256::new(); -            hasher.update(full_body.clone()); -            let hash = hasher.finalize(); - -            let mut file = std::fs::File::create( -                format!("answers-{}.age", hex::encode(hash)) -            ).unwrap(); - -            file.write_all(&full_body).unwrap(); -            println!("got hash: {:?}", hash); -            Ok(Response::new( -                "thanks for hading in these answers!".into() -            )) +            match handle_survey(req, remote_addr, settings).await { +                Some(response) => Ok(response), +                None => Ok(Response::new("Something went wrong\n".into())) +            }          }          // Return the 404 Not Found for other routes. @@ -46,17 +102,28 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {  #[tokio::main]  async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { -    let addr = ([127, 0, 0, 1], 8000).into(); +    // this is a memory leak of constant size, to avoid the borrow checker +    let opt: &'static Settings = Box::leak( +        Box::new(Settings::from_args()) +    ); + +    // I have no idea what's wrong here ... +    // look at https://docs.rs/hyper/0.14.7/hyper/service/fn.make_service_fn.html      let service = make_service_fn( -        |_| async { -            Ok::<_, hyper::Error>(service_fn(echo)) +        |socket: &AddrStream|  { +            let remote_addr = socket.remote_addr(); +            async move { +                Ok::<_, Infallible>(service_fn(move |req: Request<Body>| async move { +                    server(req, &remote_addr, &opt).await +                })) +            }          }      ); -    let server = Server::bind(&addr).serve(service); +    let server = Server::bind(&opt.bind_address).serve(service); -    println!("Listening on http://{}", addr); +    println!("Listening on http://{}", opt.bind_address);      server.await?; @@ -13,12 +13,13 @@  * TODO Server  ** DONE possibly switch from guile to rust or something  ** TODO implement missing features: -*** TODO truncate hashes +*** DONE truncate hashes  *** TODO accept unencrypted answers and store them in a single file?  *** TODO validate answers?  This seems difficult, but I guess it could at least check whether  the age-encryption header is there. -*** TODO config options: listen address, output path +*** DONE config options: listen address, output path +*** TODO limit answers from one ip address  ** TODO short description of what it does  ** TODO nix module  | 
