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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
//! # Async/await EPP client.
//!
//! Messages should be injected into the server using the helper functions in subordinate modules such as
//! [`contact`], [`host`], and [`domain`].

use crate::proto;
use futures::future::FutureExt;

pub mod epp;
pub mod epp_like;
pub mod nominet_dac;
pub mod tmch_client;

pub mod balance;
pub mod contact;
pub mod dac;
pub mod domain;
pub mod email_forward;
pub mod eurid;
pub mod fee;
pub mod host;
pub mod isnic;
pub mod launch;
pub mod maintenance;
pub mod mark;
pub mod nominet;
pub mod personal_registration;
pub mod poll;
pub mod rgp;
pub mod router;
pub mod tmch;
pub mod traficom;
pub mod verisign;

pub use router::{CommandResponse, RequestMessage, RequestSender, Response, Sender};

pub enum ClientCertConf<'a> {
    /// PCKS#12 file path for client identity
    PKCS12(&'a str),
    /// PCKS#11 HSM details
    PKCS11 {
        key_id: &'a str,
        cert_chain: &'a str,
    },
}

pub struct NominetDACConf<'a> {
    pub real_time: &'a str,
    pub time_delay: &'a str,
}

pub struct ClientConf<'a, C: Into<Option<&'a str>>> {
    /// The server connection string, in the form `domain:port`
    pub host: &'a str,
    /// The client ID/tag to login with
    pub tag: &'a str,
    /// The password to login with
    pub password: &'a str,
    /// Directory path to log commands to
    pub log_dir: std::path::PathBuf,
    pub client_cert: Option<ClientCertConf<'a>>,
    /// Source address to bind the TLS connection to, for IP based ACLs etc.
    pub source_address: Option<&'a std::net::IpAddr>,
    /// List of PEM file paths
    pub root_certs: &'a [&'a str],
    /// Accept invalid TLS certs
    pub danger_accept_invalid_certs: bool,
    /// Accept TLS certs with a hostname that doesn't match the DNS label
    pub danger_accept_invalid_hostname: bool,
    /// New password to set after login
    pub new_password: C,
    /// Does the server support multiple commands in flight at once
    pub pipelining: bool,
    /// Errata of this server
    pub errata: Option<String>,
    pub nominet_dac: Option<NominetDACConf<'a>>,
    /// Should the client send keepalive commands automatically
    pub keepalive: bool,
}

async fn send_epp_client_request<R>(
    client_sender: &mut futures::channel::mpsc::Sender<RequestMessage>,
    req: RequestMessage,
    receiver: futures::channel::oneshot::Receiver<Response<R>>,
) -> Result<R, Error> {
    match client_sender.try_send(req) {
        Ok(_) => {}
        Err(_) => return Err(Error::ServerInternal),
    }
    let mut receiver = receiver.fuse();
    let mut delay = Box::pin(tokio::time::sleep(tokio::time::Duration::new(60, 0)).fuse());
    let resp = futures::select! {
        r = receiver => r,
        _ = delay => {
            return Err(Error::Timeout);
        }
    };
    match resp {
        Ok(r) => r,
        Err(_) => Err(Error::ServerInternal),
    }
}

/// Possible errors returned by the EPP client
#[derive(Debug)]
pub enum Error {
    /// The EPP server is not currently able to accept requests
    NotReady,
    /// The EPP server doesn't support the requested action
    Unsupported,
    /// The EPP client or server encountered an internal unexpected error processing the request
    ServerInternal,
    /// The EPP server didn't respond in time to the request
    Timeout,
    /// The EPP server returned an error message (probably invalid parameters)
    Err(String),
}

#[derive(PartialEq, Debug)]
pub enum TransferStatus {
    ClientApproved,
    ClientCancelled,
    ClientRejected,
    Pending,
    ServerApproved,
    ServerCancelled,
}

#[derive(Debug)]
pub struct Period {
    /// Unit of time
    pub unit: PeriodUnit,
    /// Number of units of time
    pub value: u32,
}

#[derive(Debug)]
pub enum PeriodUnit {
    Years,
    Months,
}

#[derive(Debug)]
pub struct Phone {
    /// Initial dialable part of the number
    pub number: String,
    /// Optional internal extension
    pub extension: Option<String>,
}

#[derive(Debug)]
pub struct BlankRequest {
    pub return_path: Sender<()>,
}

/// Ends an EPP session
///
/// # Arguments
/// * `client_sender` - Reference to the tokio channel into the client
pub async fn logout(
    mut client_sender: futures::channel::mpsc::Sender<RequestMessage>,
) -> Result<CommandResponse<()>, Error> {
    let (sender, receiver) = futures::channel::oneshot::channel();
    send_epp_client_request(
        &mut client_sender,
        RequestMessage::Logout(Box::new(BlankRequest {
            return_path: sender,
        })),
        receiver,
    )
    .await
}

pub trait Client {
    fn start(
        self: Box<Self>,
    ) -> (
        futures::channel::mpsc::Sender<RequestMessage>,
        futures::channel::mpsc::UnboundedReceiver<router::CommandTransactionID>,
    );
}