From 1b9ec2d9c346589445ed1c720cca23a9712e6761 Mon Sep 17 00:00:00 2001 From: Adam Ierymenko Date: Fri, 19 Mar 2021 17:21:16 -0400 Subject: [PATCH] Web request helper and other stuff. --- osdep/rust-osdep.cpp | 6 - osdep/rust-osdep.h | 17 +- service/Cargo.lock | 248 +++++++++-------------------- service/Cargo.toml | 2 +- service/src/commands/controller.rs | 13 ++ service/src/commands/join.rs | 13 ++ service/src/commands/leave.rs | 13 ++ service/src/commands/network.rs | 13 ++ service/src/commands/peer.rs | 13 ++ service/src/commands/set.rs | 13 ++ service/src/commands/status.rs | 21 +-- service/src/main.rs | 6 +- service/src/utils.rs | 20 --- service/src/webclient.rs | 98 ++++++++++-- 14 files changed, 263 insertions(+), 233 deletions(-) diff --git a/osdep/rust-osdep.cpp b/osdep/rust-osdep.cpp index 1ae19b3ce..d4ac1c4fc 100644 --- a/osdep/rust-osdep.cpp +++ b/osdep/rust-osdep.cpp @@ -134,12 +134,6 @@ void lockDownFile(const char *path, int isDir) void getSecureRandom(void *buf, unsigned int len) { ZeroTier::Utils::getSecureRandom(buf, len); } -void sha384(const void *in, unsigned int len, void *out) -{ ZeroTier::SHA384(out, in, len); } - -void sha512(const void *in, unsigned int len, void *out) -{ ZeroTier::SHA512(out, in, len); } - static ZT_INLINE ZeroTier::AES _makeHttpAuthCipher() noexcept { uint8_t key[32]; diff --git a/osdep/rust-osdep.h b/osdep/rust-osdep.h index bec9927ee..d37565aca 100644 --- a/osdep/rust-osdep.h +++ b/osdep/rust-osdep.h @@ -102,15 +102,28 @@ extern const unsigned long c_SIOCAUTOCONF_STOP; extern "C" { #endif +// Get the default home path for this platform. extern const char *platformDefaultHomePath(); + +// This ms-since-epoch function may be faster than the one in Rust's stdlib. extern int64_t msSinceEpoch(); + +// Rust glue to C code to lock down a file, which is simple on Unix-like OSes +// and horrible on Windows. extern void lockDownFile(const char *path, int isDir); + +// Rust glue to ZeroTier's secure random PRNG. extern void getSecureRandom(void *buf, unsigned int len); -extern void sha384(const void *in, unsigned int len, void *out); -extern void sha512(const void *in, unsigned int len, void *out); + +// These AES encrypt and decrypt a single block using a key that is randomly +// generated at process init and never exported. It's used to generate HTTP +// digest authentication tokens that can just be decrypted to get and check +// a timestamp to prevent replay attacks. extern void encryptHttpAuthNonce(void *block); extern void decryptHttpAuthNonce(void *block); #ifdef __cplusplus } #endif + +/********************************************************************************************************************/ diff --git a/service/Cargo.lock b/service/Cargo.lock index bb3556223..f25938632 100644 --- a/service/Cargo.lock +++ b/service/Cargo.lock @@ -26,15 +26,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.13.0" @@ -47,7 +38,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e964e3e0a930303c7c0bdb28ebf691dd98d9eee4b8b68019d2c995710b58a18" dependencies = [ - "base64 0.13.0", + "base64", "serde", ] @@ -58,19 +49,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] -name = "byteorder" -version = "1.4.3" +name = "block-buffer" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "byteorder", - "iovec", + "generic-array", ] [[package]] @@ -136,6 +120,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "cpuid-bool" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634" + [[package]] name = "dialoguer" version = "0.7.1" @@ -148,6 +138,28 @@ dependencies = [ "zeroize", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest_auth" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fd5e24649b07f360f59a1e0a522d775540e2bc4b88f8d2657bcf8ca0360d74" +dependencies = [ + "digest", + "hex", + "md-5", + "rand", + "sha2", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -255,6 +267,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.2" @@ -281,24 +303,13 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes 0.4.12", - "fnv", - "itoa", -] - [[package]] name = "http" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes 1.0.1", + "bytes", "fnv", "itoa", ] @@ -309,8 +320,8 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ - "bytes 1.0.1", - "http 0.2.3", + "bytes", + "http", ] [[package]] @@ -331,11 +342,11 @@ version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" dependencies = [ - "bytes 1.0.1", + "bytes", "futures-channel", "futures-core", "futures-util", - "http 0.2.3", + "http", "http-body", "httparse", "httpdate", @@ -348,56 +359,12 @@ dependencies = [ "want", ] -[[package]] -name = "hyperx" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a94cbc2c6f63028e5736ca4e811ae36d3990059c384cbe68298c66728a9776" -dependencies = [ - "base64 0.10.1", - "bytes 0.4.12", - "http 0.1.21", - "httparse", - "language-tags", - "log", - "mime", - "percent-encoding", - "time", - "unicase 2.6.0", -] - -[[package]] -name = "idna" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" -[[package]] -name = "language-tags" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" - [[package]] name = "lazy_static" version = "1.4.0" @@ -420,10 +387,15 @@ dependencies = [ ] [[package]] -name = "matches" -version = "0.1.8" +name = "md-5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] [[package]] name = "memchr" @@ -431,12 +403,6 @@ version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - [[package]] name = "mio" version = "0.7.7" @@ -516,10 +482,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] -name = "percent-encoding" -version = "1.0.1" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "pin-project" @@ -699,6 +665,19 @@ dependencies = [ "serde", ] +[[package]] +name = "sha2" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpuid-bool", + "digest", + "opaque-debug", +] + [[package]] name = "signal-hook-registry" version = "1.3.0" @@ -796,21 +775,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "tinyvec" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - [[package]] name = "tokio" version = "1.0.2" @@ -871,40 +835,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] -name = "unicase" -version = "1.4.2" +name = "typenum" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" -dependencies = [ - "version_check 0.1.5", -] - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check 0.9.3", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" -dependencies = [ - "matches", -] - -[[package]] -name = "unicode-normalization" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" -dependencies = [ - "tinyvec", -] +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" [[package]] name = "unicode-width" @@ -918,29 +852,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" -[[package]] -name = "url" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" -dependencies = [ - "idna", - "matches", - "percent-encoding", -] - [[package]] name = "vec_map" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version_check" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" - [[package]] name = "version_check" version = "0.9.3" @@ -994,17 +911,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "www-authenticate" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c62efb8259cda4e4c732287397701237b78daa4c43edcf3e613c8503a6c07dd" -dependencies = [ - "hyperx", - "unicase 1.4.2", - "url", -] - [[package]] name = "zeroize" version = "0.9.3" @@ -1015,7 +921,7 @@ checksum = "45af6a010d13e4cf5b54c94ba5a2b2eba5596b9e46bf5875612d332a1f2b3f86" name = "zerotier-core" version = "0.1.0" dependencies = [ - "base64 0.13.0", + "base64", "base64-serde", "hex", "num-derive", @@ -1031,6 +937,7 @@ dependencies = [ "chrono", "clap", "dialoguer", + "digest_auth", "futures", "hex", "hyper", @@ -1043,6 +950,5 @@ dependencies = [ "socket2", "tokio", "winapi", - "www-authenticate", "zerotier-core", ] diff --git a/service/Cargo.toml b/service/Cargo.toml index c719c140d..aba2cabe2 100644 --- a/service/Cargo.toml +++ b/service/Cargo.toml @@ -23,7 +23,7 @@ num-derive = "0" hyper = { version = "0", features = ["http1", "runtime", "server", "client", "tcp", "stream"] } socket2 = { version = "0", features = ["reuseport", "unix", "pair"] } dialoguer = "0" -www-authenticate = "0" +digest_auth = "0" [target."cfg(windows)".dependencies] winapi = { version = "0.3.9", features = ["handleapi", "ws2ipdef", "ws2tcpip"] } diff --git a/service/src/commands/controller.rs b/service/src/commands/controller.rs index e69de29bb..489e2178a 100644 --- a/service/src/commands/controller.rs +++ b/service/src/commands/controller.rs @@ -0,0 +1,13 @@ +/* + * Copyright (c)2013-2021 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + diff --git a/service/src/commands/join.rs b/service/src/commands/join.rs index e69de29bb..489e2178a 100644 --- a/service/src/commands/join.rs +++ b/service/src/commands/join.rs @@ -0,0 +1,13 @@ +/* + * Copyright (c)2013-2021 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + diff --git a/service/src/commands/leave.rs b/service/src/commands/leave.rs index e69de29bb..489e2178a 100644 --- a/service/src/commands/leave.rs +++ b/service/src/commands/leave.rs @@ -0,0 +1,13 @@ +/* + * Copyright (c)2013-2021 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + diff --git a/service/src/commands/network.rs b/service/src/commands/network.rs index e69de29bb..489e2178a 100644 --- a/service/src/commands/network.rs +++ b/service/src/commands/network.rs @@ -0,0 +1,13 @@ +/* + * Copyright (c)2013-2021 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + diff --git a/service/src/commands/peer.rs b/service/src/commands/peer.rs index e69de29bb..489e2178a 100644 --- a/service/src/commands/peer.rs +++ b/service/src/commands/peer.rs @@ -0,0 +1,13 @@ +/* + * Copyright (c)2013-2021 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + diff --git a/service/src/commands/set.rs b/service/src/commands/set.rs index e69de29bb..489e2178a 100644 --- a/service/src/commands/set.rs +++ b/service/src/commands/set.rs @@ -0,0 +1,13 @@ +/* + * Copyright (c)2013-2021 ZeroTier, Inc. + * + * Use of this software is governed by the Business Source License included + * in the LICENSE.TXT file in the project's root directory. + * + * Change Date: 2026-01-01 + * + * On the date above, in accordance with the Business Source License, use + * of this software will be governed by version 2.0 of the Apache License. + */ +/****/ + diff --git a/service/src/commands/status.rs b/service/src/commands/status.rs index 068be7b83..793e1e6b4 100644 --- a/service/src/commands/status.rs +++ b/service/src/commands/status.rs @@ -11,22 +11,17 @@ */ /****/ +use std::rc::Rc; use std::str::FromStr; use std::sync::Arc; use hyper::Uri; -use crate::store::Store; -pub(crate) fn run(store: Arc) -> i32 { - crate::webclient::command(store.clone(), move |client, uri| { - async move { - let mut res = client.get(uri).await?; - println!("status: {}", res.status().as_str()); - let body = hyper::body::to_bytes(res.body_mut()).await?; - String::from_utf8(body.to_vec()).map(|body| { - println!("body: {}", body.as_str()); - }); - Ok(0) - } - }) +use crate::store::Store; +use crate::webclient::HttpClient; + +pub(crate) async fn run(store: Arc, client: HttpClient, api_base_uri: Uri, auth_token: String) -> hyper::Result { + let mut res = client.get(api_base_uri).await?; + let body = hyper::body::to_bytes(res.body_mut()).await?; + Ok(0) } diff --git a/service/src/main.rs b/service/src/main.rs index 71a17150c..78af07d09 100644 --- a/service/src/main.rs +++ b/service/src/main.rs @@ -28,9 +28,7 @@ mod weblistener; #[allow(non_snake_case, non_upper_case_globals, non_camel_case_types, dead_code, improper_ctypes)] mod osdep; // bindgen generated -use std::boxed::Box; use std::io::Write; -use std::rc::Rc; use std::sync::Arc; use std::str::FromStr; @@ -315,7 +313,7 @@ fn main() { println!("{}.{}.{}", ver.0, ver.1, ver.2); 0 } - ("status", _) => crate::commands::status::run(make_store(&cli_args)), + ("status", _) => crate::webclient::run_command(make_store(&cli_args), crate::commands::status::run), ("set", Some(sub_cli_args)) => { 0 } ("peer", Some(sub_cli_args)) => { 0 } ("network", Some(sub_cli_args)) => { 0 } @@ -323,7 +321,7 @@ fn main() { ("leave", Some(sub_cli_args)) => { 0 } ("service", _) => { let store = make_store(&cli_args); - drop(cli_args); // free memory + drop(cli_args); // free no longer needed memory before entering service service::run(store) }, ("controller", Some(sub_cli_args)) => { 0 } diff --git a/service/src/utils.rs b/service/src/utils.rs index cc0c01d02..f345fca79 100644 --- a/service/src/utils.rs +++ b/service/src/utils.rs @@ -23,26 +23,6 @@ use zerotier_core::{Identity, Locator}; use crate::osdep; use crate::osdep::time; -#[inline(always)] -pub(crate) fn sha512>(data: T) -> [u8; 64] { - let mut r: MaybeUninit<[u8; 64]> = MaybeUninit::uninit(); - let d = data.as_ref(); - unsafe { - osdep::sha512(d.as_ptr().cast(), d.len() as c_uint, r.as_mut_ptr().cast()); - r.assume_init() - } -} - -#[inline(always)] -pub(crate) fn sha384>(data: T) -> [u8; 48] { - let mut r: MaybeUninit<[u8; 48]> = MaybeUninit::uninit(); - let d = data.as_ref(); - unsafe { - osdep::sha384(d.as_ptr().cast(), d.len() as c_uint, r.as_mut_ptr().cast()); - r.assume_init() - } -} - #[inline(always)] pub(crate) fn ms_since_epoch() -> i64 { // This is easy to do in the Rust stdlib, but the version in OSUtils is probably faster. diff --git a/service/src/webclient.rs b/service/src/webclient.rs index 133985c13..ec6aae1d8 100644 --- a/service/src/webclient.rs +++ b/service/src/webclient.rs @@ -11,34 +11,100 @@ */ /****/ +use std::error::Error; +use std::future::Future; use std::str::FromStr; use std::time::Duration; -use hyper::Uri; +use std::rc::Rc; use std::sync::Arc; -use crate::store::Store; -use std::future::Future; -/// Launch the supplied function inside the tokio runtime. -/// The return value of this function should be the process exit code. -/// This is for implementation of commands that query the HTTP API, not HTTP -/// requests from a running server. -pub(crate) fn command<'a, R: Future>, F: FnOnce(Arc>, hyper::Uri) -> R>(store: Arc, func: F) -> i32 { +use hyper::{Uri, Response, Body, Method, Request, StatusCode}; +use crate::store::Store; + +pub(crate) type HttpClient = Rc>; + +/// Launch the supplied function with a ready to go HTTP client, the auth token, and the API URI. +/// This is boilerplate code for CLI commands that invoke the HTTP API. Since it instantiates and +/// then kills a tokio runtime, it's not for use in the service code that runs in a long-running +/// tokio runtime. +pub(crate) fn run_command< + R: Future>, + F: FnOnce(Arc, HttpClient, Uri, String) -> R +>(store: Arc, func: F) -> i32 { let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); let code = rt.block_on(async move { let uri = store.load_uri(); if uri.is_err() { - println!("ERROR: unable to read 'zerotier.uri' to get local HTTP API address."); + println!("ERROR: 'zerotier.uri' not found in '{}', unable to get service API endpoint.", store.base_path.to_str().unwrap()); 1 } else { - let f = func(Arc::new(hyper::Client::new()), uri.unwrap()); - f.await.map_or_else(|e| { - println!("ERROR: HTTP request failed: {}", e.to_string()); + let auth_token = store.auth_token(false); + if auth_token.is_err() { + println!("ERROR: unable to read API authorization token from '{}': {}", store.base_path.to_str().unwrap(), auth_token.err().unwrap().to_string()); 1 - }, |code| { - code - }) + } else { + let uri = uri.unwrap(); + let uri_str = uri.to_string(); + func(store, Rc::new(hyper::Client::new()), uri, auth_token.unwrap()).await.map_or_else(|e| { + println!("ERROR: service API HTTP request failed: {}", e.to_string()); + println!("ZeroTier service may not be running or '{}' may be unreachable.", uri_str); + 1 + }, |code| { + code + }) + } } }); - rt.shutdown_timeout(Duration::from_millis(10)); + rt.shutdown_timeout(Duration::from_millis(1)); // all tasks should be done in a command anyway, this is just a sanity check code } + +/// Send a request to the API with support for HTTP digest authentication. +/// The data option is for PUT and POST requests. For GET it is ignored. Errors indicate total +/// failure such as connection refused. A returned result must still have its status checked. If +/// it's 401 (unauthorized) it likely means the auth_token is wrong. +pub(crate) async fn request>(client: &HttpClient, method: Method, uri: Uri, data: D, auth_token: String) -> Result, Box> { + let body = data.as_ref().to_vec(); + + let req = Request::builder().method(&method).version(hyper::Version::HTTP_11).uri(&uri).body(Body::from(body.clone())); + if req.is_err() { + return Err(Box::new(req.err().unwrap())); + } + let res = client.request(req.unwrap()).await; + if res.is_err() { + return Err(Box::new(res.err().unwrap())); + } + let res = res.unwrap(); + + if res.status() == StatusCode::UNAUTHORIZED { + let auth = res.headers().get(hyper::header::WWW_AUTHENTICATE); + if auth.is_none() { + return Ok(res); // return the 401 reply + } + let auth = auth.unwrap().to_str(); + if auth.is_err() { + return Err(Box::new(auth.err().unwrap())); + } + let mut auth = digest_auth::parse(auth.unwrap()); + if auth.is_err() { + return Err(Box::new(auth.err().unwrap())); + } + let ac = digest_auth::AuthContext::new("zerotier", auth_token, uri.to_string()); + let auth = auth.unwrap().respond(&ac); + if auth.is_err() { + return Err(Box::new(auth.err().unwrap())); + } + + let req = Request::builder().method(&method).version(hyper::Version::HTTP_11).uri(&uri).header(hyper::header::WWW_AUTHENTICATE, auth.unwrap().to_header_string()).body(Body::from(body)); + if req.is_err() { + return Err(Box::new(req.err().unwrap())); + } + let res = client.request(req.unwrap()).await; + if res.is_err() { + return Err(Box::new(res.err().unwrap())); + } + return Ok(res.unwrap()); + } + + return Ok(res); +}