/* * meli - conf module * * Copyright 2019 Manos Pitsidianakis * * This file is part of meli. * * meli is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * meli is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ //! Configuration for composing email. use std::collections::HashMap; use melib::{conf::ActionFlag, email::HeaderName}; use serde::{de, Deserialize, Deserializer}; use super::{ default_vals::{ask, false_val, none, true_val}, deserializers::non_empty_string, }; /// Settings for writing and sending new e-mail #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct ComposingSettings { /// A command to pipe new emails to /// Required pub send_mail: SendMail, /// Command to launch editor. Can have arguments. Draft filename is given as /// the last argument. If it's missing, the environment variable $EDITOR is /// looked up. #[serde( default = "none", alias = "editor-command", alias = "editor-cmd", alias = "editor_cmd" )] pub editor_command: Option, /// Embedded editor (for terminal interfaces) instead of forking and /// waiting. #[serde(default = "false_val", alias = "embed")] pub embedded_pty: bool, /// Set "format=flowed" in plain text attachments. /// Default: true #[serde(default = "true_val", alias = "format-flowed")] pub format_flowed: bool, /// Set User-Agent /// Default: empty #[serde(default = "true_val", alias = "insert_user_agent")] pub insert_user_agent: bool, /// Set default header values for new drafts /// Default: empty #[serde(default, alias = "default-header-values")] pub default_header_values: HashMap, /// Wrap header preamble when editing a draft in an editor. This allows you /// to write non-plain text email without the preamble creating syntax /// errors. They are stripped when you return from the editor. The /// values should be a two element array of strings, a prefix and suffix. /// Default: None #[serde(default, alias = "wrap-header-preamble")] pub wrap_header_preamble: Option<(String, String)>, /// Store sent mail after successful submission. This setting is meant to be /// disabled for non-standard behaviour in gmail, which auto-saves sent /// mail on its own. Default: true #[serde(default = "true_val")] pub store_sent_mail: bool, /// The attribution line appears above the quoted reply text. /// The format specifiers for the replied address are: /// - `%+f` — the sender's name and email address. /// - `%+n` — the sender's name (or email address, if no name is included). /// - `%+a` — the sender's email address. /// The format string is passed to strftime(3) with the replied envelope's /// date. Default: "On %a, %0e %b %Y %H:%M, %+f wrote:%n" #[serde(default = "none")] pub attribution_format_string: Option, /// Whether the strftime call for the attribution string uses the POSIX /// locale instead of the user's active locale /// Default: true #[serde(default = "true_val")] pub attribution_use_posix_locale: bool, /// Forward emails as attachment? (Alternative is inline) /// Default: ask #[serde(default = "ask", alias = "forward-as-attachment")] pub forward_as_attachment: ActionFlag, /// Alternative lists of reply prefixes (etc. ["Re:", "RE:", ...]) to strip /// Default: `["Re:", "RE:", "Fwd:", "Fw:", "回复:", "回覆:", "SV:", "Sv:", /// "VS:", "Antw:", "Doorst:", "VS:", "VL:", "REF:", "TR:", "TR:", "AW:", /// "WG:", "ΑΠ:", "Απ:", "απ:", "ΠΡΘ:", "Πρθ:", "πρθ:", "ΣΧΕΤ:", "Σχετ:", /// "σχετ:", "ΠΡΘ:", "Πρθ:", "πρθ:", "Vá:", "Továbbítás:", "R:", "I:", /// "RIF:", "FS:", "BLS:", "TRS:", "VS:", "VB:", "RV:", "RES:", "Res", /// "ENC:", "Odp:", "PD:", "YNT:", "İLT:", "ATB:", "YML:"]` #[serde(default, alias = "reply-prefix-list-to-strip")] pub reply_prefix_list_to_strip: Option>, /// The prefix to use in reply subjects. The de facto prefix is "Re:". #[serde(default = "res", alias = "reply-prefix")] pub reply_prefix: String, /// Custom `compose-hooks`. #[serde(default, alias = "custom-compose-hooks")] pub custom_compose_hooks: Vec, /// Disabled `compose-hooks`. #[serde(default, alias = "disabled-compose-hooks")] pub disabled_compose_hooks: Vec, } impl Default for ComposingSettings { fn default() -> Self { Self { send_mail: SendMail::ShellCommand("false".into()), editor_command: None, embedded_pty: false, format_flowed: true, insert_user_agent: true, default_header_values: HashMap::default(), store_sent_mail: true, wrap_header_preamble: None, attribution_format_string: None, attribution_use_posix_locale: true, forward_as_attachment: ActionFlag::Ask, reply_prefix_list_to_strip: None, reply_prefix: res(), custom_compose_hooks: vec![], disabled_compose_hooks: vec![], } } } fn res() -> String { "Re:".to_string() } macro_rules! named_unit_variant { ($variant:ident) => { pub mod $variant { pub fn serialize(serializer: S) -> Result where S: serde::Serializer, { serializer.serialize_str(stringify!($variant)) } pub fn deserialize<'de, D>(deserializer: D) -> Result<(), D::Error> where D: serde::Deserializer<'de>, { struct V; impl<'de> serde::de::Visitor<'de> for V { type Value = (); fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(concat!("\"", stringify!($variant), "\"")) } fn visit_str(self, value: &str) -> Result { if value == stringify!($variant) { Ok(()) } else { Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) } } } deserializer.deserialize_str(V) } } }; } pub mod strings { named_unit_variant!(server_submission); } #[derive(Clone, Debug, Serialize)] #[serde(untagged)] pub enum SendMail { #[cfg(feature = "smtp")] Smtp(melib::smtp::SmtpServerConf), #[serde(with = "strings::server_submission")] ServerSubmission, ShellCommand(String), } /// Shell command compose hooks (See /// [`crate::mail::compose::hooks::Hook`]) #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct ComposeHook { #[serde(deserialize_with = "non_empty_string")] name: String, #[serde(deserialize_with = "non_empty_string")] command: String, } impl From for crate::mail::hooks::Hook { fn from(c: ComposeHook) -> Self { Self::new_shell_command(c.name.into(), c.command) } } const SENDMAIL_ERR_HELP: &str = r#"Invalid `send_mail` value. Here are some valid examples: Use server submission in protocols that support it (JMAP, NNTP) =============================================================== send_mail = "server_submission" Using a shell script ==================== send_mail = "msmtp --read-recipients --read-envelope-from" Direct SMTP connection ====================== send_mail = { hostname = "mail.example.com", port = 587, auth = { type = "auto", password = { type = "raw", value = "hunter2" } }, security = { type = "STARTTLS" } } [composing.send_mail] hostname = "mail.example.com" port = 587 auth = { type = "auto", password = { type = "command_eval", value = "/path/to/password_script.sh" } } security = { type = "TLS", danger_accept_invalid_certs = true } } `send_mail` direct SMTP connection fields: - hostname: text - port: valid port number - envelope_from: text (optional, default is empty), - auth: ... - security: ... (optional, default is "auto") - extensions: ... (optional, default is PIPELINING, CHUNKING, PRDR, 8BITMIME, BINARYMIME, SMTPUTF8, AUTH and DSN_NOTIFY) Possible values for `send_mail.auth`: No authentication: auth = { type = "none" } Regular authentication: Note: `require_auth` and `auth_type` are optional and can be skipped. auth = { type = "auto", username = "...", password = "...", require_auth = true, auth_type = ... } password can be: password = { type = "raw", value = "..." } password = { type = "command_eval", value = "/path/to/password_script.sh" } XOAuth2 authentication: Note: `require_auth` is optional and can be skipped. auth = { type = "xoauth2", token_command = "...", require_auth = true } Possible values for `send_mail.auth.auth_type` when `auth.type` is "auto": auth_type = { plain = false, login = true } Possible values for `send_mail.security`: Note that in all cases field `danger_accept_invalid_certs` is optional and its default value is false. security = "none" security = { type = "auto", danger_accept_invalid_certs = false } security = { type = "STARTTLS", danger_accept_invalid_certs = false } security = { type = "TLS", danger_accept_invalid_certs = false } Possible values for `send_mail.extensions` (All optional and have default values `true`: pipelining chunking 8bitmime prdr binarymime smtputf8 auth dsn_notify: Array of options e.g. ["FAILURE"] "#; impl<'de> Deserialize<'de> for SendMail { fn deserialize(deserializer: D) -> std::result::Result where D: Deserializer<'de>, { #[derive(Deserialize)] #[serde(untagged)] enum SendMailInner { #[cfg(feature = "smtp")] Smtp(melib::smtp::SmtpServerConf), #[serde(with = "strings::server_submission")] ServerSubmission, ShellCommand(String), } match ::deserialize(deserializer) { #[cfg(feature = "smtp")] Ok(SendMailInner::Smtp(v)) => Ok(Self::Smtp(v)), Ok(SendMailInner::ServerSubmission) => Ok(Self::ServerSubmission), Ok(SendMailInner::ShellCommand(v)) => Ok(Self::ShellCommand(v)), Err(_err) => Err(de::Error::custom(SENDMAIL_ERR_HELP)), } } }