smtp_client.rs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. use std::sync::Arc;
  2. use rustls_pki_types::ServerName;
  3. use tokio::io::{AsyncWriteExt, AsyncReadExt};
  4. use tokio::net::TcpStream;
  5. use std::convert::TryFrom;
  6. use std::error::Error;
  7. use anyhow::anyhow;
  8. use base64::encode;
  9. use lettre::{Address, Message};
  10. use crate::config::Config;
  11. #[cfg(not(target_os = "wasi"))]
  12. use native_tls::{TlsConnector, TlsStream};
  13. #[cfg(not(target_os = "wasi"))]
  14. use std::io::{Read, Write};
  15. #[cfg(not(target_os = "wasi"))]
  16. use std::net::TcpStream as StdTcpStream;
  17. #[cfg(target_os = "wasi")]
  18. use tokio_rustls::rustls::{ClientConfig, RootCertStore};
  19. #[cfg(target_os = "wasi")]
  20. use tokio_rustls::client::TlsStream;
  21. #[cfg(target_os = "wasi")]
  22. async fn send_command_tcp(stream: &mut TcpStream, command: &str) -> anyhow::Result<()> {
  23. stream.write_all(command.as_bytes()).await;
  24. let mut buffer = [0; 1024];
  25. stream.read(&mut buffer).await;
  26. println!("Server: {}", String::from_utf8_lossy(&buffer));
  27. Ok(())
  28. }
  29. #[cfg(not(target_os = "wasi"))]
  30. async fn send_command_tcp(stream: &mut StdTcpStream, command: &str) -> anyhow::Result<()> {
  31. stream.write_all(command.as_bytes())?;
  32. let mut buffer = [0; 1024];
  33. stream.read(&mut buffer)?;
  34. println!("Server: {}", String::from_utf8_lossy(&buffer));
  35. Ok(())
  36. }
  37. #[cfg(target_os = "wasi")]
  38. async fn send_command_bytes(stream: &mut tokio_rustls::client::TlsStream<TcpStream>, command: Vec<u8>) -> anyhow::Result<()> {
  39. stream.write_all(&*command).await?;
  40. println!("SMTP server: {:?}", read_response(stream).await?);
  41. Ok(())
  42. }
  43. #[cfg(not(target_os = "wasi"))]
  44. async fn send_command_bytes(stream: &mut TlsStream<StdTcpStream>, command: Vec<u8>) -> anyhow::Result<()> {
  45. stream.write_all(&*command)?;
  46. println!("SMTP server: {:?}", read_response(stream).await?);
  47. Ok(())
  48. }
  49. #[cfg(target_os = "wasi")]
  50. async fn send_command(stream: &mut tokio_rustls::client::TlsStream<TcpStream>, command: &str) -> anyhow::Result<()> {
  51. stream.write_all(command.as_bytes()).await?;
  52. println!("SMTP server: {:?}", read_response(stream).await?);
  53. Ok(())
  54. }
  55. #[cfg(not(target_os = "wasi"))]
  56. async fn send_command(stream: &mut TlsStream<StdTcpStream>, command: &str) -> anyhow::Result<()> {
  57. stream.write_all(command.as_bytes())?;
  58. println!("SMTP server: {:?}", read_response(stream).await?);
  59. Ok(())
  60. }
  61. #[cfg(target_os = "wasi")]
  62. async fn read_response(stream: &mut tokio_rustls::client::TlsStream<TcpStream>) -> anyhow::Result<String>{
  63. let mut buffer = [0; 1024];
  64. match stream.read(&mut buffer).await{
  65. Ok(_) => {
  66. Ok(String::from_utf8_lossy(&buffer).to_string())
  67. }
  68. Err(_) => {
  69. Ok("".to_string())
  70. }
  71. }
  72. }
  73. #[cfg(not(target_os = "wasi"))]
  74. async fn read_response(stream: &mut TlsStream<StdTcpStream>) -> anyhow::Result<String>{
  75. let mut buffer = [0; 1024];
  76. match stream.read(&mut buffer){
  77. Ok(_) => {
  78. Ok(String::from_utf8_lossy(&buffer).to_string())
  79. }
  80. Err(_) => {
  81. Ok("".to_string())
  82. }
  83. }
  84. }
  85. #[cfg(target_os = "wasi")]
  86. async fn login() -> Result<tokio_rustls::client::TlsStream<TcpStream>, Box<dyn std::error::Error>> {
  87. let mut stream = tokio::net::TcpStream::connect(
  88. &(Config::global().smtp_domain.clone(), Config::global().smtp_port.clone() as u16)
  89. ).await?;
  90. let mut buffer = [0; 1024];
  91. stream.read(&mut buffer).await?;
  92. println!("Server: {:?}", String::from_utf8(Vec::from(buffer)));
  93. // Send EHLO command
  94. send_command_tcp(&mut stream, "EHLO localhost\r\n").await;
  95. // Start TLS
  96. send_command_tcp(&mut stream, "STARTTLS\r\n").await;
  97. // Wrap the stream with TLS
  98. let mut root_cert_store = RootCertStore::empty();
  99. root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
  100. let config = ClientConfig::builder()
  101. .with_root_certificates(root_cert_store)
  102. .with_no_client_auth();
  103. let connector = tokio_rustls::TlsConnector::from(Arc::new(config));
  104. let dnsname = ServerName::try_from(Config::global().smtp_domain.clone()).unwrap();
  105. let mut stream = connector.connect(dnsname, stream).await?;
  106. // Send EHLO command again after establishing TLS
  107. send_command(&mut stream, "EHLO localhost\r\n").await;
  108. // Authenticate using LOGIN method
  109. send_command(&mut stream, "AUTH LOGIN\r\n").await;
  110. send_command(&mut stream, &(encode(Config::global().username.lock().unwrap().clone()) + "\r\n")).await;
  111. send_command(&mut stream, &(encode(Config::global().password.lock().unwrap().clone()) + "\r\n")).await;
  112. Ok(stream)
  113. }
  114. #[cfg(not(target_os = "wasi"))]
  115. async fn login() -> Result<TlsStream<StdTcpStream>, Box<dyn std::error::Error>> {
  116. let connector = TlsConnector::new().unwrap();
  117. let mut stream = StdTcpStream::connect(format!("{}:{}", Config::global().smtp_domain.clone(), Config::global().smtp_port.clone() as u16)).unwrap();
  118. let mut buffer = [0; 1024];
  119. stream.read(&mut buffer).unwrap();
  120. println!("Server: {:?}", String::from_utf8(Vec::from(buffer)));
  121. // Send EHLO command
  122. send_command_tcp(&mut stream, "EHLO localhost\r\n").await?;
  123. // Start TLS
  124. send_command_tcp(&mut stream, "STARTTLS\r\n").await?;
  125. // Wrap the stream with TLS
  126. let mut stream = connector.connect(&*Config::global().smtp_domain.clone(), stream).unwrap();
  127. // Send EHLO command again after establishing TLS
  128. send_command(&mut stream, "EHLO localhost\r\n").await?;
  129. // Authenticate using LOGIN method
  130. send_command(&mut stream, "AUTH LOGIN\r\n").await?;
  131. send_command(&mut stream, &(encode(Config::global().username.lock().unwrap().clone()) + "\r\n")).await?;
  132. send_command(&mut stream, &(encode(Config::global().password.lock().unwrap().clone()) + "\r\n")).await?; // TODO use Engine::encode()
  133. Ok(stream)
  134. }
  135. #[cfg(target_os = "wasi")]
  136. async fn logout(mut stream: &mut tokio_rustls::client::TlsStream<TcpStream>) -> anyhow::Result<()>{
  137. // Send the QUIT command to end the session
  138. send_command(&mut stream, "QUIT\r\n").await; // TODO maybe '?'
  139. // Close the connection
  140. stream.shutdown().await?;
  141. Ok(())
  142. }
  143. #[cfg(not(target_os = "wasi"))]
  144. async fn logout(mut stream: &mut TlsStream<StdTcpStream>) -> anyhow::Result<()>{
  145. // Send the QUIT command to end the session
  146. send_command(&mut stream, "QUIT\r\n").await?;
  147. // Close the connection
  148. stream.shutdown()?;
  149. Ok(())
  150. }
  151. pub async fn send_email(email: Message) -> anyhow::Result<()>{
  152. let mut stream= match login().await{
  153. Ok(stream) => {stream}
  154. Err(_) => {return Err(anyhow!("Unable to establish connection with SMTP server"))}
  155. };
  156. // Specify the sender
  157. let from = match email.envelope().from() {
  158. None => {return Err(anyhow!("Wrong sender address"))}
  159. Some(from) => {from}
  160. };
  161. send_command(&mut stream, format!("MAIL FROM:<{}@{}>\r\n",
  162. from.user(),
  163. from.domain())
  164. .to_owned().as_str()).await?;
  165. // Specify the recipient
  166. let recipients = email.envelope().to();
  167. for recipient in recipients {
  168. send_command(&mut stream, format!("RCPT TO:<{}@{}>\r\n",
  169. recipient.user(),
  170. recipient.domain()
  171. ).to_owned().as_str()).await?;
  172. }
  173. // Send the DATA command to start the message content
  174. send_command(&mut stream, "DATA\r\n").await?;
  175. // Send the message content
  176. let mut email = email.formatted();
  177. email.append(&mut Vec::from("\r\n.\r\n".as_bytes()));
  178. send_command_bytes(&mut stream, email).await?;
  179. logout(&mut stream).await?;
  180. Ok(())
  181. }