#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
use secrecy::SecretString;
use std::ffi::OsStr;
use std::path::PathBuf;
mod assuan;
mod error;
#[cfg(unix)]
pub mod unix;
pub use error::{Error, GpgError};
pub type Result<T> = std::result::Result<T, Error>;
pub struct PassphraseInput<'a> {
binary: PathBuf,
required: Option<&'a str>,
title: Option<&'a str>,
description: Option<&'a str>,
error: Option<&'a str>,
prompt: Option<&'a str>,
confirmation: Option<(&'a str, &'a str)>,
ok: Option<&'a str>,
cancel: Option<&'a str>,
timeout: Option<u16>,
#[cfg(unix)]
unix_options: unix::Options<'a>,
}
impl<'a> PassphraseInput<'a> {
pub fn with_default_binary() -> Option<Self> {
Self::with_binary("pinentry")
}
pub fn with_binary<T: AsRef<OsStr>>(binary_name: T) -> Option<Self> {
which::which(binary_name)
.ok()
.map(|binary| PassphraseInput {
binary,
required: None,
title: None,
description: None,
error: None,
prompt: None,
confirmation: None,
ok: None,
cancel: None,
timeout: None,
#[cfg(unix)]
unix_options: unix::Options::default(),
})
}
pub fn required(&mut self, empty_error: &'a str) -> &mut Self {
self.required = Some(empty_error);
self
}
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
self.title = Some(title);
self
}
pub fn with_description(&mut self, description: &'a str) -> &mut Self {
self.description = Some(description);
self
}
pub fn with_error(&mut self, error: &'a str) -> &mut Self {
self.error = Some(error);
self
}
pub fn with_prompt(&mut self, prompt: &'a str) -> &mut Self {
self.prompt = Some(prompt);
self
}
pub fn with_confirmation(
&mut self,
confirmation_prompt: &'a str,
mismatch_error: &'a str,
) -> &mut Self {
self.confirmation = Some((confirmation_prompt, mismatch_error));
self
}
pub fn with_ok(&mut self, ok: &'a str) -> &mut Self {
self.ok = Some(ok);
self
}
pub fn with_cancel(&mut self, cancel: &'a str) -> &mut Self {
self.cancel = Some(cancel);
self
}
pub fn with_timeout(&mut self, timeout: u16) -> &mut Self {
self.timeout = Some(timeout);
self
}
#[cfg(unix)]
pub fn with_unix_options(&mut self, options: unix::Options<'a>) -> &mut Self {
self.unix_options = options;
self
}
pub fn interact(&self) -> Result<SecretString> {
let mut pinentry = assuan::Connection::open(
&self.binary,
#[cfg(unix)]
self.unix_options,
)?;
if let Some(title) = &self.title {
pinentry.send_request("SETTITLE", Some(title))?;
}
if let Some(desc) = &self.description {
pinentry.send_request("SETDESC", Some(desc))?;
}
if let Some(error) = &self.error {
pinentry.send_request("SETERROR", Some(error))?;
}
if let Some(prompt) = &self.prompt {
pinentry.send_request("SETPROMPT", Some(prompt))?;
}
if let Some(ok) = &self.ok {
pinentry.send_request("SETOK", Some(ok))?;
}
if let Some(cancel) = &self.cancel {
pinentry.send_request("SETCANCEL", Some(cancel))?;
}
if let Some((confirmation_prompt, mismatch_error)) = &self.confirmation {
pinentry.send_request("SETREPEAT", Some(confirmation_prompt))?;
pinentry.send_request("SETREPEATERROR", Some(mismatch_error))?;
}
if let Some(timeout) = self.timeout {
pinentry.send_request("SETTIMEOUT", Some(&format!("{}", timeout)))?;
}
loop {
match (pinentry.send_request("GETPIN", None)?, self.required) {
(None, None) => return Ok(String::new().into()),
(Some(passphrase), _) => return Ok(passphrase),
(_, Some(empty_error)) => {
pinentry.send_request("SETERROR", Some(empty_error))?;
}
}
}
}
}
pub struct ConfirmationDialog<'a> {
binary: PathBuf,
title: Option<&'a str>,
ok: Option<&'a str>,
cancel: Option<&'a str>,
not_ok: Option<&'a str>,
timeout: Option<u16>,
#[cfg(unix)]
unix_options: unix::Options<'a>,
}
impl<'a> ConfirmationDialog<'a> {
pub fn with_default_binary() -> Option<Self> {
Self::with_binary("pinentry")
}
pub fn with_binary<T: AsRef<OsStr>>(binary_name: T) -> Option<Self> {
which::which(binary_name)
.ok()
.map(|binary| ConfirmationDialog {
binary,
title: None,
ok: None,
cancel: None,
not_ok: None,
timeout: None,
#[cfg(unix)]
unix_options: unix::Options::default(),
})
}
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
self.title = Some(title);
self
}
pub fn with_ok(&mut self, ok: &'a str) -> &mut Self {
self.ok = Some(ok);
self
}
pub fn with_cancel(&mut self, cancel: &'a str) -> &mut Self {
self.cancel = Some(cancel);
self
}
pub fn with_not_ok(&mut self, not_ok: &'a str) -> &mut Self {
self.not_ok = Some(not_ok);
self
}
pub fn with_timeout(&mut self, timeout: u16) -> &mut Self {
self.timeout = Some(timeout);
self
}
#[cfg(unix)]
pub fn with_unix_options(&mut self, options: unix::Options<'a>) -> &mut Self {
self.unix_options = options;
self
}
pub fn confirm(&self, query: &str) -> Result<bool> {
let mut pinentry = assuan::Connection::open(
&self.binary,
#[cfg(unix)]
self.unix_options,
)?;
pinentry.send_request("SETDESC", Some(query))?;
if let Some(ok) = &self.ok {
pinentry.send_request("SETOK", Some(ok))?;
}
if let Some(cancel) = &self.cancel {
pinentry.send_request("SETCANCEL", Some(cancel))?;
}
if let Some(not_ok) = &self.not_ok {
pinentry.send_request("SETNOTOK", Some(not_ok))?;
}
if let Some(timeout) = self.timeout {
pinentry.send_request("SETTIMEOUT", Some(&format!("{}", timeout)))?;
}
pinentry
.send_request("CONFIRM", None)
.map(|_| true)
.or_else(|e| match (&e, self.not_ok.is_some()) {
(Error::Cancelled, false) => Ok(false),
(Error::Gpg(gpg), true) if gpg.code() == error::GPG_ERR_NOT_CONFIRMED => Ok(false),
_ => Err(e),
})
}
}
pub struct MessageDialog<'a> {
binary: PathBuf,
title: Option<&'a str>,
ok: Option<&'a str>,
timeout: Option<u16>,
#[cfg(unix)]
unix_options: unix::Options<'a>,
}
impl<'a> MessageDialog<'a> {
pub fn with_default_binary() -> Option<Self> {
Self::with_binary("pinentry")
}
pub fn with_binary<T: AsRef<OsStr>>(binary_name: T) -> Option<Self> {
which::which(binary_name).ok().map(|binary| MessageDialog {
binary,
title: None,
ok: None,
timeout: None,
#[cfg(unix)]
unix_options: unix::Options::default(),
})
}
pub fn with_title(&mut self, title: &'a str) -> &mut Self {
self.title = Some(title);
self
}
pub fn with_ok(&mut self, ok: &'a str) -> &mut Self {
self.ok = Some(ok);
self
}
pub fn with_timeout(&mut self, timeout: u16) -> &mut Self {
self.timeout = Some(timeout);
self
}
#[cfg(unix)]
pub fn with_unix_options(&mut self, options: unix::Options<'a>) -> &mut Self {
self.unix_options = options;
self
}
pub fn show_message(&self, message: &str) -> Result<()> {
let mut pinentry = assuan::Connection::open(
&self.binary,
#[cfg(unix)]
self.unix_options,
)?;
pinentry.send_request("SETDESC", Some(message))?;
if let Some(ok) = &self.ok {
pinentry.send_request("SETOK", Some(ok))?;
}
if let Some(timeout) = self.timeout {
pinentry.send_request("SETTIMEOUT", Some(&format!("{}", timeout)))?;
}
pinentry.send_request("MESSAGE", None).map(|_| ())
}
}