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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use std::{io as std_io};

use futures::future::{self, Either, Future};

use native_tls::TlsConnector as NativeTlsConnector;
use tokio_tls::TlsConnector;

use ::error::MissingCapabilities;
use ::{
    ExecFuture, Cmd,
    Domain, Capability, EsmtpKeyword,
    map_tls_err, SetupTls, DefaultTlsSetup, EhloData
};
use ::io::{Io, Socket};
use ::response::{Response, codes};




pub struct StartTls<S = DefaultTlsSetup> {
    pub setup_tls: S,
    pub sni_domain: Domain,
}

impl StartTls<DefaultTlsSetup> {
    pub fn new<I>(sni_domain: I) -> Self
        where I: Into<Domain>
    {
        StartTls {
            sni_domain: sni_domain.into(),
            setup_tls: DefaultTlsSetup
        }
    }
}

impl<S> StartTls<S>
    where S: SetupTls
{

    pub fn new_with_tls_setup<I, F: 'static>(sni_domain: I, setup_tls: S) -> Self
        where I: Into<Domain>
    {
        StartTls {
            setup_tls,
            sni_domain: sni_domain.into(),
        }
    }
}

/// STARTTLS is the only command which does not have a "final" response,
/// after it's intermediate response it will start the tls handshake and
/// after that nothing is ever send back, but this API _always_ has a
/// response for a request, so we create a "fake" response (`"220 Ready"`)
fn tls_done_result() -> Response {
    Response::new(
        codes::STATUS_RESPONSE,
        vec![ "Ready".to_owned() ]
    )
}


fn connection_already_secure_error_future() -> ExecFuture {
    let fut = future::err(std_io::Error::new(
        std_io::ErrorKind::AlreadyExists,
        "connection is already TLS encrypted"
    ));
    return Box::new(fut);
}

const STARTTLS: &str = "STARTTLS";

impl<S> Cmd for StartTls<S>
    where S: SetupTls
{

    fn check_cmd_availability(&self, caps: Option<&EhloData>)
        -> Result<(), MissingCapabilities>
    {
        caps.and_then(|ehlo_data| {
            if ehlo_data.has_capability(STARTTLS) {
                Some(())
            } else {
                None
            }
        }).ok_or_else(|| {
            let mcap = Capability::from(EsmtpKeyword::from_unchecked(STARTTLS));
            MissingCapabilities::new(vec![mcap])
        })
    }

    fn exec(self, mut io: Io) -> ExecFuture {
        let StartTls { sni_domain, setup_tls } = self;

        let was_mock =
            match *io.socket_mut() {
                Socket::Insecure(_) => {
                    false
                },
                #[cfg(feature="mock-support")]
                Socket::Mock(ref mut socket_mock) if !socket_mock.is_secure() => {
                    socket_mock.set_is_secure(true);
                    true
                }
                #[cfg(feature="mock-support")]
                Socket::Secure(_) | Socket::Mock(_) => {
                    return connection_already_secure_error_future();
                }
                #[cfg(not(feature="mock-support"))]
                Socket::Secure(_) => {
                    return connection_already_secure_error_future();
                },
            };

        if was_mock {
            let fut = future::ok((io, Ok(tls_done_result())));
            return Box::new(fut);
        }

        let fut = io
            .flush_line_from_parts(&["STARTTLS"])
            .and_then(Io::parse_response)
            .and_then(move |(io, smtp_result)| match smtp_result {
                Err(response) => {
                    Either::A(future::ok((io, Err(response))))
                },
                Ok(_) => {
                    let connector = alttry!(
                        {
                            let contor = setup_tls.setup(NativeTlsConnector::builder())?;
                            Ok(TlsConnector::from(contor))
                        } =>
                        |err| Either::A(future::err(map_tls_err(err)))
                    );

                    let (socket, _buffer, _ehlo_data) = io.split();
                    let stream = match socket {
                        Socket::Insecure(stream) => stream,
                        _ => unreachable!()
                    };

                    let fut = connector
                        .connect(sni_domain.as_str(), stream)
                        .map_err(map_tls_err)
                        .map(move |stream| {
                            let socket = Socket::Secure(stream);
                            let io = Io::from(socket);
                            (io, Ok(tls_done_result()))
                        });

                    Either::B(fut)
                },
            });

        Box::new(fut)
    }
}