7620f854a7 2024-05-21 1: use anyhow::Result;
7620f854a7 2024-05-21 2: use async_std::task;
7620f854a7 2024-05-21 3: use samotop::{
7620f854a7 2024-05-21 4: mail::{
7620f854a7 2024-05-21 5: Builder,
7620f854a7 2024-05-21 6: DebugService,
7620f854a7 2024-05-21 7: MailDir,
7620f854a7 2024-05-21 8: Name
7620f854a7 2024-05-21 9: },
51adce1e7e 2024-05-22 10: smtp::{
51adce1e7e 2024-05-22 11: SmtpParser,
51adce1e7e 2024-05-22 12: Prudence,
51adce1e7e 2024-05-22 13: },
7620f854a7 2024-05-21 14: };
7620f854a7 2024-05-21 15: use telegram_bot::{
7620f854a7 2024-05-21 16: Api,
31aec3c4b0 2024-05-23 17: MessageOrChannelPost,
7620f854a7 2024-05-21 18: ParseMode,
7620f854a7 2024-05-21 19: SendMessage,
7620f854a7 2024-05-21 20: UserId,
7620f854a7 2024-05-21 21: };
7620f854a7 2024-05-21 22:
7620f854a7 2024-05-21 23: use std::{
7620f854a7 2024-05-21 24: borrow::Cow,
61238a3618 2024-05-22 25: collections::{
61238a3618 2024-05-22 26: HashMap,
61238a3618 2024-05-22 27: HashSet,
61238a3618 2024-05-22 28: },
7620f854a7 2024-05-21 29: io::Read,
da7fc7983d 2024-05-23 30: os::unix::fs::{
da7fc7983d 2024-05-23 31: FileTypeExt,
da7fc7983d 2024-05-23 32: PermissionsExt,
da7fc7983d 2024-05-23 33: },
7620f854a7 2024-05-21 34: path::{
7620f854a7 2024-05-21 35: Path,
7620f854a7 2024-05-21 36: PathBuf
7620f854a7 2024-05-21 37: },
7620f854a7 2024-05-21 38: time::Duration,
7620f854a7 2024-05-21 39: vec::Vec,
7620f854a7 2024-05-21 40: };
7620f854a7 2024-05-21 41:
61238a3618 2024-05-22 42: fn address_into_iter<'a>(addr: &'a mail_parser::Address<'a, >) -> impl Iterator<Item = Cow<'a, str>> {
61238a3618 2024-05-22 43: addr.clone().into_list().into_iter().map(|a| a.address.unwrap())
61238a3618 2024-05-22 44: }
7620f854a7 2024-05-21 45:
61238a3618 2024-05-22 46: fn relay_mails(maildir: &Path, core: &TelegramTransport) -> Result<()> {
7620f854a7 2024-05-21 47: let new_dir = maildir.join("new");
7620f854a7 2024-05-21 48:
7620f854a7 2024-05-21 49: std::fs::create_dir_all(&new_dir)?;
7620f854a7 2024-05-21 50:
7620f854a7 2024-05-21 51: let files = std::fs::read_dir(new_dir)?;
7620f854a7 2024-05-21 52: for file in files {
7620f854a7 2024-05-21 53: let file = file?;
7620f854a7 2024-05-21 54: let mut buf = Vec::new();
7620f854a7 2024-05-21 55: std::fs::File::open(file.path())?.read_to_end(&mut buf)?;
7620f854a7 2024-05-21 56:
7620f854a7 2024-05-21 57: task::block_on(async move {
61238a3618 2024-05-22 58: match mail_parser::MessageParser::default().parse(&buf[..]) {
7620f854a7 2024-05-21 59: Some(mail) => {
61238a3618 2024-05-22 60: let mail = mail.clone();
61238a3618 2024-05-22 61:
61238a3618 2024-05-22 62: // Fetching address lists from fields we know
61238a3618 2024-05-22 63: let mut to = HashSet::new();
61238a3618 2024-05-22 64: if let Some(addr) = mail.to() {
61238a3618 2024-05-22 65: let _ = address_into_iter(addr).map(|x| to.insert(x));
61238a3618 2024-05-22 66: };
61238a3618 2024-05-22 67: if let Some(addr) = mail.header("X-Samotop-To") {
61238a3618 2024-05-22 68: match addr {
61238a3618 2024-05-22 69: mail_parser::HeaderValue::Address(addr) => {
61238a3618 2024-05-22 70: let _ = address_into_iter(addr).map(|x| to.insert(x));
61238a3618 2024-05-22 71: },
61238a3618 2024-05-22 72: mail_parser::HeaderValue::Text(text) => {
61238a3618 2024-05-22 73: to.insert(text.clone());
61238a3618 2024-05-22 74: },
61238a3618 2024-05-22 75: _ => {}
61238a3618 2024-05-22 76: }
61238a3618 2024-05-22 77: };
61238a3618 2024-05-22 78:
61238a3618 2024-05-22 79: // Adding all known addresses to recipient list, for anyone else adding default
61238a3618 2024-05-22 80: // Also if list is empty also adding default
da7fc7983d 2024-05-23 81: let mut rcpt: HashSet<&UserId> = HashSet::new();
61238a3618 2024-05-22 82: for item in to {
61238a3618 2024-05-22 83: let item = item.into_owned();
da7fc7983d 2024-05-23 84: match core.recipients.get(&item) {
da7fc7983d 2024-05-23 85: Some(addr) => rcpt.insert(addr),
da7fc7983d 2024-05-23 86: None => {
da7fc7983d 2024-05-23 87: core.debug(format!("Recipient [{}] not found.", &item)).await.unwrap();
da7fc7983d 2024-05-23 88: rcpt.insert(core.recipients.get("_").unwrap())
da7fc7983d 2024-05-23 89: }
da7fc7983d 2024-05-23 90: };
61238a3618 2024-05-22 91: };
61238a3618 2024-05-22 92: if rcpt.is_empty() {
61238a3618 2024-05-22 93: core.debug("No recipient or envelope address.").await.unwrap();
da7fc7983d 2024-05-23 94: rcpt.insert(core.recipients.get("_").unwrap());
61238a3618 2024-05-22 95: };
61238a3618 2024-05-22 96:
61238a3618 2024-05-22 97: // prepating message header
61238a3618 2024-05-22 98: let mut reply: Vec<Cow<str>> = vec![];
61238a3618 2024-05-22 99: if let Some(subject) = mail.subject() {
667b874fdb 2024-05-22 100: reply.push(format!("**Subject:** `{}`", subject).into());
61238a3618 2024-05-22 101: } else if let Some(thread) = mail.thread_name() {
667b874fdb 2024-05-22 102: reply.push(format!("**Thread:** `{}`", thread).into());
61238a3618 2024-05-22 103: }
61238a3618 2024-05-22 104: if let Some(from) = mail.from() {
03fe0265ac 2024-05-22 105: reply.push(format!("**From:** `{:?}`", address_into_iter(from).collect::<Vec<_>>().join(", ")).into());
61238a3618 2024-05-22 106: }
61238a3618 2024-05-22 107: if let Some(sender) = mail.sender() {
9c12e26fb6 2024-05-22 108: reply.push(format!("**Sender:** `{:?}`", address_into_iter(sender).collect::<Vec<_>>().join(", ")).into());
61238a3618 2024-05-22 109: }
61238a3618 2024-05-22 110: reply.push("".into());
61238a3618 2024-05-22 111: let header_size = reply.join("\n").len() + 1;
61238a3618 2024-05-22 112:
61238a3618 2024-05-22 113: let html_parts = mail.html_body_count();
61238a3618 2024-05-22 114: let text_parts = mail.text_body_count();
61238a3618 2024-05-22 115: let attachments = mail.attachment_count();
61238a3618 2024-05-22 116: if html_parts != text_parts {
61238a3618 2024-05-22 117: core.debug(format!("Hm, we have {} HTML parts and {} text parts.", html_parts, text_parts)).await.unwrap();
61238a3618 2024-05-22 118: }
61238a3618 2024-05-22 119: //let mut html_num = 0;
61238a3618 2024-05-22 120: let mut text_num = 0;
61238a3618 2024-05-22 121: let mut file_num = 0;
61238a3618 2024-05-22 122: // let's display first html or text part as body
61238a3618 2024-05-22 123: let mut body = "".into();
7620f854a7 2024-05-21 124: /*
61238a3618 2024-05-22 125: * actually I don't wanna parse that html stuff
61238a3618 2024-05-22 126: if html_parts > 0 {
61238a3618 2024-05-22 127: let text = mail.body_html(0).unwrap();
61238a3618 2024-05-22 128: if text.len() < 4096 - header_size {
61238a3618 2024-05-22 129: body = text;
61238a3618 2024-05-22 130: html_num = 1;
61238a3618 2024-05-22 131: }
61238a3618 2024-05-22 132: };
61238a3618 2024-05-22 133: */
61238a3618 2024-05-22 134: if body == "" && text_parts > 0 {
61238a3618 2024-05-22 135: let text = mail.body_text(0).unwrap();
61238a3618 2024-05-22 136: if text.len() < 4096 - header_size {
61238a3618 2024-05-22 137: body = text;
61238a3618 2024-05-22 138: text_num = 1;
61238a3618 2024-05-22 139: }
7620f854a7 2024-05-21 140: };
667b874fdb 2024-05-22 141: reply.push("```".into());
03fe0265ac 2024-05-22 142: for line in body.lines() {
03fe0265ac 2024-05-22 143: reply.push(line.into());
03fe0265ac 2024-05-22 144: }
667b874fdb 2024-05-22 145: reply.push("```".into());
61238a3618 2024-05-22 146:
31aec3c4b0 2024-05-23 147: // and let's collect all other attachment parts
61238a3618 2024-05-22 148: let mut files_to_send = vec![];
61238a3618 2024-05-22 149: /*
61238a3618 2024-05-22 150: * let's just skip html parts for now, they just duplicate text?
61238a3618 2024-05-22 151: while html_num < html_parts {
61238a3618 2024-05-22 152: files_to_send.push(mail.html_part(html_num).unwrap());
61238a3618 2024-05-22 153: html_num += 1;
61238a3618 2024-05-22 154: }
7620f854a7 2024-05-21 155: */
61238a3618 2024-05-22 156: while text_num < text_parts {
61238a3618 2024-05-22 157: files_to_send.push(mail.text_part(text_num).unwrap());
61238a3618 2024-05-22 158: text_num += 1;
61238a3618 2024-05-22 159: }
61238a3618 2024-05-22 160: while file_num < attachments {
61238a3618 2024-05-22 161: files_to_send.push(mail.attachment(file_num).unwrap());
61238a3618 2024-05-22 162: file_num += 1;
61238a3618 2024-05-22 163: }
61238a3618 2024-05-22 164:
61238a3618 2024-05-22 165: for chat in rcpt {
31aec3c4b0 2024-05-23 166: let base_post = core.send(chat, reply.join("\n")).await.unwrap();
61238a3618 2024-05-22 167: for chunk in &files_to_send {
61238a3618 2024-05-22 168: let data = chunk.contents().to_vec();
61238a3618 2024-05-22 169: let obj = telegram_bot::types::InputFileUpload::with_data(data, "Attachment");
31aec3c4b0 2024-05-23 170: core.sendfile(chat, obj, Some(&base_post)).await.unwrap();
61238a3618 2024-05-22 171: }
61238a3618 2024-05-22 172: }
7620f854a7 2024-05-21 173: },
7620f854a7 2024-05-21 174: None => { core.debug("None mail.").await.unwrap(); },
7620f854a7 2024-05-21 175: };
7620f854a7 2024-05-21 176: });
7620f854a7 2024-05-21 177:
7620f854a7 2024-05-21 178: std::fs::remove_file(file.path())?;
7620f854a7 2024-05-21 179: }
7620f854a7 2024-05-21 180: Ok(())
7620f854a7 2024-05-21 181: }
7620f854a7 2024-05-21 182:
7620f854a7 2024-05-21 183: fn my_prudence() -> Prudence {
7620f854a7 2024-05-21 184: Prudence::default().with_read_timeout(Duration::from_secs(60)).with_banner_delay(Duration::from_secs(1))
7620f854a7 2024-05-21 185: }
7620f854a7 2024-05-21 186:
61238a3618 2024-05-22 187: pub struct TelegramTransport {
7620f854a7 2024-05-21 188: tg: Api,
7620f854a7 2024-05-21 189: recipients: HashMap<String, UserId>,
7620f854a7 2024-05-21 190: }
7620f854a7 2024-05-21 191:
61238a3618 2024-05-22 192: impl TelegramTransport {
da7fc7983d 2024-05-23 193: pub fn new(settings: config::Config) -> TelegramTransport {
da7fc7983d 2024-05-23 194: let tg = Api::new(settings.get_string("api_key")
da7fc7983d 2024-05-23 195: .expect("[smtp2tg.toml] missing \"api_key\" parameter.\n"));
da7fc7983d 2024-05-23 196: let recipients: HashMap<String, UserId> = settings.get_table("recipients")
da7fc7983d 2024-05-23 197: .expect("[smtp2tg.toml] missing table \"recipients\".\n")
da7fc7983d 2024-05-23 198: .into_iter().map(|(a, b)| (a, UserId::new(b.into_int()
da7fc7983d 2024-05-23 199: .expect("[smtp2tg.toml] \"recipient\" table values should be integers.\n")
da7fc7983d 2024-05-23 200: ))).collect();
da7fc7983d 2024-05-23 201: if !recipients.contains_key("_") {
da7fc7983d 2024-05-23 202: eprintln!("[smtp2tg.toml] \"recipient\" table misses \"default_recipient\".\n");
da7fc7983d 2024-05-23 203: panic!("no default recipient");
da7fc7983d 2024-05-23 204: }
61238a3618 2024-05-22 205:
61238a3618 2024-05-22 206: TelegramTransport {
7620f854a7 2024-05-21 207: tg,
7620f854a7 2024-05-21 208: recipients,
61238a3618 2024-05-22 209: }
61238a3618 2024-05-22 210: }
61238a3618 2024-05-22 211:
31aec3c4b0 2024-05-23 212: pub async fn debug<'b, S>(&self, msg: S) -> Result<MessageOrChannelPost>
31aec3c4b0 2024-05-23 213: where S: Into<Cow<'b, str>> {
31aec3c4b0 2024-05-23 214: task::sleep(Duration::from_secs(5)).await;
31aec3c4b0 2024-05-23 215: Ok(self.tg.send(SendMessage::new(self.recipients.get("_").unwrap(), msg)
31aec3c4b0 2024-05-23 216: .parse_mode(ParseMode::Markdown)).await?)
31aec3c4b0 2024-05-23 217: }
31aec3c4b0 2024-05-23 218:
31aec3c4b0 2024-05-23 219: pub async fn send<'b, S>(&self, to: &UserId, msg: S) -> Result<MessageOrChannelPost>
31aec3c4b0 2024-05-23 220: where S: Into<Cow<'b, str>> {
31aec3c4b0 2024-05-23 221: task::sleep(Duration::from_secs(5)).await;
31aec3c4b0 2024-05-23 222: Ok(self.tg.send(SendMessage::new(to, msg)
31aec3c4b0 2024-05-23 223: .parse_mode(ParseMode::Markdown)).await?)
31aec3c4b0 2024-05-23 224: }
31aec3c4b0 2024-05-23 225:
31aec3c4b0 2024-05-23 226: pub async fn sendfile<V>(&self, to: &UserId, chunk: V, basic_mail: Option<&MessageOrChannelPost>) -> Result<()>
31aec3c4b0 2024-05-23 227: where V: Into<telegram_bot::InputFile> {
31aec3c4b0 2024-05-23 228: task::sleep(Duration::from_secs(5)).await;
31aec3c4b0 2024-05-23 229: match basic_mail {
31aec3c4b0 2024-05-23 230: Some(post) => {
31aec3c4b0 2024-05-23 231: self.tg.send(telegram_bot::SendDocument::new(to, chunk).reply_to(post)).await?;
31aec3c4b0 2024-05-23 232: },
31aec3c4b0 2024-05-23 233: None => {
31aec3c4b0 2024-05-23 234: self.tg.send(telegram_bot::SendDocument::new(to, chunk)).await?;
31aec3c4b0 2024-05-23 235: },
31aec3c4b0 2024-05-23 236: };
7620f854a7 2024-05-21 237: Ok(())
7620f854a7 2024-05-21 238: }
7620f854a7 2024-05-21 239: }
7620f854a7 2024-05-21 240:
7620f854a7 2024-05-21 241: #[async_std::main]
7620f854a7 2024-05-21 242: async fn main() {
7620f854a7 2024-05-21 243: let settings: config::Config = config::Config::builder()
7620f854a7 2024-05-21 244: .add_source(config::File::with_name("smtp2tg.toml"))
da7fc7983d 2024-05-23 245: .build()
da7fc7983d 2024-05-23 246: .expect("[smtp2tg.toml] there was an error reading config\n\
da7fc7983d 2024-05-23 247: \tplease consult \"smtp2tg.toml.example\" for details");
7620f854a7 2024-05-21 248:
da7fc7983d 2024-05-23 249: let maildir: PathBuf = settings.get_string("maildir")
da7fc7983d 2024-05-23 250: .expect("[smtp2tg.toml] missing \"maildir\" parameter.\n").into();
da7fc7983d 2024-05-23 251: let listen_on = settings.get_string("listen_on")
da7fc7983d 2024-05-23 252: .expect("[smtp2tg.toml] missing \"listen_on\" parameter.\n");
da7fc7983d 2024-05-23 253: let core = TelegramTransport::new(settings);
7620f854a7 2024-05-21 254: let sink = Builder + Name::new("smtp2tg") + DebugService +
51adce1e7e 2024-05-22 255: my_prudence() + MailDir::new(maildir.clone()).unwrap();
31aec3c4b0 2024-05-23 256:
31aec3c4b0 2024-05-23 257: env_logger::init();
7620f854a7 2024-05-21 258:
7620f854a7 2024-05-21 259: task::spawn(async move {
7620f854a7 2024-05-21 260: loop {
7620f854a7 2024-05-21 261: relay_mails(&maildir, &core).unwrap();
61238a3618 2024-05-22 262: task::sleep(Duration::from_secs(5)).await;
7620f854a7 2024-05-21 263: }
7620f854a7 2024-05-21 264: });
7620f854a7 2024-05-21 265:
7620f854a7 2024-05-21 266: match listen_on.as_str() {
51adce1e7e 2024-05-22 267: "socket" => {
da7fc7983d 2024-05-23 268: let socket_path = "./smtp2tg.sock";
da7fc7983d 2024-05-23 269: match std::fs::symlink_metadata(socket_path) {
da7fc7983d 2024-05-23 270: Ok(metadata) => {
da7fc7983d 2024-05-23 271: if metadata.file_type().is_socket() {
da7fc7983d 2024-05-23 272: std::fs::remove_file(socket_path)
da7fc7983d 2024-05-23 273: .expect("[smtp2tg] failed to remove old socket.\n");
da7fc7983d 2024-05-23 274: } else {
da7fc7983d 2024-05-23 275: eprintln!("[smtp2tg] \"./smtp2tg.sock\" we wanted to use is actually not a socket.\n\
da7fc7983d 2024-05-23 276: [smtp2tg] please check the file and remove it manually.\n");
da7fc7983d 2024-05-23 277: panic!("socket path unavailable");
da7fc7983d 2024-05-23 278: }
da7fc7983d 2024-05-23 279: },
da7fc7983d 2024-05-23 280: Err(err) => {
da7fc7983d 2024-05-23 281: match err.kind() {
da7fc7983d 2024-05-23 282: std::io::ErrorKind::NotFound => {},
da7fc7983d 2024-05-23 283: _ => {
da7fc7983d 2024-05-23 284: eprintln!("{:?}", err);
da7fc7983d 2024-05-23 285: panic!("unhandled file type error");
da7fc7983d 2024-05-23 286: }
da7fc7983d 2024-05-23 287: };
da7fc7983d 2024-05-23 288: }
da7fc7983d 2024-05-23 289: };
da7fc7983d 2024-05-23 290:
51adce1e7e 2024-05-22 291: let sink = sink + samotop::smtp::Lmtp.with(SmtpParser);
da7fc7983d 2024-05-23 292: task::spawn(async move {
da7fc7983d 2024-05-23 293: // Postpone mode change on the socket. I can't actually change
da7fc7983d 2024-05-23 294: // other way, as UnixServer just grabs path, and blocks
da7fc7983d 2024-05-23 295: task::sleep(Duration::from_secs(1)).await;
da7fc7983d 2024-05-23 296: std::fs::set_permissions(socket_path, std::fs::Permissions::from_mode(0o777)).unwrap();
da7fc7983d 2024-05-23 297: });
da7fc7983d 2024-05-23 298: samotop::server::UnixServer::on(socket_path)
51adce1e7e 2024-05-22 299: .serve(sink.build()).await.unwrap();
51adce1e7e 2024-05-22 300: },
51adce1e7e 2024-05-22 301: _ => {
51adce1e7e 2024-05-22 302: let sink = sink + samotop::smtp::Esmtp.with(SmtpParser);
51adce1e7e 2024-05-22 303: samotop::server::TcpServer::on(listen_on)
51adce1e7e 2024-05-22 304: .serve(sink.build()).await.unwrap();
51adce1e7e 2024-05-22 305: },
61238a3618 2024-05-22 306: };
7620f854a7 2024-05-21 307: }