1
/*
2
 * This file is part of mailpot
3
 *
4
 * Copyright 2020 - Manos Pitsidianakis
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 */
19

            
20
use std::{
21
    io::{Read, Write},
22
    os::unix::fs::PermissionsExt,
23
    path::{Path, PathBuf},
24
};
25

            
26
use chrono::prelude::*;
27

            
28
use super::errors::*;
29

            
30
/// How to send e-mail.
31
11
#[derive(Debug, Serialize, Deserialize, Clone)]
32
#[serde(tag = "type", content = "value")]
33
pub enum SendMail {
34
    /// A `melib` configuration for talking to an SMTP server.
35
4
    Smtp(melib::smtp::SmtpServerConf),
36
    /// A plain shell command passed to `sh -c` with the e-mail passed in the
37
    /// stdin.
38
2
    ShellCommand(String),
39
}
40

            
41
/// The configuration for the mailpot database and the mail server.
42
17
#[derive(Debug, Serialize, Deserialize, Clone)]
43
pub struct Configuration {
44
    /// How to send e-mail.
45
    pub send_mail: SendMail,
46
    /// The location of the sqlite3 file.
47
6
    pub db_path: PathBuf,
48
    /// The directory where data are stored.
49
6
    pub data_path: PathBuf,
50
    /// Instance administrators (List of e-mail addresses). Optional.
51
    #[serde(default)]
52
6
    pub administrators: Vec<String>,
53
}
54

            
55
impl Configuration {
56
    /// Create a new configuration value from a given database path value.
57
    ///
58
    /// If you wish to create a new database with this configuration, use
59
    /// [`Connection::open_or_create_db`](crate::Connection::open_or_create_db).
60
    /// To open an existing database, use
61
    /// [`Database::open_db`](crate::Connection::open_db).
62
    pub fn new(db_path: impl Into<PathBuf>) -> Self {
63
        let db_path = db_path.into();
64
        Self {
65
            send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
66
            data_path: db_path
67
                .parent()
68
                .map(Path::to_path_buf)
69
                .unwrap_or_else(|| db_path.clone()),
70
            administrators: vec![],
71
            db_path,
72
        }
73
    }
74

            
75
    /// Deserialize configuration from TOML file.
76
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
77
        let path = path.as_ref();
78
        let mut s = String::new();
79
        let mut file = std::fs::File::open(path)?;
80
        file.read_to_string(&mut s)?;
81
        let config: Self = toml::from_str(&s).context(format!(
82
            "Could not parse configuration file `{}` succesfully: ",
83
            path.display()
84
        ))?;
85

            
86
        Ok(config)
87
    }
88

            
89
    /// The saved data path.
90
    pub fn data_directory(&self) -> &Path {
91
        self.data_path.as_path()
92
    }
93

            
94
    /// The sqlite3 database path.
95
    pub fn db_path(&self) -> &Path {
96
        self.db_path.as_path()
97
    }
98

            
99
    /// Save message to a custom path.
100
    pub fn save_message_to_path(&self, msg: &str, mut path: PathBuf) -> Result<PathBuf> {
101
        if path.is_dir() {
102
            let now = Local::now().timestamp();
103
            path.push(format!("{}-failed.eml", now));
104
        }
105

            
106
        debug_assert!(path != self.db_path());
107
        let mut file = std::fs::File::create(&path)?;
108
        let metadata = file.metadata()?;
109
        let mut permissions = metadata.permissions();
110

            
111
        permissions.set_mode(0o600); // Read/write for owner only.
112
        file.set_permissions(permissions)?;
113
        file.write_all(msg.as_bytes())?;
114
        file.flush()?;
115
        Ok(path)
116
    }
117

            
118
    /// Save message to the data directory.
119
    pub fn save_message(&self, msg: String) -> Result<PathBuf> {
120
        self.save_message_to_path(&msg, self.data_directory().to_path_buf())
121
    }
122

            
123
    /// Serialize configuration to a TOML string.
124
5
    pub fn to_toml(&self) -> String {
125
5
        toml::Value::try_from(self)
126
            .expect("Could not serialize config to TOML")
127
            .to_string()
128
5
    }
129
}