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
//! # Queues
21

            
22
use std::borrow::Cow;
23

            
24
use super::*;
25

            
26
/// In-database queues of mail.
27
31
#[derive(Copy, Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
28
#[serde(rename_all = "kebab-case")]
29
pub enum Queue {
30
    /// Messages that have been submitted but not yet processed, await
31
    /// processing in the `maildrop` queue. Messages can be added to the
32
    /// `maildrop` queue even when mailpot is not running.
33
    Maildrop,
34
    /// List administrators may introduce rules for emails to be placed
35
    /// indefinitely in the `hold` queue. Messages placed in the `hold`
36
    /// queue stay there until the administrator intervenes. No periodic
37
    /// delivery attempts are made for messages in the `hold` queue.
38
    Hold,
39
    /// When all the deliverable recipients for a message are delivered, and for
40
    /// some recipients delivery failed for a transient reason (it might
41
    /// succeed later), the message is placed in the `deferred` queue.
42
    Deferred,
43
    /// Invalid received or generated e-mail saved for debug and troubleshooting
44
    /// reasons.
45
    Corrupt,
46
    /// Emails that must be sent as soon as possible.
47
    Out,
48
    /// Error queue
49
    Error,
50
}
51

            
52
impl Queue {
53
    /// Returns the name of the queue used in the database schema.
54
66
    pub fn as_str(&self) -> &'static str {
55
66
        match self {
56
            Self::Maildrop => "maildrop",
57
16
            Self::Hold => "hold",
58
            Self::Deferred => "deferred",
59
            Self::Corrupt => "corrupt",
60
31
            Self::Out => "out",
61
19
            Self::Error => "error",
62
        }
63
66
    }
64
}
65

            
66
/// A queue entry.
67
20
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
68
pub struct QueueEntry {
69
    /// Database primary key.
70
10
    pub pk: i64,
71
    /// Owner queue.
72
10
    pub queue: Queue,
73
    /// Related list foreign key, optional.
74
10
    pub list: Option<i64>,
75
    /// Entry comment, optional.
76
10
    pub comment: Option<String>,
77
    /// Entry recipients in rfc5322 format.
78
10
    pub to_addresses: String,
79
    /// Entry submitter in rfc5322 format.
80
10
    pub from_address: String,
81
    /// Entry subject.
82
10
    pub subject: String,
83
    /// Entry Message-ID in rfc5322 format.
84
10
    pub message_id: String,
85
    /// Message in rfc5322 format as bytes.
86
10
    pub message: Vec<u8>,
87
    /// Unix timestamp of date.
88
10
    pub timestamp: u64,
89
    /// Datetime as string.
90
10
    pub datetime: String,
91
}
92

            
93
impl std::fmt::Display for QueueEntry {
94
21
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
95
21
        write!(fmt, "{:?}", self)
96
21
    }
97
}
98

            
99
impl std::fmt::Debug for QueueEntry {
100
21
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
101
252
        fmt.debug_struct(stringify!(QueueEntry))
102
21
            .field("pk", &self.pk)
103
21
            .field("queue", &self.queue)
104
            .field("list", &self.list)
105
21
            .field("comment", &self.comment)
106
21
            .field("to_addresses", &self.to_addresses)
107
21
            .field("from_address", &self.from_address)
108
21
            .field("subject", &self.subject)
109
21
            .field("message_id", &self.message_id)
110
21
            .field("message length", &self.message.len())
111
            .field(
112
                "message",
113
21
                &format!("{:.15}", String::from_utf8_lossy(&self.message)),
114
            )
115
21
            .field("timestamp", &self.timestamp)
116
21
            .field("datetime", &self.datetime)
117
            .finish()
118
21
    }
119
}
120

            
121
impl QueueEntry {
122
    /// Create new entry.
123
25
    pub fn new(
124
        queue: Queue,
125
        list: Option<i64>,
126
        env: Option<Cow<'_, Envelope>>,
127
        raw: &[u8],
128
        comment: Option<String>,
129
    ) -> Result<Self> {
130
50
        let env = env
131
            .map(Ok)
132
43
            .unwrap_or_else(|| melib::Envelope::from_bytes(raw, None).map(Cow::Owned))?;
133
25
        let now = chrono::offset::Utc::now();
134
25
        Ok(Self {
135
            pk: -1,
136
            list,
137
            queue,
138
25
            comment,
139
25
            to_addresses: env.field_to_to_string(),
140
25
            from_address: env.field_from_to_string(),
141
25
            subject: env.subject().to_string(),
142
25
            message_id: env.message_id().to_string(),
143
25
            message: raw.to_vec(),
144
25
            timestamp: now.timestamp() as u64,
145
25
            datetime: now.to_string(),
146
        })
147
25
    }
148
}
149

            
150
impl Connection {
151
    /// Insert a received email into a queue.
152
30
    pub fn insert_to_queue(&self, mut entry: QueueEntry) -> Result<DbVal<QueueEntry>> {
153
30
        log::trace!("Inserting to queue: {entry}");
154
30
        let mut stmt = self.connection.prepare(
155
            "INSERT INTO queue(which, list, comment, to_addresses, from_address, subject, \
156
             message_id, message, timestamp, datetime) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?) \
157
             RETURNING pk;",
158
        )?;
159
30
        let pk = stmt.query_row(
160
30
            rusqlite::params![
161
30
                entry.queue.as_str(),
162
30
                &entry.list,
163
30
                &entry.comment,
164
30
                &entry.to_addresses,
165
30
                &entry.from_address,
166
30
                &entry.subject,
167
30
                &entry.message_id,
168
30
                &entry.message,
169
30
                &entry.timestamp,
170
30
                &entry.datetime,
171
            ],
172
30
            |row| {
173
30
                let pk: i64 = row.get("pk")?;
174
30
                Ok(pk)
175
30
            },
176
        )?;
177
30
        entry.pk = pk;
178
30
        Ok(DbVal(entry, pk))
179
30
    }
180

            
181
    /// Fetch all queue entries.
182
27
    pub fn queue(&self, queue: Queue) -> Result<Vec<DbVal<QueueEntry>>> {
183
27
        let mut stmt = self
184
            .connection
185
            .prepare("SELECT * FROM queue WHERE which = ?;")?;
186
60
        let iter = stmt.query_map([&queue.as_str()], |row| {
187
33
            let pk = row.get::<_, i64>("pk")?;
188
33
            Ok(DbVal(
189
33
                QueueEntry {
190
                    pk,
191
33
                    queue,
192
33
                    list: row.get::<_, Option<i64>>("list")?,
193
33
                    comment: row.get::<_, Option<String>>("comment")?,
194
33
                    to_addresses: row.get::<_, String>("to_addresses")?,
195
33
                    from_address: row.get::<_, String>("from_address")?,
196
33
                    subject: row.get::<_, String>("subject")?,
197
33
                    message_id: row.get::<_, String>("message_id")?,
198
33
                    message: row.get::<_, Vec<u8>>("message")?,
199
33
                    timestamp: row.get::<_, u64>("timestamp")?,
200
33
                    datetime: row.get::<_, String>("datetime")?,
201
                },
202
                pk,
203
            ))
204
33
        })?;
205

            
206
27
        let mut ret = vec![];
207
60
        for item in iter {
208
33
            let item = item?;
209
33
            ret.push(item);
210
        }
211
27
        Ok(ret)
212
27
    }
213

            
214
    /// Delete queue entries returning the deleted values.
215
9
    pub fn delete_from_queue(&mut self, queue: Queue, index: Vec<i64>) -> Result<Vec<QueueEntry>> {
216
9
        let tx = self.connection.transaction()?;
217

            
218
24
        let cl = |row: &rusqlite::Row<'_>| {
219
15
            Ok(QueueEntry {
220
                pk: -1,
221
15
                queue,
222
15
                list: row.get::<_, Option<i64>>("list")?,
223
15
                comment: row.get::<_, Option<String>>("comment")?,
224
15
                to_addresses: row.get::<_, String>("to_addresses")?,
225
15
                from_address: row.get::<_, String>("from_address")?,
226
15
                subject: row.get::<_, String>("subject")?,
227
15
                message_id: row.get::<_, String>("message_id")?,
228
15
                message: row.get::<_, Vec<u8>>("message")?,
229
15
                timestamp: row.get::<_, u64>("timestamp")?,
230
15
                datetime: row.get::<_, String>("datetime")?,
231
            })
232
15
        };
233
18
        let mut stmt = if index.is_empty() {
234
8
            tx.prepare("DELETE FROM queue WHERE which = ? RETURNING *;")?
235
        } else {
236
1
            tx.prepare("DELETE FROM queue WHERE which = ? AND pk IN rarray(?) RETURNING *;")?
237
        };
238
17
        let iter = if index.is_empty() {
239
8
            stmt.query_map([&queue.as_str()], cl)?
240
        } else {
241
            // Note: A `Rc<Vec<Value>>` must be used as the parameter.
242
1
            let index = std::rc::Rc::new(
243
1
                index
244
                    .into_iter()
245
                    .map(rusqlite::types::Value::from)
246
                    .collect::<Vec<rusqlite::types::Value>>(),
247
            );
248
1
            stmt.query_map(rusqlite::params![queue.as_str(), index], cl)?
249
1
        };
250

            
251
9
        let mut ret = vec![];
252
24
        for item in iter {
253
15
            let item = item?;
254
15
            ret.push(item);
255
        }
256
9
        drop(stmt);
257
18
        tx.commit()?;
258
9
        Ok(ret)
259
9
    }
260
}
261

            
262
#[cfg(test)]
263
mod tests {
264
    use super::*;
265

            
266
    #[test]
267
2
    fn test_queue_delete_array() {
268
        use tempfile::TempDir;
269

            
270
1
        let tmp_dir = TempDir::new().unwrap();
271
1
        let db_path = tmp_dir.path().join("mpot.db");
272
1
        let config = Configuration {
273
1
            send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
274
1
            db_path,
275
1
            data_path: tmp_dir.path().to_path_buf(),
276
1
            administrators: vec![],
277
        };
278

            
279
1
        let mut db = Connection::open_or_create_db(config).unwrap().trusted();
280
6
        for i in 0..5 {
281
5
            db.insert_to_queue(
282
5
                QueueEntry::new(
283
5
                    Queue::Hold,
284
5
                    None,
285
5
                    None,
286
5
                    format!("Subject: testing\r\nMessage-Id: {i}@localhost\r\n\r\nHello\r\n")
287
                        .as_bytes(),
288
5
                    None,
289
                )
290
                .unwrap(),
291
            )
292
5
            .unwrap();
293
        }
294
1
        let entries = db.queue(Queue::Hold).unwrap();
295
1
        assert_eq!(entries.len(), 5);
296
1
        let out_entries = db.delete_from_queue(Queue::Out, vec![]).unwrap();
297
1
        assert_eq!(db.queue(Queue::Hold).unwrap().len(), 5);
298
1
        assert!(out_entries.is_empty());
299
1
        let deleted_entries = db.delete_from_queue(Queue::Hold, vec![]).unwrap();
300
1
        assert_eq!(deleted_entries.len(), 5);
301
1
        assert_eq!(
302
1
            &entries
303
                .iter()
304
                .cloned()
305
                .map(DbVal::into_inner)
306
5
                .map(|mut e| {
307
5
                    e.pk = -1;
308
5
                    e
309
5
                })
310
                .collect::<Vec<_>>(),
311
1
            &deleted_entries
312
        );
313

            
314
6
        for e in deleted_entries {
315
5
            db.insert_to_queue(e).unwrap();
316
        }
317

            
318
1
        let index = db
319
1
            .queue(Queue::Hold)
320
            .unwrap()
321
            .into_iter()
322
            .skip(2)
323
2
            .map(|e| e.pk())
324
            .take(2)
325
            .collect::<Vec<i64>>();
326
1
        let deleted_entries = db.delete_from_queue(Queue::Hold, index).unwrap();
327
1
        assert_eq!(deleted_entries.len(), 2);
328
1
        assert_eq!(db.queue(Queue::Hold).unwrap().len(), 3);
329
2
    }
330
}