Check-in [3c858ed7c4]
Logged in as anonymous
Overview
Comment:reorganize to lib + bin to separate tests, add logging, fix some small stuff
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 3c858ed7c4bad10014f3480fccef1320679f97a891d5ef1ccabbc9d7cad12114
User & Date: arcade on 2026-07-31 10:42:58.113
Other Links: manifest | tags
Context
2026-07-31
10:43
add some tests check-in: dd307557e6 user: arcade tags: trunk
10:42
reorganize to lib + bin to separate tests, add logging, fix some small stuff check-in: 3c858ed7c4 user: arcade tags: trunk
10:38
add deny.toml for checks check-in: f88aa03bf5 user: arcade tags: trunk
Changes
Added src/lib.rs version [9dab3246bf].
104
105
106
107
108
109
110
111

112
113

114
115
116
117
118
119
120
104
105
106
107
108
109
110

111
112

113
114
115
116
117
118
119
120







-
+

-
+







			tg,
			fields,
			address,
		})
	}

	/// Returns id for provided email address
	fn get_id (&self, name: &str) -> Result<&ChatPeerId> {
	fn get_id (&self, name_str: &str) -> Result<&ChatPeerId> {
		// here we need to store String locally to borrow it after
		let mut link = name;
		let mut link = name_str;
		let name: String;
		if let Some(caps) = self.address.captures(link) {
			name = caps["name"].to_string();
			link = &name;
		}
		match self.tg.get(link) {
			Ok(addr) => Ok(addr),
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

24
25
26
27
28
29
30
31
32
33
34

35
36
37

38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
1
2
3
4









5









6




7






8
9
10

11
12
13





























































14
15




-
-
-
-
-
-
-
-
-

-
-
-
-
-
-
-
-
-
+
-
-
-
-

-
-
-
-
-
-
+


-
+


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


//! Simple SMTP-to-Telegram gateway. Can parse email and send them as telegram
//! messages to specified chats, generally you specify which email address is
//! available in configuration, everything else is sent to default address.

mod mail;
mod telegram;
mod utils;

#[cfg(test)]
mod tests;

use crate::mail::MailServer;

use async_compat::Compat;
use just_getopt::{
	OptFlags,
	OptSpecs,
	OptValue,
};
use smol::{
	fs::metadata,
};
use stacked_errors::{
use stacked_errors:: Result;
	Result,
	StackableErr,
	bail,
};

use std::{
	io::Cursor,
	os::unix::fs::PermissionsExt,
	path::Path,
};

// main function stub that executes main code from lib
fn main () -> Result<()> {
	smol::block_on(Compat::new(async {
		async_main().await.unwrap()
		smtp2tg::async_main().await.unwrap()
	}));

	Ok(())
}

/// Actual main function running async with Error propagation support
async fn async_main () -> Result<()> {
	let specs = OptSpecs::new()
		.option("help", "h", OptValue::None)
		.option("help", "help", OptValue::None)
		.option("config", "c", OptValue::Required)
		.option("config", "config", OptValue::Required)
		.flag(OptFlags::OptionsEverywhere);
	let mut args = std::env::args();
	args.next();
	let parsed = specs.getopt(args);
	for u in &parsed.unknown {
		println!("Unknown option: {u}");
	}
	if !(parsed.unknown.is_empty()) || parsed.options_first("help").is_some() {
		println!("SMTP2TG v{}, (C) 2024 - 2026\n\n\
			\t-h|--help\tDisplay this help\n\
			\t-c|--config …\tSet configuration file location.",
			env!("CARGO_PKG_VERSION"));
		return Ok(());
	};
	let config_file = Path::new(if let Some(path) = parsed.options_value_last("config") {
		&path[..]
	} else {
		"smtp2tg.toml"
	});
	if !config_file.exists() {
		bail!("can't read configuration from {config_file:?}");
	};
	{
		let meta = metadata(config_file).await.stack()?;
		if (!0o100600 & meta.permissions().mode()) > 0 {
			bail!("other users can read or write config file {config_file:?}\n\
				File permissions: {:o}", meta.permissions().mode());
		}
	}
	let settings: config::Config = config::Config::builder()
		.set_default("api_gateway", "https://api.telegram.org").stack()?
		.set_default("fields", vec!["date", "from", "subject"]).stack()?
		.set_default("hostname", "smtp.2.tg").stack()?
		.set_default("listen_on", "0.0.0.0:1025").stack()?
		.set_default("unknown", "relay").stack()?
		.set_default("domains", vec!["localhost", hostname::get().stack()?.to_str().expect("Failed to get current hostname")]).stack()?
		.add_source(config::File::from(config_file))
		.build()
		.with_context(|| format!("[{config_file:?}] there was an error reading config\n\
			\tplease consult \"smtp2tg.toml.example\" for details"))?;

	let listen_on = settings.get_string("listen_on").stack()?;
	let server_name = settings.get_string("hostname").stack()?;
	let core = MailServer::new(settings)?;
	let mut server = mailin_embedded::Server::new(core);

	server.with_name(server_name)
		.with_ssl(mailin_embedded::SslConfig::None).unwrap()
		.with_addr(listen_on).unwrap();
	server.serve().unwrap();

	Ok(())
}
Deleted src/tests.rs version [e383e9fd77].
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15


16
17
18



19
20
21
22
23
24
25
26
27

28
29
30
31
32
33
1
2
3
4
5
6
7
8
9
10
11
12
13


14
15
16
17

18
19
20
21
22
23
24
25
26
27
28

29
30
31
32
33
34
35













-
-
+
+


-
+
+
+








-
+






use crate::Cursor;

use std::borrow::Cow;

use html_escape::encode_text;
use lazy_static::lazy_static;
use regex::Regex;
use stacked_errors::{
	bail,
	Result,
};

lazy_static! {
	pub static ref RE_DOMAIN: Regex = Regex::new(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$").unwrap();
	pub static ref RE_CLOSING: Regex = Regex::new(r"</[ \t]*(pre|code)[ \t]*>").unwrap();
	pub static ref RE_DOMAIN: Regex = Regex::new(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$").expect("Invalid domain regex");
	pub static ref RE_CLOSING: Regex = Regex::new(r"</[ \t]*(pre|code)[ \t]*>").expect("Invalid cloding tag regex");
}

/// `Attachment` object to store number attachment data and corresponding file name
/// Stores binary attachment data and metadata for Telegram messages.
/// The data is wrapped in a `Cursor<Vec<u8>>` for efficient streaming,
/// while `name` holds the filename or display name of the attachment.
#[derive(Debug)]
pub struct Attachment {
	pub data: Cursor<Vec<u8>>,
	pub name: String,
}

/// Pass any text here to be validated as not breaking from Telegram preformatted blocks
/// escape all HTML chars afterwards
pub fn validate (text: &str) -> Result<Cow<'_, str>> {
pub fn validate <'a>(text: &'a str) -> Result<Cow<'a, str>> {
	if RE_CLOSING.is_match(text) {
		bail!("Telegram closing tag found.");
	} else {
		Ok(encode_text(text))
	}
}