@@ -11,9 +11,7 @@ use rand::Rng;
1111use std:: net:: ToSocketAddrs ;
1212use std:: str:: FromStr ;
1313use std:: time:: Duration ;
14-
1514/// Performs the SMTP RCPT TO check for a single email address.
16- /// This attempts to replicate the logic from the Python script's _verify_smtp function.
1715/// Uses lower-level SmtpConnection for command control.
1816///
1917/// # Arguments
@@ -64,18 +62,89 @@ async fn verify_smtp_email(
6462
6563 let helo_name = lettre:: transport:: smtp:: extension:: ClientId :: Domain ( "localhost" . to_string ( ) ) ;
6664
65+ // First attempt: Try without TLS (standard connection)
66+ let connect_result = try_verify_with_connection (
67+ socket_addr,
68+ & helo_name,
69+ & sender_address,
70+ & recipient_address,
71+ email,
72+ domain,
73+ mail_server,
74+ false ,
75+ )
76+ . await ;
77+
78+ // If the first attempt failed with a STARTTLS requirement, retry with TLS
79+ match & connect_result {
80+ Ok ( result) => {
81+ // Check if the error message indicates STARTTLS is needed
82+ let msg = result. message . to_lowercase ( ) ;
83+ if msg. contains ( "starttls" )
84+ || msg. contains ( "tls required" )
85+ || ( msg. contains ( "530" ) && msg. contains ( "5.7.0" ) )
86+ {
87+ tracing:: info!( target: "smtp_task" ,
88+ "Server appears to require STARTTLS, retrying with TLS enabled" ) ;
89+
90+ // Try again with TLS enabled
91+ return try_verify_with_connection (
92+ socket_addr,
93+ & helo_name,
94+ & sender_address,
95+ & recipient_address,
96+ email,
97+ domain,
98+ mail_server,
99+ true ,
100+ )
101+ . await ;
102+ }
103+ }
104+ Err ( e) => {
105+ tracing:: error!( target: "smtp_task" ,
106+ "Error during verification attempt: {}" , e) ;
107+ }
108+ }
109+
110+ connect_result
111+ }
112+
113+ /// Tries to verify an email using a specific connection type (with or without TLS)
114+ async fn try_verify_with_connection (
115+ socket_addr : std:: net:: SocketAddr ,
116+ helo_name : & lettre:: transport:: smtp:: extension:: ClientId ,
117+ sender_address : & Address ,
118+ recipient_address : & Address ,
119+ email : & str ,
120+ domain : & str ,
121+ mail_server : & str ,
122+ use_tls : bool ,
123+ ) -> Result < SmtpVerificationResult > {
124+ let tls_parameters = if use_tls {
125+ Some (
126+ lettre:: transport:: smtp:: client:: TlsParameters :: new ( mail_server. to_string ( ) ) . map_err (
127+ |e| AppError :: SmtpTls ( format ! ( "Failed to create TLS parameters: {}" , e) ) ,
128+ ) ?,
129+ )
130+ } else {
131+ None
132+ } ;
133+
67134 let mut smtp_conn = match SmtpConnection :: connect (
68135 socket_addr,
69136 Some ( CONFIG . smtp_timeout ) ,
70- & helo_name,
71- None ,
137+ helo_name,
138+ tls_parameters . as_ref ( ) ,
72139 None ,
73140 ) {
74141 Ok ( conn) => conn,
75142 Err ( e) => {
76- tracing:: warn!( target: "smtp_task" , "SMTP connection failed for {}: {}" , mail_server, e) ;
77-
78143 let err_string = e. to_string ( ) ;
144+ tracing:: warn!( target: "smtp_task" ,
145+ "SMTP connection failed for {} (TLS={}): {}" ,
146+ mail_server, use_tls, e) ;
147+
79148 if err_string. contains ( "timed out" ) || err_string. contains ( "connection refused" ) {
80149 tracing:: error!( target: "smtp_task" ,
81150 "Port 25 appears to be blocked by your ISP or network. Consider using a different network or VPN." ) ;
@@ -89,33 +158,53 @@ async fn verify_smtp_email(
89158 }
90159 } ;
91160
161+ tracing:: debug!( target: "smtp_task" ,
162+ "Established {} connection to {}:{}" ,
163+ if use_tls { "TLS" } else { "plaintext" } ,
164+ mail_server,
165+ socket_addr. port( ) ) ;
166+
92167 match smtp_conn. command ( Ehlo :: new ( helo_name. clone ( ) ) ) {
93168 Ok ( _) => {
94- tracing:: debug!( target: "smtp_task" , "Initial EHLO successful" ) ;
169+ tracing:: debug!( target: "smtp_task" , "EHLO successful" ) ;
95170 }
96171 Err ( e) => {
97- tracing:: warn!( target: "smtp_task" , "Initial EHLO failed: {}" , e) ;
172+ tracing:: warn!( target: "smtp_task" , "EHLO failed: {}" , e) ;
98173 return Ok ( handle_smtp_error ( & e, mail_server) ) ;
99174 }
100175 }
101176
102- tracing:: debug!( target: "smtp_task" , "SMTP connection established to {}:{}" , mail_server, socket_addr. port( ) ) ;
103-
104177 tracing:: debug!( target: "smtp_task" , "Sending MAIL FROM:<{}>..." , & CONFIG . smtp_sender_email) ;
105178 match smtp_conn. command ( Mail :: new ( Some ( sender_address. clone ( ) ) , vec ! [ ] ) ) {
106179 Ok ( response) => {
107180 if response. is_positive ( ) {
108181 tracing:: debug!( target: "smtp_task" , "MAIL FROM accepted by {}: {:?}" , mail_server, response) ;
109182 } else {
183+ let message = response. message ( ) . collect :: < Vec < & str > > ( ) . join ( " " ) ;
110184 tracing:: error!( target: "smtp_task" ,
111- "SMTP sender '{}' rejected by {}: {:?}" ,
112- & CONFIG . smtp_sender_email, mail_server, response
185+ "SMTP sender '{}' rejected by {}: {} { :?}" ,
186+ & CONFIG . smtp_sender_email, mail_server, response. code ( ) , message
113187 ) ;
188+
189+ // Check if server requires STARTTLS but we didn't detect it
190+ if message. to_lowercase ( ) . contains ( "starttls" )
191+ || ( response. code ( ) . to_string ( ) . starts_with ( "530" ) && message. contains ( "5.7.0" ) )
192+ {
193+ tracing:: warn!( target: "smtp_task" ,
194+ "Server requires STARTTLS but current connection doesn't support it" ) ;
195+ smtp_conn. quit ( ) . ok ( ) ;
196+ return Ok ( SmtpVerificationResult :: inconclusive_retry ( format ! (
197+ "Server requires STARTTLS: {} {}" ,
198+ response. code( ) ,
199+ message
200+ ) ) ) ;
201+ }
202+
114203 smtp_conn. quit ( ) . ok ( ) ;
115204 return Ok ( SmtpVerificationResult :: inconclusive_no_retry ( format ! (
116205 "MAIL FROM rejected: {} {}" ,
117206 response. code( ) ,
118- response . message( ) . collect :: < Vec < & str >> ( ) . join ( " " )
207+ message
119208 ) ) ) ;
120209 }
121210 }
@@ -238,9 +327,9 @@ async fn verify_smtp_email(
238327 ] ;
239328 let message_lower = target_message. to_lowercase ( ) ;
240329
241- let code_value = u16 :: from ( target_code) ;
330+ let code_value = target_code. to_string ( ) ;
242331
243- if [ 550 , 551 , 553 ] . contains ( & code_value)
332+ if [ " 550" , " 551" , " 553" ] . contains ( & code_value. as_str ( ) )
244333 || rejection_phrases. iter ( ) . any ( |p| message_lower. contains ( p) )
245334 {
246335 SmtpVerificationResult :: conclusive (
@@ -282,6 +371,18 @@ fn handle_smtp_error(
282371) -> SmtpVerificationResult {
283372 let err_string = error. to_string ( ) ;
284373
374+ // Check if the error is related to STARTTLS requirement
375+ if err_string. contains ( "STARTTLS" )
376+ || err_string. contains ( "starttls" )
377+ || ( err_string. contains ( "530" ) && err_string. contains ( "5.7.0" ) )
378+ {
379+ tracing:: warn!( target: "smtp_task" , "Server {} requires STARTTLS: {}" , server, error) ;
380+ return SmtpVerificationResult :: inconclusive_retry ( format ! (
381+ "SMTP requires TLS encryption: {}" ,
382+ err_string
383+ ) ) ;
384+ }
385+
285386 if err_string. contains ( "550" )
286387 && ( err_string. contains ( "does not exist" )
287388 || err_string. contains ( "no such user" )
0 commit comments