{"record":{"id":"a09e9fa65794af9f","repo":"stalwartlabs/stalwart","slug":"unspecified","errorCode":null,"errorMessage":"Unspecified","messagePattern":"Unspecified","errorType":"exception","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/smtp/src/outbound/client.rs","lineNumber":535,"sourceCode":"        tokio::time::timeout(self.timeout, async {\n            Ok(SmtpClient {\n                stream: tls_connector\n                    .connect(\n                        ServerName::try_from(hostname)\n                            .map_err(|_| ClientError::InvalidTLSName)?\n                            .to_owned(),\n                        self.stream,\n                    )\n                    .await\n                    .map_err(|err| {\n                        let kind = err.kind();\n                        if let Some(inner) = err.into_inner() {\n                            match inner.downcast::<rustls::Error>() {\n                                Ok(error) => ClientError::Tls(error),\n                                Err(error) => ClientError::Io(std::io::Error::new(kind, error)),\n                            }\n                        } else {\n                            ClientError::Io(std::io::Error::new(kind, \"Unspecified\"))\n                        }\n                    })?,\n                timeout: self.timeout,\n                session_id: self.session_id,\n            })\n        })\n        .await\n        .map_err(|_| ClientError::Timeout)?\n    }\n}\n\nimpl SmtpClient<TcpStream> {\n    /// Connects to a remote host address\n    pub async fn connect(\n        remote_addr: SocketAddr,\n        timeout: Duration,\n        session_id: u64,\n    ) -> ClientResult<Self> {","sourceCodeStart":517,"sourceCodeEnd":553,"githubUrl":"https://github.com/stalwartlabs/stalwart/blob/e96200385781a6a9995a8b839ac27d6c75a983ee/crates/smtp/src/outbound/client.rs#L517-L553","documentation":"This error occurs when the SMTP client fails to upgrade a plain TCP connection to TLS via STARTTLS, but the underlying io::Error carries no inner error payload. The library falls back to wrapping the io::ErrorKind with the literal message 'Unspecified', so the actual TLS handshake failure cause is unknown. It is produced by SmtpClient::into_tls when tls_connector.connect() fails without a nested error.","triggerScenarios":"Calling SmtpClient::starttls or SmtpClient::into_tls where tokio-rustls' TlsConnector::connect() returns an io::Error whose into_inner() is None — typically the peer drops/resets the TCP connection mid-handshake, or the OS reports a socket error without a nested cause.","commonSituations":"Remote SMTP server crashes or closes the connection right after accepting STARTTLS; a firewall or DPI middlebox RSTs the TLS handshake; connecting to a port whose TLS stack is broken; MTU/fragmentation issues killing large handshake records.","solutions":["Retry and inspect server-side logs on the remote SMTP host to learn why the handshake connection was dropped","Test the endpoint manually (openssl s_client -starttls smtp -connect host:port) to confirm it negotiates TLS correctly","Check for firewalls, DPI middleboxes, or MTU problems between client and server that reset TLS handshakes","Update tokio-rustls/rustls so handshake errors carry detailed causes instead of bare io::Errors","Handle ClientError::Io with ConnectionAborted/UnexpectedEof kinds by skipping to the next MX host or falling back to plaintext delivery"],"exampleFix":"// before\nmatch client.starttls(&tls_connector, hostname).await {\n    Ok(c) => ..., \n    Err(e) => panic!(\"{:?}\", e),\n}\n// after\nmatch client.starttls(&tls_connector, hostname).await {\n    Ok(c) => ...,\n    Err(ClientError::Io(e))\n        if matches!(e.kind(), std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::UnexpectedEof) =>\n    {\n        tracing::warn!(\"TLS handshake dropped by peer; trying next MX host\");\n    }\n    Err(e) => return Err(e),\n}","handlingStrategy":"try-catch","validationCode":"// before dialing, confirm the host supports STARTTLS\nlet caps = smtp_client.capabilities().await?;\nif !caps.supports_starttls() {\n    // skip TLS upgrade or choose another MX host\n}","typeGuard":"fn is_tls_handshake_dropped(err: &ClientError) -> bool {\n    matches!(err, ClientError::Io(e) if matches!(e.kind(),\n        std::io::ErrorKind::ConnectionAborted | std::io::ErrorKind::UnexpectedEof))\n}","tryCatchPattern":"match client.starttls(&tls_connector, hostname).await {\n    Ok(c) => c,\n    Err(e) if is_tls_handshake_dropped(&e) => {\n        tracing::warn!(\"peer dropped TLS handshake; failing over\");\n        try_next_mx_host()? // or fall back to plaintext relay\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Verify destination servers support STARTTLS before requiring TLS (test with openssl s_client)","Check network path for firewalls/DPI devices that reset TLS handshakes","Keep tokio-rustls/rustls updated so errors carry detailed causes","Implement per-host failover so one broken relay does not stop delivery","Monitor logs for repeated 'Unspecified' errors from the same peer and investigate server-side"],"tags":["tls","network","smtp","starttls"],"backgroundTag":"tls-handshake-failure","analyzedSha":"e96200385781a6a9995a8b839ac27d6c75a983ee","analyzedAt":"2026-09-06T22:07:17.982Z","contentChangedAt":"2026-09-06T22:07:17.982Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}