1#![doc(html_favicon_url = "https://docs.sequoia-pgp.org/favicon.png")]
36#![doc(html_logo_url = "https://docs.sequoia-pgp.org/logo.svg")]
37#![warn(missing_docs)]
38
39use std::fs;
40use std::io::{self, Read, Seek, Write};
41use std::net::{Ipv4Addr, SocketAddr, TcpStream, TcpListener};
42use std::path::Path;
43use std::path::PathBuf;
44use std::thread::JoinHandle;
45
46use anyhow::anyhow;
47use anyhow::Context as _;
48
49use fs2::FileExt;
50
51use capnp_rpc::{RpcSystem, twoparty};
52use capnp_rpc::rpc_twoparty_capnp::Side;
53pub use capnp_rpc as capnp_rpc;
54
55#[cfg(unix)]
56use std::os::unix::{io::{IntoRawFd, FromRawFd}, fs::OpenOptionsExt};
57#[cfg(windows)]
58use std::os::windows::io::{AsRawSocket, IntoRawSocket, FromRawSocket};
59#[cfg(windows)]
60use winapi::um::winsock2;
61
62use std::process::{Command, Stdio};
63use std::thread;
64
65use sequoia_openpgp as openpgp;
66use openpgp::crypto::mem::Encrypted;
67use openpgp::crypto::mem::Protected;
68use openpgp::crypto::random;
69
70#[macro_use] mod macros;
71pub mod keybox;
72mod keygrip;
73pub use self::keygrip::Keygrip;
74pub mod sexp;
75mod core;
76pub use crate::core::{Config, Context, IPCPolicy};
77
78#[cfg(test)]
79mod tests;
80
81pub trait Handler {
83 fn handle(&self,
85 network: capnp_rpc::twoparty::VatNetwork<tokio_util::compat::Compat<tokio::net::tcp::OwnedReadHalf>>)
86 -> RpcSystem<Side>;
87}
88
89pub type HandlerFactory = fn(
91 descriptor: Descriptor,
92 local: &tokio::task::LocalSet
93) -> Result<Box<dyn Handler>>;
94
95#[derive(Clone)]
97pub struct Descriptor {
98 ctx: core::Context,
99 rendezvous: PathBuf,
100 executable: PathBuf,
101 factory: HandlerFactory,
102}
103
104impl std::fmt::Debug for Descriptor {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 f.debug_struct("Descriptor")
107 .field("rendezvous", &self.rendezvous)
108 .field("executable", &self.executable)
109 .finish()
110 }
111}
112
113impl Descriptor {
114 pub fn new(ctx: &core::Context, rendezvous: PathBuf,
117 executable: PathBuf, factory: HandlerFactory)
118 -> Self {
119 Descriptor {
120 ctx: ctx.clone(),
121 rendezvous,
122 executable,
123 factory,
124 }
125 }
126
127 pub fn context(&self) -> &core::Context {
129 &self.ctx
130 }
131
132 pub fn rendez_vous(&self) -> &Path {
134 &self.rendezvous
135 }
136
137 pub fn connect(&self) -> Result<RpcSystem<Side>> {
145 self.connect_with_policy(*self.ctx.ipc_policy())
146 }
147
148 pub fn connect_with_policy(&self, policy: core::IPCPolicy)
159 -> Result<RpcSystem<Side>> {
160 let do_connect = |cookie: Cookie, mut s: TcpStream| {
161 cookie.send(&mut s)?;
162
163 s.set_nonblocking(true)?;
165 let stream = tokio::net::TcpStream::from_std(s)?;
166 stream.set_nodelay(true)?;
167
168 let (reader, writer) = stream.into_split();
169 use tokio_util::compat::TokioAsyncReadCompatExt;
170 use tokio_util::compat::TokioAsyncWriteCompatExt;
171 let (reader, writer) = (reader.compat(), writer.compat_write());
172
173 let network =
174 Box::new(twoparty::VatNetwork::new(reader, writer,
175 Side::Client,
176 Default::default()));
177
178 Ok(RpcSystem::new(network, None))
179 };
180
181 fs::create_dir_all(self.ctx.home())?;
182
183 let mut file = CookieFile::open(&self.rendezvous)?;
184
185 if let Some((cookie, rest)) = file.read()? {
186 let stream = String::from_utf8(rest).map_err(drop)
187 .and_then(|rest| rest.parse::<SocketAddr>().map_err(drop))
188 .and_then(|addr| TcpStream::connect(addr).map_err(drop));
189
190 if let Ok(s) = stream {
191 do_connect(cookie, s)
192 } else {
193 file.clear()?;
195 drop(file);
196 self.connect()
197 }
198 } else {
199 let cookie = Cookie::new()?;
200
201 let (addr, external, _join_handle) = match policy {
202 core::IPCPolicy::Internal => self.start(false)?,
203 core::IPCPolicy::External => self.start(true)?,
204 core::IPCPolicy::Robust => self.start(true)
205 .or_else(|_| self.start(false))?
206 };
207
208 cookie.send(&mut TcpStream::connect(addr)?)?;
210
211 if external {
212 file.write(&cookie, format!("{}", addr).as_bytes())?;
214 }
215 drop(file);
216
217 do_connect(cookie, TcpStream::connect(addr)?)
218 }
219 }
220
221 fn start(&self, external: bool)
224 -> Result<(SocketAddr, bool, Option<JoinHandle<Result<()>>>)>
225 {
226 let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 0)).unwrap();
227 let addr = listener.local_addr()?;
228
229 let join_handle: Option<JoinHandle<Result<()>>> = if external {
231 self.fork(listener)?;
232 None
233 } else {
234 Some(self.spawn(listener)?)
235 };
236
237 Ok((addr, external, join_handle))
238 }
239
240 fn fork(&self, listener: TcpListener) -> Result<()> {
241 let mut cmd = new_background_command(&self.executable);
242 cmd
243 .arg("--home")
244 .arg(self.ctx.home())
245 .arg("--lib")
246 .arg(self.ctx.lib())
247 .arg("--ephemeral")
248 .arg(self.ctx.ephemeral().to_string())
249 .arg("--socket").arg("0")
250 .stdout(Stdio::null())
251 .stderr(Stdio::null());
252
253 platform! {
254 unix => {
255 cmd.stdin(unsafe { Stdio::from_raw_fd(listener.into_raw_fd()) });
257 },
258 windows => {
259 unsafe {
262 match winapi::um::handleapi::SetHandleInformation(
263 listener.as_raw_socket() as _,
264 winapi::um::winbase::HANDLE_FLAG_INHERIT,
265 winapi::um::winbase::HANDLE_FLAG_INHERIT,
266 ) {
267 0 => Err(std::io::Error::last_os_error()),
268 _ => Ok(())
269 }?
270 };
271 cmd.env("SOCKET", format!("{}", listener.into_raw_socket()));
276 }
277 }
278
279 cmd.spawn()?;
280 Ok(())
281 }
282
283 fn spawn(&self, l: TcpListener) -> Result<JoinHandle<Result<()>>> {
284 let descriptor = self.clone();
285 let join_handle = thread::spawn(move || -> Result<()> {
286 Server::new(descriptor)
287 .with_context(|| "Failed to spawn server".to_string())?
288 .serve_listener(l)
289 .with_context(|| "Failed to spawn server".to_string())?;
290 Ok(())
291 });
292
293 Ok(join_handle)
294 }
295
296 pub fn bootstrap(&mut self) -> Result<Option<JoinHandle<Result<()>>>> {
305 let mut file = CookieFile::open(&self.rendezvous)?;
306
307 if let Some((cookie, rest)) = file.read()? {
310 if let Ok(addr) = String::from_utf8(rest).map_err(drop)
311 .and_then(|rest| rest.parse::<SocketAddr>().map_err(drop))
312 {
313 let stream = TcpStream::connect(&addr).map_err(drop);
314
315 if let Ok(mut s) = stream {
316 if let Ok(()) = cookie.send(&mut s) {
317 return Ok(None);
319 }
320 }
321 }
322 }
323
324 let cookie = Cookie::new()?;
326
327 let (addr, _external, join_handle) = self.start(false)?;
329 let join_handle = join_handle
330 .expect("start returns the join handle for in-process servers");
331
332 file.write(&cookie, format!("{}", addr).as_bytes())?;
333 drop(file);
335
336 let mut s = TcpStream::connect(addr)?;
338 cookie.send(&mut s)?;
339
340 Ok(Some(join_handle))
341 }
342}
343
344pub struct Server {
346 runtime: tokio::runtime::Runtime,
347 descriptor: Descriptor,
348}
349
350impl Server {
351 pub fn new(descriptor: Descriptor) -> Result<Self> {
353 Ok(Server {
354 runtime: tokio::runtime::Runtime::new()?,
355 descriptor,
356 })
357 }
358
359 pub fn context() -> Result<core::Context> {
361 use std::env::args;
362 let args: Vec<String> = args().collect();
363
364 if args.len() != 7 || args[1] != "--home"
365 || args[3] != "--lib" || args[5] != "--ephemeral" {
366 return Err(anyhow!(
367 "Usage: {} --home <HOMEDIR> --lib <LIBDIR> \
368 --ephemeral true|false", args[0]));
369 }
370
371 let mut cfg = core::Context::configure()
372 .home(&args[2]).lib(&args[4]);
373
374 if let Ok(ephemeral) = args[6].parse() {
375 if ephemeral {
376 cfg.set_ephemeral();
377 }
378 } else {
379 return Err(anyhow!(
380 "Expected 'true' or 'false' for --ephemeral, got: {}",
381 args[6]));
382 }
383
384 cfg.build()
385 }
386
387 pub fn serve(&mut self) -> Result<()> {
395 let listener = platform! {
396 unix => unsafe { TcpListener::from_raw_fd(0) },
397 windows => {
398 let socket = std::env::var("SOCKET")?.parse()?;
399 unsafe { TcpListener::from_raw_socket(socket) }
400 }
401 };
402 self.serve_listener(listener)
403 }
404
405 fn serve_listener(&mut self, l: TcpListener) -> Result<()> {
406 let cookie = {
432 let mut i = l.accept()?;
433 Cookie::receive(&mut i.0)?
434 };
435
436 let local = tokio::task::LocalSet::new();
438 let handler = (self.descriptor.factory)(self.descriptor.clone(), &local)?;
439
440 let server = async move {
441 l.set_nonblocking(true)?;
442 let socket = tokio::net::TcpListener::from_std(l).unwrap();
443
444 loop {
445 let (mut socket, _) = socket.accept().await?;
446
447 let _ = socket.set_nodelay(true);
448 let received_cookie = match Cookie::receive_async(&mut socket).await {
449 Err(_) => continue, Ok(received_cookie) => received_cookie,
451 };
452 if received_cookie != cookie {
453 continue; }
455
456 let (reader, writer) = socket.into_split();
457
458 use tokio_util::compat::TokioAsyncReadCompatExt;
459 use tokio_util::compat::TokioAsyncWriteCompatExt;
460 let (reader, writer) = (reader.compat(), writer.compat_write());
461
462 let network =
463 twoparty::VatNetwork::new(reader, writer,
464 Side::Server, Default::default());
465
466 let rpc_system = handler.handle(network);
467 let _ = tokio::task::spawn_local(rpc_system).await;
468 }
469 };
470
471 local.block_on(&self.runtime, server)
472 }
473}
474
475struct Cookie(Encrypted);
477
478impl Cookie {
479 const SIZE: usize = 32;
480
481 fn new() -> Result<Self> {
483 let mut c = Protected::new(Cookie::SIZE);
484 random(&mut c)
485 .context("Generating authentication token")?;
486 Ok(Cookie(Encrypted::new(c)?))
487 }
488
489 fn from(buf: &[u8]) -> Result<Option<Self>> {
491 if buf.len() == Cookie::SIZE {
492 let c = Protected::from(buf);
493 Ok(Some(Cookie(Encrypted::new(c)?)))
494 } else {
495 Ok(None)
496 }
497 }
498
499 fn extract(mut buf: Vec<u8>) -> Result<Option<(Self, Vec<u8>)>> {
502 if buf.len() >= Cookie::SIZE {
503 let r = buf.split_off(Cookie::SIZE);
504 Ok(Some((Cookie(Encrypted::new(Protected::from(buf))?), r)))
505 } else {
506 Ok(None)
507 }
508 }
509
510 fn receive<R: Read>(from: &mut R) -> Result<Self> {
512 let mut buf = Protected::new(Cookie::SIZE);
513 from.read_exact(&mut buf)?;
514 Ok(Cookie(Encrypted::new(buf)?))
515 }
516
517 async fn receive_async(socket: &mut tokio::net::TcpStream) -> io::Result<Cookie> {
519 use tokio::io::AsyncReadExt;
520
521 let mut buf = vec![0; Cookie::SIZE];
522 socket.read_exact(&mut buf).await?;
523 Ok(Cookie::from(&buf)
524 .map_err(|err| {
525 std::io::Error::new(std::io::ErrorKind::Other, err)
526 })?
527 .expect("enough bytes read"))
528 }
529
530
531 fn send<W: Write>(&self, to: &mut W) -> io::Result<()> {
533 self.0.map(|cookie| to.write_all(cookie))
534 }
535}
536
537impl PartialEq for Cookie {
538 fn eq(&self, other: &Cookie) -> bool {
539 self.0.map(|a| {
540 other.0.map(|b| {
541 a == b
542 })
543 })
544 }
545}
546
547struct CookieFile {
549 path: PathBuf,
550 file: fs::File,
551}
552
553impl CookieFile {
554 fn open(path: &Path) -> Result<CookieFile> {
559 if let Some(parent) = path.parent() {
560 fs::create_dir_all(parent)
561 .with_context(|| format!("Creating {}", parent.display()))?;
562 }
563
564 let mut file = fs::OpenOptions::new();
565 file
566 .read(true)
567 .write(true)
568 .create(true);
569 #[cfg(unix)]
570 file.mode(0o600);
571 let file = file.open(path)
572 .with_context(|| format!("Opening {}", path.display()))?;
573 file.lock_exclusive()
574 .with_context(|| format!("Locking {}", path.display()))?;
575
576 Ok(Self {
577 path: path.to_path_buf(),
578 file,
579 })
580 }
581
582 fn read(&mut self) -> Result<Option<(Cookie, Vec<u8>)>> {
588 let mut content = vec![];
589 self.file.read_to_end(&mut content)
590 .with_context(|| format!("Opening {}", self.path.display()))?;
591 Ok(Cookie::extract(content)?)
592 }
593
594 fn write(&mut self, cookie: &Cookie, data: &[u8]) -> Result<()> {
599 self.file.rewind()
600 .with_context(|| format!("Rewinding {}", self.path.display()))?;
601 self.file.set_len(0)
602 .with_context(|| format!("Truncating {}", self.path.display()))?;
603 cookie.0.map(|cookie| {
604 self.file.write_all(cookie)
605 .with_context(|| format!("Updating {}", self.path.display()))
606 })?;
607 self.file.write_all(data)
608 .with_context(|| format!("Updating {}", self.path.display()))?;
609
610 Ok(())
611 }
612
613 fn clear(&mut self) -> Result<()> {
617 self.file.set_len(0)
618 .with_context(|| format!("Truncating {}", self.path.display()))?;
619 Ok(())
620 }
621}
622
623#[derive(thiserror::Error, Debug)]
624pub enum Error {
626 #[error("Connection closed unexpectedly.")]
628 ConnectionClosed(Vec<u8>),
629}
630
631pub type Result<T> = ::std::result::Result<T, anyhow::Error>;
633
634#[cfg(windows)]
637use std::sync::atomic::{AtomicBool, Ordering};
638#[cfg(windows)]
639static WSA_INITED: AtomicBool = AtomicBool::new(false);
640
641#[cfg(windows)]
642#[ctor::ctor]
643fn wsa_startup() {
644 unsafe {
645 let ret = winsock2::WSAStartup(
646 0x202, &mut std::mem::zeroed(),
648 );
649 WSA_INITED.store(ret != 0, Ordering::SeqCst);
650 }
651}
652
653#[cfg(windows)]
654#[ctor::dtor]
655fn wsa_cleanup() {
656 if WSA_INITED.load(Ordering::SeqCst) {
657 let _ = unsafe { winsock2::WSACleanup() };
658 }
659}
660
661pub(crate) fn new_background_command<S>(program: S) -> Command
662where
663 S: AsRef<std::ffi::OsStr>,
664{
665 let command = Command::new(program);
666
667 #[cfg(windows)]
668 let command = {
669 use std::os::windows::process::CommandExt;
670
671 const CREATE_NO_WINDOW: u32 = 0x08000000;
673 let mut command = command;
674 command.creation_flags(CREATE_NO_WINDOW);
675 command
676 };
677
678 command
679}