Skip to main content

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}