openvpn_mgmt_codec/message.rs
1use crate::auth::AuthType;
2use crate::client_event::ClientEvent;
3use crate::log_level::LogLevel;
4use crate::openvpn_state::OpenVpnState;
5
6/// Sub-types of `>PASSWORD:` notifications. The password notification
7/// has several distinct forms with completely different structures.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum PasswordNotification {
10 /// `>PASSWORD:Need 'Auth' username/password`
11 NeedAuth {
12 /// The credential set being requested.
13 auth_type: AuthType,
14 },
15
16 /// `>PASSWORD:Need 'Private Key' password`
17 NeedPassword {
18 /// The credential set being requested.
19 auth_type: AuthType,
20 },
21
22 /// `>PASSWORD:Verification Failed: 'Auth'`
23 VerificationFailed {
24 /// The credential set that failed verification.
25 auth_type: AuthType,
26 },
27
28 /// Static challenge: `>PASSWORD:Need 'Auth' username/password SC:{flag},{challenge}`
29 /// The flag is a multi-bit integer: bit 0 = ECHO, bit 1 = FORMAT.
30 StaticChallenge {
31 /// Whether to echo the user's response (bit 0 of the SC flag).
32 echo: bool,
33 /// Whether the response should be concatenated with the password
34 /// as plain text (bit 1 of the SC flag). When `false`, the response
35 /// and password are base64-encoded per the SCRV1 format.
36 response_concat: bool,
37 /// The challenge text presented to the user.
38 challenge: String,
39 },
40
41 /// Dynamic challenge (CRV1):
42 /// `>PASSWORD:Verification Failed: 'Auth' ['CRV1:{flags}:{state_id}:{username_b64}:{challenge}']`
43 DynamicChallenge {
44 /// Comma-separated CRV1 flags.
45 flags: String,
46 /// Opaque state identifier for the auth backend.
47 state_id: String,
48 /// Base64-encoded username.
49 username_b64: String,
50 /// The challenge text presented to the user.
51 challenge: String,
52 },
53}
54
55/// A parsed real-time notification from OpenVPN.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum Notification {
58 /// A multi-line `>CLIENT:` notification (CONNECT, REAUTH, ESTABLISHED,
59 /// DISCONNECT). The header and all ENV key=value pairs are accumulated
60 /// into a single struct before this is emitted.
61 Client {
62 /// The client event sub-type.
63 event: ClientEvent,
64 /// Client ID (sequential, assigned by OpenVPN).
65 cid: u64,
66 /// Key ID (present for CONNECT/REAUTH, absent for ESTABLISHED/DISCONNECT).
67 kid: Option<u64>,
68 /// Accumulated ENV pairs, in order. Each `>CLIENT:ENV,key=val` line
69 /// becomes one `(key, val)` entry. The terminating `>CLIENT:ENV,END`
70 /// is consumed but not included.
71 env: Vec<(String, String)>,
72 },
73
74 /// A single-line `>CLIENT:ADDRESS` notification.
75 ClientAddress {
76 /// Client ID.
77 cid: u64,
78 /// Assigned virtual address.
79 addr: String,
80 /// Whether this is the primary address for the client.
81 primary: bool,
82 },
83
84 /// `>STATE:timestamp,name,desc,local_ip,remote_ip,remote_port,local_addr,local_port,local_ipv6`
85 ///
86 /// Field order per management-notes.txt: (a) timestamp, (b) state name,
87 /// (c) description, (d) TUN/TAP local IPv4, (e) remote server address,
88 /// (f) remote server port, (g) local address, (h) local port,
89 /// (i) TUN/TAP local IPv6.
90 State {
91 /// (a) Unix timestamp of the state change.
92 timestamp: u64,
93 /// (b) State name (e.g. `Connected`, `Reconnecting`).
94 name: OpenVpnState,
95 /// (c) Verbose description (mostly for RECONNECTING/EXITING).
96 description: String,
97 /// (d) TUN/TAP local IPv4 address (may be empty).
98 local_ip: String,
99 /// (e) Remote server address (may be empty).
100 remote_ip: String,
101 /// (f) Remote server port (may be empty).
102 remote_port: String,
103 /// (g) Local address (may be empty).
104 local_addr: String,
105 /// (h) Local port (may be empty).
106 local_port: String,
107 /// (i) TUN/TAP local IPv6 address (may be empty).
108 local_ipv6: String,
109 },
110
111 /// `>BYTECOUNT:bytes_in,bytes_out` (client mode)
112 ByteCount {
113 /// Bytes received since last reset.
114 bytes_in: u64,
115 /// Bytes sent since last reset.
116 bytes_out: u64,
117 },
118
119 /// `>BYTECOUNT_CLI:cid,bytes_in,bytes_out` (server mode, per-client)
120 ByteCountCli {
121 /// Client ID.
122 cid: u64,
123 /// Bytes received from this client.
124 bytes_in: u64,
125 /// Bytes sent to this client.
126 bytes_out: u64,
127 },
128
129 /// `>LOG:timestamp,level,message`
130 Log {
131 /// Unix timestamp of the log entry.
132 timestamp: u64,
133 /// Log severity level.
134 level: LogLevel,
135 /// The log message text.
136 message: String,
137 },
138
139 /// `>ECHO:timestamp,param_string`
140 Echo {
141 /// Unix timestamp.
142 timestamp: u64,
143 /// The echoed parameter string.
144 param: String,
145 },
146
147 /// `>HOLD:Waiting for hold release[:N]`
148 Hold {
149 /// The hold message text.
150 text: String,
151 },
152
153 /// `>FATAL:message`
154 Fatal {
155 /// The fatal error message.
156 message: String,
157 },
158
159 /// `>PKCS11ID-COUNT:count`
160 Pkcs11IdCount {
161 /// Number of available PKCS#11 identities.
162 count: u32,
163 },
164
165 /// `>NEED-OK:Need 'name' confirmation MSG:message`
166 NeedOk {
167 /// The prompt name.
168 name: String,
169 /// The prompt message to display.
170 message: String,
171 },
172
173 /// `>NEED-STR:Need 'name' input MSG:message`
174 NeedStr {
175 /// The prompt name.
176 name: String,
177 /// The prompt message to display.
178 message: String,
179 },
180
181 /// `>RSA_SIGN:base64_data`
182 RsaSign {
183 /// Base64-encoded data to be signed.
184 data: String,
185 },
186
187 /// `>REMOTE:host,port,protocol`
188 Remote {
189 /// Remote server hostname or IP.
190 host: String,
191 /// Remote server port.
192 port: u16,
193 /// Transport protocol.
194 protocol: crate::transport_protocol::TransportProtocol,
195 },
196
197 /// `>PROXY:index,proxy_type,host`
198 ///
199 /// Sent when OpenVPN needs proxy information (requires
200 /// `--management-query-proxy`). The management client responds
201 /// with a `proxy` command.
202 Proxy {
203 /// Connection index (1-based).
204 index: u32,
205 /// Proxy type string (e.g. `"TCP"`, `"UDP"`).
206 proxy_type: String,
207 /// Server hostname or IP to connect through.
208 host: String,
209 },
210
211 /// `>PASSWORD:...` — see [`PasswordNotification`] for the sub-types.
212 Password(PasswordNotification),
213
214 /// Fallback for any notification type not explicitly modeled above.
215 /// Kept for forward compatibility with future OpenVPN versions.
216 Simple {
217 /// The notification type keyword (e.g. `"BYTECOUNT"`).
218 kind: String,
219 /// Everything after the first colon.
220 payload: String,
221 },
222}
223
224/// A fully decoded message from the OpenVPN management interface.
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub enum OvpnMessage {
227 /// A success response: `SUCCESS: [text]`.
228 Success(String),
229
230 /// An error response: `ERROR: [text]`.
231 Error(String),
232
233 /// A multi-line response block (from `status`, `version`, `help`, etc.).
234 /// The terminating `END` line is consumed but not included.
235 MultiLine(Vec<String>),
236
237 /// Parsed response from `>PKCS11ID-ENTRY:` notification (sent by
238 /// `pkcs11-id-get`). Wire: `>PKCS11ID-ENTRY:'index', ID:'id', BLOB:'blob'`
239 Pkcs11IdEntry {
240 /// Certificate index.
241 index: String,
242 /// PKCS#11 identifier.
243 id: String,
244 /// Base64-encoded certificate blob.
245 blob: String,
246 },
247
248 /// A real-time notification, either single-line or accumulated multi-line.
249 Notification(Notification),
250
251 /// The `>INFO:` banner sent when the management socket first connects.
252 /// Technically a notification, but surfaced separately since it's always
253 /// the first thing you see and is useful for version detection.
254 Info(String),
255
256 /// Management interface password prompt. Sent when `--management` is
257 /// configured with a password file. The client must respond with the
258 /// password (via [`crate::OvpnCommand::ManagementPassword`]) before any
259 /// commands are accepted.
260 PasswordPrompt,
261
262 /// A line that could not be classified into any known message type.
263 /// Contains the raw line and a description of what went wrong.
264 Unrecognized {
265 /// The raw line that could not be parsed.
266 line: String,
267 /// Why the line was not recognized.
268 kind: crate::unrecognized::UnrecognizedKind,
269 },
270}