1use anyhow::{anyhow, bail};
2use colored::*;
3use dialoguer::Confirm;
4use hyper::{http, server::conn::AddrStream, Body, Request, Response};
5use indoc::printdoc;
6use innernet_shared::{
7 get_local_addrs, update_hosts_file, AddCidrOpts, AddPeerOpts, DeleteCidrOpts,
8 EnableDisablePeerOpts, Endpoint, IoErrorContext, NetworkOpts, PeerContents, RenameCidrOpts,
9 RenamePeerOpts, INNERNET_PUBKEY_HEADER,
10};
11use ipnet::IpNet;
12use parking_lot::{Mutex, RwLock};
13use rusqlite::Connection;
14use serde::{Deserialize, Serialize};
15use std::{
16 collections::{HashMap, VecDeque},
17 convert::TryInto,
18 env,
19 fs::File,
20 io::prelude::*,
21 net::{IpAddr, SocketAddr, TcpListener},
22 ops::Deref,
23 path::{Path, PathBuf},
24 sync::Arc,
25 time::Duration,
26};
27use subtle::ConstantTimeEq;
28use wireguard_control::{Backend, Device, DeviceUpdate, InterfaceName, Key, PeerConfigBuilder};
29
30mod api;
31mod db;
32mod error;
33pub mod initialize;
34#[cfg(test)]
35mod test;
36mod util;
37
38use db::{DatabaseCidr, DatabasePeer};
39pub use error::ServerError;
40use innernet_shared::{prompts, wg, CidrTree, Error, Interface};
41
42const VERSION: &str = env!("CARGO_PKG_VERSION");
43
44type Db = Arc<Mutex<Connection>>;
45type Endpoints = Arc<RwLock<HashMap<String, SocketAddr>>>;
46
47#[derive(Clone)]
48pub struct Context {
49 pub db: Db,
50 pub endpoints: Endpoints,
51 pub interface: InterfaceName,
52 pub backend: Backend,
53 pub public_key: Key,
54}
55
56pub struct Session {
57 pub context: Context,
58 pub peer: DatabasePeer,
59}
60
61impl Session {
62 pub fn admin_capable(&self) -> bool {
63 self.peer.is_admin && self.user_capable()
64 }
65
66 pub fn user_capable(&self) -> bool {
67 !self.peer.is_disabled && self.peer.is_redeemed
68 }
69
70 pub fn redeemable(&self) -> bool {
71 !self.peer.is_disabled && !self.peer.is_redeemed
72 }
73}
74
75#[derive(Deserialize, Serialize, Debug)]
76#[serde(rename_all = "kebab-case")]
77pub struct ConfigFile {
78 pub private_key: String,
80
81 pub listen_port: u16,
83
84 pub address: IpAddr,
86
87 pub network_cidr_prefix: u8,
89}
90
91impl ConfigFile {
92 pub fn write_to_path<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
93 let mut invitation_file = File::create(&path).with_path(&path)?;
94 innernet_shared::chmod(&invitation_file, 0o600)?;
95 invitation_file
96 .write_all(toml::to_string(self).unwrap().as_bytes())
97 .with_path(path)?;
98 Ok(())
99 }
100
101 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
102 let path = path.as_ref();
103 let file = File::open(path).with_path(path)?;
104 if innernet_shared::chmod(&file, 0o600)? {
105 println!(
106 "{} updated permissions for {} to 0600.",
107 "[!]".yellow(),
108 path.display()
109 );
110 }
111 Ok(toml::from_str(
112 &std::fs::read_to_string(path).with_path(path)?,
113 )?)
114 }
115}
116
117#[derive(Clone, Debug)]
118pub struct ServerConfig {
119 pub config_dir: PathBuf,
120 pub data_dir: PathBuf,
121}
122
123impl ServerConfig {
124 pub fn new(config_dir: PathBuf, data_dir: PathBuf) -> Self {
125 Self {
126 config_dir,
127 data_dir,
128 }
129 }
130
131 pub fn database_dir(&self) -> &Path {
132 &self.data_dir
133 }
134
135 pub fn database_path(&self, interface: &InterfaceName) -> PathBuf {
136 PathBuf::new()
137 .join(self.database_dir())
138 .join(interface.to_string())
139 .with_extension("db")
140 }
141
142 pub fn config_dir(&self) -> &Path {
143 &self.config_dir
144 }
145
146 pub fn config_path(&self, interface: &InterfaceName) -> PathBuf {
147 PathBuf::new()
148 .join(self.config_dir())
149 .join(interface.to_string())
150 .with_extension("conf")
151 }
152}
153
154fn open_database_connection(
155 interface: &InterfaceName,
156 conf: &ServerConfig,
157) -> Result<rusqlite::Connection, Error> {
158 let database_path = conf.database_path(interface);
159 if !Path::new(&database_path).exists() {
160 bail!(
161 "no database file found at {}",
162 database_path.to_string_lossy()
163 );
164 }
165
166 let conn = Connection::open(&database_path)?;
167 conn.pragma_update(None, "foreign_keys", 1)?;
169 db::auto_migrate(&conn)?;
170 Ok(conn)
171}
172
173pub fn add_peer(
174 interface: &InterfaceName,
175 conf: &ServerConfig,
176 opts: AddPeerOpts,
177 network: NetworkOpts,
178) -> Result<(), Error> {
179 let config = ConfigFile::from_file(conf.config_path(interface))?;
180 let conn = open_database_connection(interface, conf)?;
181 let peers = DatabasePeer::list(&conn)?
182 .into_iter()
183 .map(|dp| dp.inner)
184 .collect::<Vec<_>>();
185 let cidrs = DatabaseCidr::list(&conn)?;
186 let cidr_tree = CidrTree::new(&cidrs[..]);
187
188 if let Some(result) = innernet_shared::prompts::add_peer(&peers, &cidr_tree, &opts)? {
189 let (peer_request, keypair, target_path, mut target_file) = result;
190 let peer = DatabasePeer::create(&conn, peer_request)?;
191 if cfg!(not(test)) && Device::get(interface, network.backend).is_ok() {
192 DeviceUpdate::new()
194 .add_peer(PeerConfigBuilder::from(&*peer))
195 .apply(interface, network.backend)
196 .map_err(|_| ServerError::WireGuard)?;
197
198 println!("adding to WireGuard interface: {}", &*peer);
199 }
200
201 let server_peer = DatabasePeer::get(&conn, 1)?;
202 prompts::write_peer_invitation(
203 (&mut target_file, &target_path),
204 interface,
205 &peer,
206 &server_peer,
207 &cidr_tree,
208 keypair,
209 &SocketAddr::new(config.address, config.listen_port),
210 )?;
211 } else {
212 println!("exited without creating peer.");
213 }
214
215 Ok(())
216}
217
218pub fn rename_peer(
219 interface: &InterfaceName,
220 conf: &ServerConfig,
221 opts: RenamePeerOpts,
222) -> Result<(), Error> {
223 let conn = open_database_connection(interface, conf)?;
224 let peers = DatabasePeer::list(&conn)?
225 .into_iter()
226 .map(|dp| dp.inner)
227 .collect::<Vec<_>>();
228
229 if let Some((peer_request, old_name)) = innernet_shared::prompts::rename_peer(&peers, &opts)? {
230 let mut db_peer = DatabasePeer::list(&conn)?
231 .into_iter()
232 .find(|p| p.name == old_name)
233 .ok_or_else(|| anyhow!("Peer not found."))?;
234 db_peer.update(&conn, peer_request)?;
235 } else {
236 println!("exited without creating peer.");
237 }
238
239 Ok(())
240}
241
242pub fn enable_or_disable_peer(
243 interface: &InterfaceName,
244 conf: &ServerConfig,
245 enable: bool,
246 network: NetworkOpts,
247 opts: EnableDisablePeerOpts,
248) -> Result<(), Error> {
249 let conn = open_database_connection(interface, conf)?;
250 let peers = DatabasePeer::list(&conn)?
251 .into_iter()
252 .map(|dp| dp.inner)
253 .collect::<Vec<_>>();
254
255 if let Some(peer) = prompts::enable_or_disable_peer(&peers[..], &opts, enable)? {
256 let mut db_peer = DatabasePeer::get(&conn, peer.id)?;
257 db_peer.update(
258 &conn,
259 PeerContents {
260 is_disabled: !enable,
261 ..peer.contents.clone()
262 },
263 )?;
264
265 if enable {
266 DeviceUpdate::new()
267 .add_peer(db_peer.deref().into())
268 .apply(interface, network.backend)
269 .map_err(|_| ServerError::WireGuard)?;
270 } else {
271 let public_key =
272 Key::from_base64(&peer.public_key).map_err(|_| ServerError::WireGuard)?;
273
274 DeviceUpdate::new()
275 .remove_peer_by_key(&public_key)
276 .apply(interface, network.backend)
277 .map_err(|_| ServerError::WireGuard)?;
278 }
279 } else {
280 log::info!("exiting without enabling or disabling peer.");
281 }
282
283 Ok(())
284}
285
286pub fn add_cidr(
287 interface: &InterfaceName,
288 conf: &ServerConfig,
289 opts: AddCidrOpts,
290) -> Result<(), Error> {
291 let conn = open_database_connection(interface, conf)?;
292 let cidrs = DatabaseCidr::list(&conn)?;
293 if let Some(cidr_request) = innernet_shared::prompts::add_cidr(&cidrs, &opts)? {
294 let cidr = DatabaseCidr::create(&conn, cidr_request)?;
295 printdoc!(
296 "
297 CIDR \"{cidr_name}\" added.
298
299 Right now, peers within {cidr_name} can only see peers in the same CIDR, and in
300 the special \"innernet-server\" CIDR that includes the innernet server peer.
301
302 You'll need to add more associations for peers in diffent CIDRs to communicate.
303 ",
304 cidr_name = cidr.name.bold()
305 );
306 } else {
307 println!("exited without creating CIDR.");
308 }
309
310 Ok(())
311}
312
313pub fn rename_cidr(
314 interface: &InterfaceName,
315 conf: &ServerConfig,
316 opts: RenameCidrOpts,
317) -> Result<(), Error> {
318 let conn = open_database_connection(interface, conf)?;
319 let cidrs = DatabaseCidr::list(&conn)?;
320
321 if let Some((cidr_request, old_name)) = innernet_shared::prompts::rename_cidr(&cidrs, &opts)? {
322 let db_cidr = DatabaseCidr::list(&conn)?
323 .into_iter()
324 .find(|c| c.name == old_name)
325 .ok_or_else(|| anyhow!("CIDR not found."))?;
326 db::DatabaseCidr::from(db_cidr).update(&conn, cidr_request)?;
327 } else {
328 println!("exited without renaming CIDR.");
329 }
330
331 Ok(())
332}
333
334pub fn delete_cidr(
335 interface: &InterfaceName,
336 conf: &ServerConfig,
337 args: DeleteCidrOpts,
338) -> Result<(), Error> {
339 println!("Fetching eligible CIDRs");
340 let conn = open_database_connection(interface, conf)?;
341 let cidrs = DatabaseCidr::list(&conn)?;
342 let peers = DatabasePeer::list(&conn)?
343 .into_iter()
344 .map(|dp| dp.inner)
345 .collect::<Vec<_>>();
346
347 let cidr_id = prompts::delete_cidr(&cidrs, &peers, &args)?;
348
349 println!("Deleting CIDR...");
350 DatabaseCidr::delete(&conn, cidr_id)?;
351
352 println!("CIDR deleted.");
353
354 Ok(())
355}
356
357pub fn uninstall(
358 interface: &InterfaceName,
359 conf: &ServerConfig,
360 network: NetworkOpts,
361 yes: bool,
362) -> Result<(), Error> {
363 if yes
364 || Confirm::with_theme(&*prompts::THEME)
365 .with_prompt(format!(
366 "Permanently delete network \"{}\"?",
367 interface.as_str_lossy().yellow()
368 ))
369 .default(false)
370 .interact()?
371 {
372 println!("{} bringing down interface (if up).", "[*]".dimmed());
373 wg::down(interface, network.backend).ok();
374 let config = conf.config_path(interface);
375 let data = conf.database_path(interface);
376 std::fs::remove_file(&config)
377 .with_path(&config)
378 .map_err(|e| println!("[!] {}", e.to_string().yellow()))
379 .ok();
380 std::fs::remove_file(&data)
381 .with_path(&data)
382 .map_err(|e| println!("[!] {}", e.to_string().yellow()))
383 .ok();
384 println!(
385 "{} network {} is uninstalled.",
386 "[*]".dimmed(),
387 interface.as_str_lossy().yellow()
388 );
389 }
390 Ok(())
391}
392
393fn spawn_endpoint_refresher(interface: InterfaceName, network: NetworkOpts) -> Endpoints {
394 let endpoints = Arc::new(RwLock::new(HashMap::new()));
395 tokio::task::spawn({
396 let endpoints = endpoints.clone();
397 async move {
398 let mut interval = tokio::time::interval(Duration::from_secs(10));
399 loop {
400 interval.tick().await;
401 if let Ok(info) = Device::get(&interface, network.backend) {
402 for peer in info.peers {
403 if let Some(endpoint) = peer.config.endpoint {
404 endpoints
405 .write()
406 .insert(peer.config.public_key.to_base64(), endpoint);
407 }
408 }
409 }
410 }
411 }
412 });
413 endpoints
414}
415
416fn spawn_expired_invite_sweeper(db: Db) {
417 tokio::task::spawn(async move {
418 let mut interval = tokio::time::interval(Duration::from_secs(10));
419 loop {
420 interval.tick().await;
421 match DatabasePeer::delete_expired_invites(&db.lock()) {
422 Ok(deleted) if deleted > 0 => {
423 log::info!("Deleted {} expired peer invitations.", deleted)
424 },
425 Err(e) => log::error!("Failed to delete expired peer invitations: {}", e),
426 _ => {},
427 }
428 }
429 });
430}
431
432fn spawn_hostfile_writer(db: Db, interface: InterfaceName, hosts_path: PathBuf) {
433 tokio::task::spawn({
434 async move {
435 let mut interval = tokio::time::interval(Duration::from_secs(10));
436 loop {
437 interval.tick().await;
438
439 match DatabasePeer::list_enabled(&db.lock()) {
440 Ok(peers) => {
441 if let Err(e) = update_hosts_file(
442 &interface,
443 &hosts_path,
444 peers.into_iter().map(|peer| peer.inner),
445 ) {
446 log::error!("Failed to write hostfile: {}", e);
447 }
448 },
449 Err(e) => {
450 log::error!("Failed to list enabled peers for hostfile writing: {}", e)
451 },
452 }
453 }
454 }
455 });
456}
457
458pub async fn serve(
459 interface: InterfaceName,
460 conf: &ServerConfig,
461 network: NetworkOpts,
462 hosts_path: Option<PathBuf>,
463) -> Result<(), Error> {
464 let config = ConfigFile::from_file(conf.config_path(&interface))?;
465 log::debug!("opening database connection...");
466 let conn = open_database_connection(&interface, conf)?;
467
468 let mut peers = DatabasePeer::list(&conn)?;
469 log::debug!("peers listed...");
470 let peer_configs = peers
471 .iter()
472 .map(|peer| peer.deref().into())
473 .collect::<Vec<PeerConfigBuilder>>();
474
475 log::info!("bringing up interface.");
476 wg::up(
477 &interface,
478 &config.private_key,
479 IpNet::new(config.address, config.network_cidr_prefix)?,
480 Some(config.listen_port),
481 None,
482 network,
483 )?;
484
485 DeviceUpdate::new()
486 .add_peers(&peer_configs)
487 .apply(&interface, network.backend)?;
488
489 log::info!("{} peers added to wireguard interface.", peers.len());
490
491 let candidates: Vec<Endpoint> = get_local_addrs()?
492 .map(|addr| SocketAddr::from((addr, config.listen_port)).into())
493 .collect();
494 let num_candidates = candidates.len();
495 let myself = peers
496 .iter_mut()
497 .find(|peer| peer.ip == config.address)
498 .expect("Couldn't find server peer in peer list.");
499 myself.update(
500 &conn,
501 PeerContents {
502 candidates,
503 ..myself.contents.clone()
504 },
505 )?;
506
507 log::info!(
508 "{} local candidates added to server peer config.",
509 num_candidates
510 );
511
512 let public_key = wireguard_control::Key::from_base64(&config.private_key)?.get_public();
513 let db = Arc::new(Mutex::new(conn));
514 let endpoints = spawn_endpoint_refresher(interface, network);
515 spawn_expired_invite_sweeper(db.clone());
516
517 if let Some(path) = hosts_path {
518 spawn_hostfile_writer(db.clone(), interface, path);
519 }
520
521 let context = Context {
522 db,
523 endpoints,
524 interface,
525 public_key,
526 backend: network.backend,
527 };
528
529 log::info!("innernet-server {} starting.", VERSION);
530
531 let listener = get_listener((config.address, config.listen_port).into(), &interface)?;
532
533 let make_svc = hyper::service::make_service_fn(move |socket: &AddrStream| {
534 let remote_addr = socket.remote_addr();
535 let context = context.clone();
536 async move {
537 Ok::<_, http::Error>(hyper::service::service_fn(move |req: Request<Body>| {
538 log::debug!("{} - {} {}", &remote_addr, req.method(), req.uri());
539 hyper_service(req, context.clone(), remote_addr)
540 }))
541 }
542 });
543
544 let server = hyper::Server::from_tcp(listener)?.serve(make_svc);
545
546 server.await?;
547
548 Ok(())
549}
550
551#[cfg(target_os = "linux")]
558fn get_listener(addr: SocketAddr, interface: &InterfaceName) -> Result<TcpListener, Error> {
559 let listener = TcpListener::bind(addr)?;
560 listener.set_nonblocking(true)?;
561 let sock = socket2::Socket::from(listener);
562 sock.bind_device(Some(interface.as_str_lossy().as_bytes()))?;
563 Ok(sock.into())
564}
565
566#[cfg(not(target_os = "linux"))]
573fn get_listener(addr: SocketAddr, _interface: &InterfaceName) -> Result<TcpListener, Error> {
574 let listener = TcpListener::bind(addr)?;
575 listener.set_nonblocking(true)?;
576 Ok(listener)
577}
578
579pub(crate) async fn hyper_service(
580 req: Request<Body>,
581 context: Context,
582 remote_addr: SocketAddr,
583) -> Result<Response<Body>, http::Error> {
584 let components: VecDeque<_> = req
586 .uri()
587 .path()
588 .trim_start_matches('/')
589 .split('/')
590 .map(String::from)
591 .collect();
592
593 routes(req, context, remote_addr, components)
594 .await
595 .or_else(TryInto::try_into)
596}
597
598async fn routes(
599 req: Request<Body>,
600 context: Context,
601 remote_addr: SocketAddr,
602 mut components: VecDeque<String>,
603) -> Result<Response<Body>, ServerError> {
604 if components.pop_front().as_deref() != Some("v1") {
606 Err(ServerError::NotFound)
607 } else {
608 let session = get_session(&req, context, remote_addr.ip())?;
609 let component = components.pop_front();
610 match component.as_deref() {
611 Some("user") => api::user::routes(req, components, session).await,
612 Some("admin") => api::admin::routes(req, components, session).await,
613 _ => Err(ServerError::NotFound),
614 }
615 }
616}
617
618fn get_session(
619 req: &Request<Body>,
620 context: Context,
621 addr: IpAddr,
622) -> Result<Session, ServerError> {
623 let pubkey = req
624 .headers()
625 .get(INNERNET_PUBKEY_HEADER)
626 .ok_or(ServerError::Unauthorized)?;
627 let pubkey = pubkey.to_str().map_err(|_| ServerError::Unauthorized)?;
628 let pubkey = Key::from_base64(pubkey).map_err(|_| ServerError::Unauthorized)?;
629 if pubkey
630 .as_bytes()
631 .ct_eq(context.public_key.as_bytes())
632 .into()
633 {
634 let peer = DatabasePeer::get_from_ip(&context.db.lock(), addr).map_err(|e| match e {
635 rusqlite::Error::QueryReturnedNoRows => ServerError::Unauthorized,
636 e => ServerError::Database(e),
637 })?;
638
639 if !peer.is_disabled {
640 return Ok(Session { context, peer });
641 }
642 }
643
644 Err(ServerError::Unauthorized)
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650 use crate::test;
651 use anyhow::Result;
652 use hyper::StatusCode;
653 use std::path::Path;
654
655 #[test]
656 fn test_init_wizard() -> Result<(), Error> {
657 let server = test::Server::new()?;
659
660 assert!(Path::new(&server.wg_conf_path()).exists());
661
662 Ok(())
663 }
664
665 #[tokio::test]
666 async fn test_with_session_disguised_with_headers() -> Result<(), Error> {
667 let server = test::Server::new()?;
668
669 let path = if cfg!(feature = "v6-test") {
670 format!("http://[{}]/v1/admin/peers", test::WG_MANAGE_PEER_IP)
671 } else {
672 format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP)
673 };
674 let req = Request::builder()
675 .uri(path)
676 .header("Forwarded", format!("for={}", test::ADMIN_PEER_IP))
677 .header("X-Forwarded-For", test::ADMIN_PEER_IP)
678 .header("X-Real-IP", test::ADMIN_PEER_IP)
679 .body(Body::empty())
680 .unwrap();
681
682 let res = if cfg!(feature = "v6-test") {
684 server.raw_request("fd00:1337::1337", req).await
685 } else {
686 server.raw_request("10.80.80.80", req).await
687 };
688
689 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
693
694 Ok(())
695 }
696
697 #[tokio::test]
698 async fn test_incorrect_public_key() -> Result<(), Error> {
699 let server = test::Server::new()?;
700
701 let key = Key::generate_private().get_public();
702
703 let path = if cfg!(feature = "v6-test") {
704 format!("http://[{}]/v1/admin/peers", test::WG_MANAGE_PEER_IP)
705 } else {
706 format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP)
707 };
708 let req = Request::builder()
710 .uri(path)
711 .header(innernet_shared::INNERNET_PUBKEY_HEADER, key.to_base64())
712 .body(Body::empty())
713 .unwrap();
714 let res = if cfg!(feature = "v6-test") {
715 server.raw_request("fd00:1337::1337", req).await
716 } else {
717 server.raw_request("10.80.80.80", req).await
718 };
719
720 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
724
725 Ok(())
726 }
727
728 #[tokio::test]
729 async fn test_unparseable_public_key() -> Result<(), Error> {
730 let server = test::Server::new()?;
731
732 let path = if cfg!(feature = "v6-test") {
733 format!("http://[{}]/v1/admin/peers", test::WG_MANAGE_PEER_IP)
734 } else {
735 format!("http://{}/v1/admin/peers", test::WG_MANAGE_PEER_IP)
736 };
737 let req = Request::builder()
738 .uri(path)
739 .header(innernet_shared::INNERNET_PUBKEY_HEADER, "!!!")
740 .body(Body::empty())
741 .unwrap();
742 let res = if cfg!(feature = "v6-test") {
743 server.raw_request("fd00:1337::1337", req).await
744 } else {
745 server.raw_request("10.80.80.80", req).await
746 };
747
748 assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
752
753 Ok(())
754 }
755}