greetd_ipc-0.9.0/.cargo_vcs_info.json0000644000000001500000000000100131430ustar { "git": { "sha1": "f3f32498851c6775a9d9ca0d46239b9a6de78204" }, "path_in_vcs": "greetd_ipc" }greetd_ipc-0.9.0/Cargo.toml0000644000000023140000000000100111450ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "greetd_ipc" version = "0.9.0" authors = ["Kenny Levinsen"] description = "An implementation of the greetd IPC protocol" homepage = "https://kl.wtf/projects/greetd" keywords = ["greetd"] license = "GPL-3.0" repository = "https://git.sr.ht/~kennylevinsen/greetd/" [package.metadata.docs.rs] all-features = true [dependencies.async-trait] version = "0.1" optional = true [dependencies.serde] version = "1.0" features = ["derive"] [dependencies.serde_json] version = "1.0" [dependencies.thiserror] version = "1.0" optional = true [dependencies.tokio] version = "1" features = ["io-util"] optional = true [features] codec = ["thiserror"] sync-codec = ["codec"] tokio-codec = [ "codec", "tokio", "async-trait", ] greetd_ipc-0.9.0/Cargo.toml.orig000064400000000000000000000012771046102023000146350ustar 00000000000000[package] name = "greetd_ipc" version = "0.9.0" authors = ["Kenny Levinsen"] edition = "2018" license = "GPL-3.0" homepage = "https://kl.wtf/projects/greetd" repository = "https://git.sr.ht/~kennylevinsen/greetd/" description = "An implementation of the greetd IPC protocol" keywords = ["greetd"] [package.metadata.docs.rs] all-features = true [features] codec = ["thiserror"] sync-codec = ["codec"] tokio-codec = ["codec", "tokio", "async-trait"] [dependencies] serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tokio = { version = "1", features = ["io-util"], optional = true } async-trait = { version = "0.1", optional = true } thiserror = { version = "1.0", optional = true } greetd_ipc-0.9.0/src/codec/mod.rs000064400000000000000000000024241046102023000147320ustar 00000000000000//! Reader/writer codecs for Request/Response. //! //! This is implemented in the form of two traits, SyncCodec and TokioCodec, //! which operate on the `std` and `tokio` implementations of reader/writer //! traits. The former is intended as the name suggests for synchronous //! operation, while the latter is for asynchronous operation when using tokio. //! //! These codecs are hidden behind the `sync-codec` and `tokio-codec` features, //! respectively. These features also implicitly enable the `codec` feature, //! which controls the entire `codec` module. //! use thiserror::Error as ThisError; #[derive(Debug, ThisError)] pub enum Error { #[error("serialization error: {0}")] Serialization(String), #[error("i/o error: {0}")] Io(String), #[error("EOF")] Eof, } impl From for Error { fn from(error: serde_json::error::Error) -> Self { Error::Serialization(error.to_string()) } } impl From for Error { fn from(error: std::io::Error) -> Self { Error::Io(format!("{}", error)) } } #[cfg(feature = "sync-codec")] mod sync; #[cfg(feature = "sync-codec")] pub use self::sync::SyncCodec; #[cfg(feature = "tokio-codec")] mod tokio; #[cfg(feature = "tokio-codec")] pub use self::tokio::TokioCodec; greetd_ipc-0.9.0/src/codec/sync.rs000064400000000000000000000050721046102023000151310ustar 00000000000000//! Synchronous reader/writer implementation, operating on an implementor of std::io::{Read, Write}. //! //! # Example //! //! ```no_run //! use std::env; //! use std::os::unix::net::UnixStream; //! use greetd_ipc::{Request, Response}; //! use greetd_ipc::codec::SyncCodec; //! //! fn main() -> Result<(), Box> { //! let mut stream = UnixStream::connect(env::var("GREETD_SOCK")?)?; //! Request::CreateSession { username: "john".to_string() }.write_to(&mut stream)?; //! let resp = Response::read_from(&mut stream)?; //! Ok(()) //! } //! ``` use crate::{codec::Error, Request, Response}; use std::io::{Read, Write}; /// Reader/writer implementation over std::io::{Read,Write}. pub trait SyncCodec { fn read_from(stream: &mut T) -> Result where Self: std::marker::Sized; fn write_to(&self, stream: &mut T) -> Result<(), Error>; } impl SyncCodec for Request { fn read_from(stream: &mut T) -> Result { let mut len_bytes = [0; 4]; stream .read_exact(&mut len_bytes) .map_err(|e| match e.kind() { std::io::ErrorKind::UnexpectedEof => Error::Eof, _ => e.into(), })?; let len = u32::from_ne_bytes(len_bytes); let mut resp_buf = vec![0; len as usize]; stream.read_exact(&mut resp_buf)?; serde_json::from_slice(&resp_buf).map_err(|e| e.into()) } fn write_to(&self, stream: &mut T) -> Result<(), Error> { let body_bytes = serde_json::to_vec(self)?; let len_bytes = (body_bytes.len() as u32).to_ne_bytes(); stream.write_all(&len_bytes)?; stream.write_all(&body_bytes)?; Ok(()) } } impl SyncCodec for Response { fn read_from(stream: &mut T) -> Result { let mut len_bytes = [0; 4]; stream .read_exact(&mut len_bytes) .map_err(|e| match e.kind() { std::io::ErrorKind::UnexpectedEof => Error::Eof, _ => e.into(), })?; let len = u32::from_ne_bytes(len_bytes); let mut resp_buf = vec![0; len as usize]; stream.read_exact(&mut resp_buf)?; serde_json::from_slice(&resp_buf).map_err(|e| e.into()) } fn write_to(&self, stream: &mut T) -> Result<(), Error> { let body_bytes = serde_json::to_vec(self)?; let len_bytes = (body_bytes.len() as u32).to_ne_bytes(); stream.write_all(&len_bytes)?; stream.write_all(&body_bytes)?; Ok(()) } } greetd_ipc-0.9.0/src/codec/tokio.rs000064400000000000000000000063221046102023000153010ustar 00000000000000//! Asynchronous reader/writer implementation, operating on an implementor of tokio::io::{AsyncReadExt, AsyncWriteExt}. //! //! # Example //! //! ```no_run //! use std::env; //! use tokio::net::UnixStream; //! use greetd_ipc::{Request, Response}; //! use greetd_ipc::codec::TokioCodec; //! //! #[tokio::main(flavor = "current_thread")] //! async fn main() -> Result<(), Box> { //! let mut stream = UnixStream::connect(env::var("GREETD_SOCK")?).await?; //! Request::CreateSession { username: "john".to_string() }.write_to(&mut stream).await?; //! let resp = Response::read_from(&mut stream).await?; //! Ok(()) //! } //! ``` use crate::{codec::Error, Request, Response}; use async_trait::async_trait; use tokio::io::{AsyncReadExt, AsyncWriteExt}; /// Reader/writer implementation over tokio::io::{AsyncReadExt, AsyncWriteExt}. #[async_trait] pub trait TokioCodec { async fn read_from( stream: &mut T, ) -> Result where Self: std::marker::Sized; async fn write_to( &self, stream: &mut T, ) -> Result<(), Error>; } #[async_trait] impl TokioCodec for Request { async fn read_from( stream: &mut T, ) -> Result { let mut len_bytes = [0; 4]; stream .read_exact(&mut len_bytes) .await .map_err(|e| match e.kind() { std::io::ErrorKind::UnexpectedEof => Error::Eof, _ => e.into(), })?; let len = u32::from_ne_bytes(len_bytes); let mut body_bytes = vec![0; len as usize]; stream.read_exact(&mut body_bytes).await?; let body = serde_json::from_slice(&body_bytes)?; Ok(body) } async fn write_to( &self, stream: &mut T, ) -> Result<(), Error> { let body_bytes = serde_json::to_vec(self)?; let len_bytes = (body_bytes.len() as u32).to_ne_bytes(); stream.write_all(&len_bytes).await?; stream.write_all(&body_bytes).await?; Ok(()) } } #[async_trait] impl TokioCodec for Response { async fn read_from( stream: &mut T, ) -> Result { let mut len_bytes = [0; 4]; stream .read_exact(&mut len_bytes) .await .map_err(|e| match e.kind() { std::io::ErrorKind::UnexpectedEof => Error::Eof, _ => e.into(), })?; let len = u32::from_ne_bytes(len_bytes); let mut body_bytes = vec![0; len as usize]; stream.read_exact(&mut body_bytes).await?; let body = serde_json::from_slice(&body_bytes)?; Ok(body) } async fn write_to( &self, stream: &mut T, ) -> Result<(), Error> { let body_bytes = serde_json::to_vec(self)?; let len_bytes = (body_bytes.len() as u32).to_ne_bytes(); stream.write_all(&len_bytes).await?; stream.write_all(&body_bytes).await?; Ok(()) } } greetd_ipc-0.9.0/src/lib.rs000064400000000000000000000121261046102023000136440ustar 00000000000000//! # `greetd` IPC protocol library //! //! This library implements the [greetd](https://git.sr.ht/~kennylevinsen/greetd) IPC protocol. //! //! The library exposes a [Request](enum.Request.html) and a //! [Response](enum.Response.html) enum, representing the valid protocol //! messages. Furthermore, codec implementations are available to serialize //! these to/from both sync and async readers/writers. The availability of //! these are controlled by feature flags. //! //! Additional types are part of the different request and response values. //! //! See `agreety` for a simple example use of this library. //! //! # Format //! //! The message format is as follows: //! //! ```text //! +----------+-------------------+ //! | len: u32 | JSON payload: str | //! +----------+-------------------+ //! ``` //! //! Length is in native byte-order. The JSON payload is a variant of the //! Request or Response enums. //! //! # Request and response types //! //! See [Request](enum.Request.html) and [Response](enum.Response.html) for //! information about the request and response types, as well as their //! serialization. //! use serde::{Deserialize, Serialize}; #[cfg(feature = "codec")] #[cfg_attr(docsrs, doc(cfg(feature = "codec")))] pub mod codec; /// A request from a greeter to greetd. The request type is internally tagged /// with the"type" field, with the type written in snake_case. /// /// Example serialization: /// /// ```json /// { /// "type": "create_session", /// "username": "bob" /// } /// ``` #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] #[serde(tag = "type")] pub enum Request { /// CreateSession initiates a login attempt for the given user. /// CreateSession returns either a Response::AuthMessage, /// Response::Success or Response::Failure. /// /// If an auth message is returned, it should be answered with a /// Request::PostAuthMessageResponse. If a success is returned, the session /// can then be started with Request::StartSession. /// /// If a login flow needs to be aborted at any point, send /// Request::CancelSession. Note that the session is cancelled /// automatically on error. CreateSession { username: String }, /// PostAuthMessageResponse responds to the last auth message, and returns /// either a Response::AuthMessage, Response::Success or Response::Failure. /// /// If an auth message is returned, it should be answered with a /// Request::PostAuthMessageResponse. If a success is returned, the session /// can then be started with Request::StartSession. PostAuthMessageResponse { response: Option }, /// Start a successfully logged in session. This will fail if the session /// has pending messages or has encountered an error. StartSession { cmd: Vec, #[serde(default)] env: Vec, }, /// Cancel a session. This can only be done if the session has not been /// started. Cancel does not have to be called if an error has been /// encountered in its setup or login flow. CancelSession, } /// An error type for Response::Error. Serialized as snake_case. #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum ErrorType { /// A generic error. See the error description for more details. Error, /// An error caused by failed authentication. AuthError, } /// A message type for a Response::AuthMessage. Serialized as snake_case. #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum AuthMessageType { /// A question whose answer should be visible during input. Visible, /// A question whose answer should be kept secret during input. Secret, /// An information message. Info, /// An error message. Error, } /// A response from greetd to a greeter. The request type is internally tagged /// with the"type" field, with the type written in snake_case. /// /// Example serialization: /// /// ```json /// { /// "type": "auth_message", /// "message": "Password:", /// "message_type": "secret" /// } /// ``` #[derive(Debug, Deserialize, Serialize)] #[serde(tag = "type")] #[serde(rename_all = "snake_case")] pub enum Response { /// The request was successful. Success, /// The request failed. See the type and/or description for more /// information about this failure. Error { error_type: ErrorType, description: String, }, /// An authentication message needs to be answered to continue through the /// authentication flow. /// /// An authentication message can consist of anything. While it will /// commonly just be a request for the users' password, it could also ask /// for TOTP codes, or whether or not you felt sad when Littlefoot's mother /// died in the original "Land Before Time". It is therefore important that /// no assumptions are made about the questions that will be asked, and /// attempts to automatically answer these questions should not be made. AuthMessage { auth_message_type: AuthMessageType, auth_message: String, }, }