1use bytes::{BufMut, BytesMut};
2use std::io;
3use tokio_util::codec::{Decoder, Encoder};
4
5use crate::command::{OvpnCommand, ResponseKind};
6use crate::kill_target::KillTarget;
7use crate::message::{Notification, OvpnMessage};
8use crate::proxy_action::ProxyAction;
9use crate::remote_action::RemoteAction;
10use crate::status_format::StatusFormat;
11use crate::unrecognized::UnrecognizedKind;
12
13fn quote_and_escape(s: &str) -> String {
22 let mut out = String::with_capacity(s.len() + 2);
23 out.push('"');
24 for c in s.chars() {
25 match c {
26 '\\' => out.push_str("\\\\"),
27 '"' => out.push_str("\\\""),
28 '\n' | '\r' | '\0' => {} _ => out.push(c),
30 }
31 }
32 out.push('"');
33 out
34}
35
36fn sanitize_line(s: &str) -> String {
39 if s.contains('\n') || s.contains('\r') || s.contains('\0') {
40 s.chars()
41 .filter(|&c| c != '\n' && c != '\r' && c != '\0')
42 .collect()
43 } else {
44 s.to_string()
45 }
46}
47
48use crate::client_event::ClientEvent;
49use crate::log_level::LogLevel;
50use crate::openvpn_state::OpenVpnState;
51use crate::transport_protocol::TransportProtocol;
52
53#[derive(Debug)]
55struct ClientNotifAccum {
56 event: ClientEvent,
57 cid: u64,
58 kid: Option<u64>,
59 env: Vec<(String, String)>,
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum AccumulationLimit {
66 Unlimited,
68 Max(usize),
70}
71
72pub struct OvpnCodec {
94 expected: ResponseKind,
100
101 multi_line_buf: Option<Vec<String>>,
103
104 client_notif: Option<ClientNotifAccum>,
107
108 max_multi_line_lines: AccumulationLimit,
110
111 max_client_env_entries: AccumulationLimit,
113}
114
115impl OvpnCodec {
116 pub fn new() -> Self {
119 Self {
120 expected: ResponseKind::SuccessOrError,
125 multi_line_buf: None,
126 client_notif: None,
127 max_multi_line_lines: AccumulationLimit::Unlimited,
128 max_client_env_entries: AccumulationLimit::Unlimited,
129 }
130 }
131
132 pub fn with_max_multi_line_lines(mut self, limit: AccumulationLimit) -> Self {
135 self.max_multi_line_lines = limit;
136 self
137 }
138
139 pub fn with_max_client_env_entries(mut self, limit: AccumulationLimit) -> Self {
142 self.max_client_env_entries = limit;
143 self
144 }
145}
146
147fn check_accumulation_limit(
148 current_len: usize,
149 limit: AccumulationLimit,
150 what: &str,
151) -> Result<(), io::Error> {
152 if let AccumulationLimit::Max(max) = limit
153 && current_len >= max
154 {
155 return Err(io::Error::other(format!(
156 "{what} accumulation limit exceeded ({max})"
157 )));
158 }
159 Ok(())
160}
161
162impl Default for OvpnCodec {
163 fn default() -> Self {
164 Self::new()
165 }
166}
167
168impl Encoder<OvpnCommand> for OvpnCodec {
171 type Error = io::Error;
172
173 fn encode(&mut self, item: OvpnCommand, dst: &mut BytesMut) -> Result<(), Self::Error> {
174 debug_assert!(
175 self.multi_line_buf.is_none() && self.client_notif.is_none(),
176 "encode() called while the decoder is mid-accumulation \
177 (multi_line_buf or client_notif is active). \
178 Drain decode() before sending a new command."
179 );
180
181 self.expected = item.expected_response();
184
185 match item {
186 OvpnCommand::Status(StatusFormat::V1) => write_line(dst, "status"),
188 OvpnCommand::Status(ref fmt) => write_line(dst, &format!("status {fmt}")),
189 OvpnCommand::State => write_line(dst, "state"),
190 OvpnCommand::StateStream(ref m) => write_line(dst, &format!("state {m}")),
191 OvpnCommand::Version => write_line(dst, "version"),
192 OvpnCommand::Pid => write_line(dst, "pid"),
193 OvpnCommand::Help => write_line(dst, "help"),
194 OvpnCommand::Net => write_line(dst, "net"),
195 OvpnCommand::Verb(Some(n)) => write_line(dst, &format!("verb {n}")),
196 OvpnCommand::Verb(None) => write_line(dst, "verb"),
197 OvpnCommand::Mute(Some(n)) => write_line(dst, &format!("mute {n}")),
198 OvpnCommand::Mute(None) => write_line(dst, "mute"),
199
200 OvpnCommand::Log(ref m) => write_line(dst, &format!("log {m}")),
202 OvpnCommand::Echo(ref m) => write_line(dst, &format!("echo {m}")),
203 OvpnCommand::ByteCount(n) => write_line(dst, &format!("bytecount {n}")),
204
205 OvpnCommand::Signal(sig) => write_line(dst, &format!("signal {sig}")),
207 OvpnCommand::Kill(KillTarget::CommonName(ref cn)) => {
208 write_line(dst, &format!("kill {}", sanitize_line(cn)));
209 }
210 OvpnCommand::Kill(KillTarget::Address {
211 ref protocol,
212 ref ip,
213 port,
214 }) => {
215 write_line(
216 dst,
217 &format!(
218 "kill {}:{}:{port}",
219 sanitize_line(protocol),
220 sanitize_line(ip)
221 ),
222 );
223 }
224 OvpnCommand::HoldQuery => write_line(dst, "hold"),
225 OvpnCommand::HoldOn => write_line(dst, "hold on"),
226 OvpnCommand::HoldOff => write_line(dst, "hold off"),
227 OvpnCommand::HoldRelease => write_line(dst, "hold release"),
228
229 OvpnCommand::Username {
234 ref auth_type,
235 ref value,
236 } => {
237 let at = quote_and_escape(&auth_type.to_string());
241 let val = quote_and_escape(value);
242 write_line(dst, &format!("username {at} {val}"));
243 }
244 OvpnCommand::Password {
245 ref auth_type,
246 ref value,
247 } => {
248 let at = quote_and_escape(&auth_type.to_string());
249 let val = quote_and_escape(value);
250 write_line(dst, &format!("password {at} {val}"));
251 }
252 OvpnCommand::AuthRetry(mode) => write_line(dst, &format!("auth-retry {mode}")),
253 OvpnCommand::ForgetPasswords => write_line(dst, "forget-passwords"),
254
255 OvpnCommand::ChallengeResponse {
257 ref state_id,
258 ref response,
259 } => {
260 let value = format!("CRV1::{state_id}::{response}");
261 let escaped = quote_and_escape(&value);
262 write_line(dst, &format!("password \"Auth\" {escaped}"));
263 }
264 OvpnCommand::StaticChallengeResponse {
265 ref password_b64,
266 ref response_b64,
267 } => {
268 let value = format!("SCRV1:{password_b64}:{response_b64}");
269 let escaped = quote_and_escape(&value);
270 write_line(dst, &format!("password \"Auth\" {escaped}"));
271 }
272
273 OvpnCommand::NeedOk { ref name, response } => {
275 write_line(dst, &format!("needok {} {response}", sanitize_line(name)));
276 }
277 OvpnCommand::NeedStr {
278 ref name,
279 ref value,
280 } => {
281 let escaped = quote_and_escape(value);
282 write_line(dst, &format!("needstr {} {escaped}", sanitize_line(name)));
283 }
284
285 OvpnCommand::Pkcs11IdCount => write_line(dst, "pkcs11-id-count"),
287 OvpnCommand::Pkcs11IdGet(idx) => write_line(dst, &format!("pkcs11-id-get {idx}")),
288
289 OvpnCommand::RsaSig { ref base64_lines } => write_block(dst, "rsa-sig", base64_lines),
297
298 OvpnCommand::ClientAuth {
306 cid,
307 kid,
308 ref config_lines,
309 } => write_block(dst, &format!("client-auth {cid} {kid}"), config_lines),
310
311 OvpnCommand::ClientAuthNt { cid, kid } => {
312 write_line(dst, &format!("client-auth-nt {cid} {kid}"));
313 }
314
315 OvpnCommand::ClientDeny {
316 cid,
317 kid,
318 ref reason,
319 ref client_reason,
320 } => {
321 let r = quote_and_escape(reason);
322 match client_reason {
323 Some(cr) => {
324 let cr_esc = quote_and_escape(cr);
325 write_line(dst, &format!("client-deny {cid} {kid} {r} {cr_esc}"));
326 }
327 None => write_line(dst, &format!("client-deny {cid} {kid} {r}")),
328 }
329 }
330
331 OvpnCommand::ClientKill { cid, ref message } => match message {
332 Some(msg) => write_line(dst, &format!("client-kill {cid} {}", sanitize_line(msg))),
333 None => write_line(dst, &format!("client-kill {cid}")),
334 },
335
336 OvpnCommand::LoadStats => write_line(dst, "load-stats"),
338
339 OvpnCommand::ClientPendingAuth {
341 cid,
342 kid,
343 ref extra,
344 timeout,
345 } => write_line(
346 dst,
347 &format!(
348 "client-pending-auth {cid} {kid} {} {timeout}",
349 sanitize_line(extra)
350 ),
351 ),
352
353 OvpnCommand::CrResponse { ref response } => {
354 write_line(dst, &format!("cr-response {}", sanitize_line(response)))
355 }
356
357 OvpnCommand::Certificate { ref pem_lines } => {
359 write_block(dst, "certificate", pem_lines);
360 }
361
362 OvpnCommand::Remote(RemoteAction::Accept) => write_line(dst, "remote ACCEPT"),
364 OvpnCommand::Remote(RemoteAction::Skip) => write_line(dst, "remote SKIP"),
365 OvpnCommand::Remote(RemoteAction::Modify { ref host, port }) => {
366 write_line(dst, &format!("remote MOD {} {port}", sanitize_line(host)));
367 }
368 OvpnCommand::Proxy(ProxyAction::None) => write_line(dst, "proxy NONE"),
369 OvpnCommand::Proxy(ProxyAction::Http {
370 ref host,
371 port,
372 non_cleartext_only,
373 }) => {
374 let nct = if non_cleartext_only { " nct" } else { "" };
375 write_line(
376 dst,
377 &format!("proxy HTTP {} {port}{nct}", sanitize_line(host)),
378 );
379 }
380 OvpnCommand::Proxy(ProxyAction::Socks { ref host, port }) => {
381 write_line(dst, &format!("proxy SOCKS {} {port}", sanitize_line(host)));
382 }
383
384 OvpnCommand::ManagementPassword(ref pw) => {
388 write_line(dst, &sanitize_line(pw));
389 }
390
391 OvpnCommand::Exit => write_line(dst, "exit"),
393 OvpnCommand::Quit => write_line(dst, "quit"),
394
395 OvpnCommand::Raw(ref cmd) | OvpnCommand::RawMultiLine(ref cmd) => {
397 write_line(dst, &sanitize_line(cmd));
398 }
399 }
400
401 Ok(())
402 }
403}
404
405fn write_line(dst: &mut BytesMut, s: &str) {
407 dst.reserve(s.len() + 1);
408 dst.put_slice(s.as_bytes());
409 dst.put_u8(b'\n');
410}
411
412fn write_block(dst: &mut BytesMut, header: &str, lines: &[String]) {
418 let total: usize = header.len() + 1 + lines.iter().map(|l| l.len() + 2).sum::<usize>() + 4;
419 dst.reserve(total);
420 dst.put_slice(header.as_bytes());
421 dst.put_u8(b'\n');
422 for line in lines {
423 let needs_sanitize = line.contains('\n') || line.contains('\r') || line.contains('\0');
424 if !needs_sanitize && line != "END" {
425 dst.put_slice(line.as_bytes());
426 } else {
427 let sanitized: String = if needs_sanitize {
428 line.chars()
429 .filter(|&c| c != '\n' && c != '\r' && c != '\0')
430 .collect()
431 } else {
432 line.to_string()
433 };
434 if sanitized == "END" {
435 dst.put_slice(b" END");
436 } else {
437 dst.put_slice(sanitized.as_bytes());
438 }
439 }
440 dst.put_u8(b'\n');
441 }
442 dst.put_slice(b"END\n");
443}
444
445impl Decoder for OvpnCodec {
448 type Item = OvpnMessage;
449 type Error = io::Error;
450
451 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
452 loop {
453 let Some(newline_pos) = src.iter().position(|&b| b == b'\n') else {
455 return Ok(None); };
457
458 let line_bytes = src.split_to(newline_pos + 1);
460 let line = match std::str::from_utf8(&line_bytes) {
461 Ok(s) => s,
462 Err(e) => {
463 self.multi_line_buf = None;
466 self.client_notif = None;
467 self.expected = ResponseKind::SuccessOrError;
468 return Err(io::Error::new(io::ErrorKind::InvalidData, e));
469 }
470 }
471 .trim_end_matches(['\r', '\n'])
472 .to_string();
473
474 if let Some(ref mut accum) = self.client_notif
483 && let Some(rest) = line.strip_prefix(">CLIENT:ENV,")
484 {
485 if rest == "END" {
486 let finished = self.client_notif.take().expect("guarded by if-let");
487 return Ok(Some(OvpnMessage::Notification(Notification::Client {
488 event: finished.event,
489 cid: finished.cid,
490 kid: finished.kid,
491 env: finished.env,
492 })));
493 } else {
494 let (k, v) = rest
496 .split_once('=')
497 .map(|(k, v)| (k.to_string(), v.to_string()))
498 .unwrap_or_else(|| (rest.to_string(), String::new()));
499 check_accumulation_limit(
500 accum.env.len(),
501 self.max_client_env_entries,
502 "client ENV",
503 )?;
504 accum.env.push((k, v));
505 continue; }
507 }
508 if let Some(ref mut buf) = self.multi_line_buf {
513 if line == "END" {
514 let lines = self.multi_line_buf.take().expect("guarded by if-let");
515 return Ok(Some(OvpnMessage::MultiLine(lines)));
516 }
517 if line.starts_with('>') {
522 if let Some(msg) = self.parse_notification(&line) {
523 return Ok(Some(msg));
524 }
525 continue;
528 }
529 check_accumulation_limit(
530 buf.len(),
531 self.max_multi_line_lines,
532 "multi-line response",
533 )?;
534 buf.push(line);
535 continue; }
537
538 if let Some(rest) = line.strip_prefix("SUCCESS:") {
544 return Ok(Some(OvpnMessage::Success(
545 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
546 )));
547 }
548 if let Some(rest) = line.strip_prefix("ERROR:") {
549 return Ok(Some(OvpnMessage::Error(
550 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
551 )));
552 }
553
554 if line == "ENTER PASSWORD:" {
556 return Ok(Some(OvpnMessage::PasswordPrompt));
557 }
558
559 if line.starts_with('>') {
561 if let Some(msg) = self.parse_notification(&line) {
562 return Ok(Some(msg));
563 }
564 continue;
566 }
567
568 match self.expected {
574 ResponseKind::MultiLine => {
575 if line == "END" {
576 return Ok(Some(OvpnMessage::MultiLine(Vec::new())));
578 }
579 self.multi_line_buf = Some(vec![line]);
580 continue; }
582 ResponseKind::SuccessOrError | ResponseKind::NoResponse => {
583 return Ok(Some(OvpnMessage::Unrecognized {
584 line,
585 kind: UnrecognizedKind::UnexpectedLine,
586 }));
587 }
588 }
589 }
590 }
591}
592
593impl OvpnCodec {
594 fn parse_notification(&mut self, line: &str) -> Option<OvpnMessage> {
598 let inner = &line[1..]; let Some((kind, payload)) = inner.split_once(':') else {
601 return Some(OvpnMessage::Unrecognized {
603 line: line.to_string(),
604 kind: UnrecognizedKind::MalformedNotification,
605 });
606 };
607
608 if kind == "INFO" {
611 return Some(OvpnMessage::Info(payload.to_string()));
612 }
613
614 if kind == "CLIENT" {
616 let (event, args) = payload
617 .split_once(',')
618 .map(|(e, a)| (e.to_string(), a.to_string()))
619 .unwrap_or_else(|| (payload.to_string(), String::new()));
620
621 if event == "ADDRESS" {
623 let mut parts = args.splitn(3, ',');
624 let cid = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
625 let addr = parts.next().unwrap_or("").to_string();
626 let primary = parts.next() == Some("1");
627 return Some(OvpnMessage::Notification(Notification::ClientAddress {
628 cid,
629 addr,
630 primary,
631 }));
632 }
633
634 let mut id_parts = args.splitn(3, ',');
639 let cid = id_parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
640 let kid = id_parts.next().and_then(|s| s.parse().ok());
641
642 self.client_notif = Some(ClientNotifAccum {
644 event: ClientEvent::parse(&event),
645 cid,
646 kid,
647 env: Vec::new(),
648 });
649 return None; }
651
652 let notification = match kind {
654 "STATE" => parse_state(payload),
655 "BYTECOUNT" => parse_bytecount(payload),
656 "BYTECOUNT_CLI" => parse_bytecount_cli(payload),
657 "LOG" => parse_log(payload),
658 "ECHO" => parse_echo(payload),
659 "HOLD" => Some(Notification::Hold {
660 text: payload.to_string(),
661 }),
662 "FATAL" => Some(Notification::Fatal {
663 message: payload.to_string(),
664 }),
665 "PKCS11ID-COUNT" => parse_pkcs11id_count(payload),
666 "NEED-OK" => parse_need_ok(payload),
667 "NEED-STR" => parse_need_str(payload),
668 "RSA_SIGN" => Some(Notification::RsaSign {
669 data: payload.to_string(),
670 }),
671 "REMOTE" => parse_remote(payload),
672 "PROXY" => parse_proxy(payload),
673 "PASSWORD" => parse_password(payload),
674 "PKCS11ID-ENTRY" => {
675 return parse_pkcs11id_entry_notif(payload).or_else(|| {
676 Some(OvpnMessage::Notification(Notification::Simple {
677 kind: kind.to_string(),
678 payload: payload.to_string(),
679 }))
680 });
681 }
682 _ => None,
683 };
684
685 Some(OvpnMessage::Notification(notification.unwrap_or(
686 Notification::Simple {
687 kind: kind.to_string(),
688 payload: payload.to_string(),
689 },
690 )))
691 }
692}
693
694fn parse_state(payload: &str) -> Option<Notification> {
702 let mut parts = payload.splitn(9, ',');
706 let timestamp = parts.next()?.parse().ok()?;
707 let name = OpenVpnState::parse(parts.next()?);
708 let description = parts.next()?.to_string();
709 let local_ip = parts.next()?.to_string();
710 let remote_ip = parts.next()?.to_string();
711 let remote_port = parts.next().unwrap_or("").to_string();
712 let local_addr = parts.next().unwrap_or("").to_string();
713 let local_port = parts.next().unwrap_or("").to_string();
714 let local_ipv6 = parts.next().unwrap_or("").to_string();
715 Some(Notification::State {
716 timestamp,
717 name,
718 description,
719 local_ip,
720 remote_ip,
721 remote_port,
722 local_addr,
723 local_port,
724 local_ipv6,
725 })
726}
727
728fn parse_bytecount(payload: &str) -> Option<Notification> {
729 let (a, b) = payload.split_once(',')?;
730 Some(Notification::ByteCount {
731 bytes_in: a.parse().ok()?,
732 bytes_out: b.parse().ok()?,
733 })
734}
735
736fn parse_bytecount_cli(payload: &str) -> Option<Notification> {
737 let mut parts = payload.splitn(3, ',');
738 let cid = parts.next()?.parse().ok()?;
739 let bytes_in = parts.next()?.parse().ok()?;
740 let bytes_out = parts.next()?.parse().ok()?;
741 Some(Notification::ByteCountCli {
742 cid,
743 bytes_in,
744 bytes_out,
745 })
746}
747
748fn parse_log(payload: &str) -> Option<Notification> {
749 let (ts_str, rest) = payload.split_once(',')?;
750 let timestamp = ts_str.parse().ok()?;
751 let (level_str, message) = rest.split_once(',')?;
752 Some(Notification::Log {
753 timestamp,
754 level: LogLevel::parse(level_str),
755 message: message.to_string(),
756 })
757}
758
759fn parse_echo(payload: &str) -> Option<Notification> {
760 let (ts_str, param) = payload.split_once(',')?;
761 let timestamp = ts_str.parse().ok()?;
762 Some(Notification::Echo {
763 timestamp,
764 param: param.to_string(),
765 })
766}
767
768fn parse_pkcs11id_count(payload: &str) -> Option<Notification> {
769 let count = payload.trim().parse().ok()?;
770 Some(Notification::Pkcs11IdCount { count })
771}
772
773fn parse_pkcs11id_entry_notif(payload: &str) -> Option<OvpnMessage> {
776 let rest = payload.strip_prefix('\'')?;
777 let (index, rest) = rest.split_once("', ID:'")?;
778 let (id, rest) = rest.split_once("', BLOB:'")?;
779 let blob = rest.strip_suffix('\'')?;
780 Some(OvpnMessage::Pkcs11IdEntry {
781 index: index.to_string(),
782 id: id.to_string(),
783 blob: blob.to_string(),
784 })
785}
786
787fn parse_need_ok(payload: &str) -> Option<Notification> {
789 let rest = payload.strip_prefix("Need '")?;
791 let (name, rest) = rest.split_once('\'')?;
792 let msg = rest.split_once("MSG:")?.1;
793 Some(Notification::NeedOk {
794 name: name.to_string(),
795 message: msg.to_string(),
796 })
797}
798
799fn parse_need_str(payload: &str) -> Option<Notification> {
801 let rest = payload.strip_prefix("Need '")?;
802 let (name, rest) = rest.split_once('\'')?;
803 let msg = rest.split_once("MSG:")?.1;
804 Some(Notification::NeedStr {
805 name: name.to_string(),
806 message: msg.to_string(),
807 })
808}
809
810fn parse_remote(payload: &str) -> Option<Notification> {
811 let mut parts = payload.splitn(3, ',');
812 let host = parts.next()?.to_string();
813 let port = parts.next()?.parse().ok()?;
814 let protocol = TransportProtocol::parse(parts.next()?);
815 Some(Notification::Remote {
816 host,
817 port,
818 protocol,
819 })
820}
821
822fn parse_proxy(payload: &str) -> Option<Notification> {
823 let mut parts = payload.splitn(3, ',');
825 let index = parts.next()?.parse().ok()?;
826 let proxy_type = parts.next()?.to_string();
827 let host = parts.next()?.to_string();
828 Some(Notification::Proxy {
829 index,
830 proxy_type,
831 host,
832 })
833}
834
835use crate::message::PasswordNotification;
836
837use crate::auth::AuthType;
838
839fn parse_auth_type(s: &str) -> AuthType {
841 match s {
842 "Auth" => AuthType::Auth,
843 "Private Key" => AuthType::PrivateKey,
844 "HTTP Proxy" => AuthType::HttpProxy,
845 "SOCKS Proxy" => AuthType::SocksProxy,
846 other => AuthType::Custom(other.to_string()),
847 }
848}
849
850fn parse_password(payload: &str) -> Option<Notification> {
851 if let Some(rest) = payload.strip_prefix("Verification Failed: '") {
854 if let Some((auth_part, crv1_part)) = rest.split_once("' ['CRV1:") {
856 let _ = auth_part; let crv1_data = crv1_part.strip_suffix("']")?;
858 let mut parts = crv1_data.splitn(4, ':');
859 let flags = parts.next()?.to_string();
860 let state_id = parts.next()?.to_string();
861 let username_b64 = parts.next()?.to_string();
862 let challenge = parts.next()?.to_string();
863 return Some(Notification::Password(
864 PasswordNotification::DynamicChallenge {
865 flags,
866 state_id,
867 username_b64,
868 challenge,
869 },
870 ));
871 }
872 let auth_type = rest.strip_suffix('\'')?;
874 return Some(Notification::Password(
875 PasswordNotification::VerificationFailed {
876 auth_type: parse_auth_type(auth_type),
877 },
878 ));
879 }
880
881 let rest = payload.strip_prefix("Need '")?;
884 let (auth_type_str, rest) = rest.split_once('\'')?;
885 let rest = rest.trim_start();
886
887 if let Some(after_up) = rest.strip_prefix("username/password") {
888 let after_up = after_up.trim_start();
889
890 if let Some(sc) = after_up.strip_prefix("SC:") {
893 let (flag_str, challenge) = sc.split_once(',')?;
894 let flags: u32 = flag_str.parse().ok()?;
895 return Some(Notification::Password(
896 PasswordNotification::StaticChallenge {
897 echo: flags & 1 != 0,
898 response_concat: flags & 2 != 0,
899 challenge: challenge.to_string(),
900 },
901 ));
902 }
903
904 return Some(Notification::Password(PasswordNotification::NeedAuth {
906 auth_type: parse_auth_type(auth_type_str),
907 }));
908 }
909
910 if rest.starts_with("password") {
912 return Some(Notification::Password(PasswordNotification::NeedPassword {
913 auth_type: parse_auth_type(auth_type_str),
914 }));
915 }
916
917 None }
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923 use crate::auth::AuthType;
924 use crate::client_event::ClientEvent;
925 use crate::message::PasswordNotification;
926 use crate::signal::Signal;
927 use crate::status_format::StatusFormat;
928 use crate::stream_mode::StreamMode;
929 use bytes::BytesMut;
930 use tokio_util::codec::{Decoder, Encoder};
931
932 fn encode_to_string(cmd: OvpnCommand) -> String {
934 let mut codec = OvpnCodec::new();
935 let mut buf = BytesMut::new();
936 codec.encode(cmd, &mut buf).unwrap();
937 String::from_utf8(buf.to_vec()).unwrap()
938 }
939
940 fn decode_all(input: &str) -> Vec<OvpnMessage> {
942 let mut codec = OvpnCodec::new();
943 let mut buf = BytesMut::from(input);
944 let mut msgs = Vec::new();
945 while let Some(msg) = codec.decode(&mut buf).unwrap() {
946 msgs.push(msg);
947 }
948 msgs
949 }
950
951 fn encode_then_decode(cmd: OvpnCommand, response: &str) -> Vec<OvpnMessage> {
953 let mut codec = OvpnCodec::new();
954 let mut enc_buf = BytesMut::new();
955 codec.encode(cmd, &mut enc_buf).unwrap();
956 let mut dec_buf = BytesMut::from(response);
957 let mut msgs = Vec::new();
958 while let Some(msg) = codec.decode(&mut dec_buf).unwrap() {
959 msgs.push(msg);
960 }
961 msgs
962 }
963
964 #[test]
967 fn encode_status_v1() {
968 assert_eq!(
969 encode_to_string(OvpnCommand::Status(StatusFormat::V1)),
970 "status\n"
971 );
972 }
973
974 #[test]
975 fn encode_status_v3() {
976 assert_eq!(
977 encode_to_string(OvpnCommand::Status(StatusFormat::V3)),
978 "status 3\n"
979 );
980 }
981
982 #[test]
983 fn encode_signal() {
984 assert_eq!(
985 encode_to_string(OvpnCommand::Signal(Signal::SigUsr1)),
986 "signal SIGUSR1\n"
987 );
988 }
989
990 #[test]
991 fn encode_state_on_all() {
992 assert_eq!(
993 encode_to_string(OvpnCommand::StateStream(StreamMode::OnAll)),
994 "state on all\n"
995 );
996 }
997
998 #[test]
999 fn encode_state_recent() {
1000 assert_eq!(
1001 encode_to_string(OvpnCommand::StateStream(StreamMode::Recent(5))),
1002 "state 5\n"
1003 );
1004 }
1005
1006 #[test]
1007 fn encode_password_escaping() {
1008 let wire = encode_to_string(OvpnCommand::Password {
1011 auth_type: AuthType::PrivateKey,
1012 value: r#"foo\"bar"#.to_string(),
1013 });
1014 assert_eq!(wire, "password \"Private Key\" \"foo\\\\\\\"bar\"\n");
1015 }
1016
1017 #[test]
1018 fn encode_password_simple() {
1019 let wire = encode_to_string(OvpnCommand::Password {
1020 auth_type: AuthType::Auth,
1021 value: "hunter2".to_string(),
1022 });
1023 assert_eq!(wire, "password \"Auth\" \"hunter2\"\n");
1024 }
1025
1026 #[test]
1027 fn encode_client_auth_with_config() {
1028 let wire = encode_to_string(OvpnCommand::ClientAuth {
1029 cid: 42,
1030 kid: 0,
1031 config_lines: vec![
1032 "push \"route 10.0.0.0 255.255.0.0\"".to_string(),
1033 "push \"dhcp-option DNS 10.0.0.1\"".to_string(),
1034 ],
1035 });
1036 assert_eq!(
1037 wire,
1038 "client-auth 42 0\n\
1039 push \"route 10.0.0.0 255.255.0.0\"\n\
1040 push \"dhcp-option DNS 10.0.0.1\"\n\
1041 END\n"
1042 );
1043 }
1044
1045 #[test]
1046 fn encode_client_auth_empty_config() {
1047 let wire = encode_to_string(OvpnCommand::ClientAuth {
1048 cid: 1,
1049 kid: 0,
1050 config_lines: vec![],
1051 });
1052 assert_eq!(wire, "client-auth 1 0\nEND\n");
1053 }
1054
1055 #[test]
1056 fn encode_client_deny_with_client_reason() {
1057 let wire = encode_to_string(OvpnCommand::ClientDeny {
1058 cid: 5,
1059 kid: 0,
1060 reason: "cert revoked".to_string(),
1061 client_reason: Some("Your access has been revoked.".to_string()),
1062 });
1063 assert_eq!(
1064 wire,
1065 "client-deny 5 0 \"cert revoked\" \"Your access has been revoked.\"\n"
1066 );
1067 }
1068
1069 #[test]
1070 fn encode_rsa_sig() {
1071 let wire = encode_to_string(OvpnCommand::RsaSig {
1072 base64_lines: vec!["AAAA".to_string(), "BBBB".to_string()],
1073 });
1074 assert_eq!(wire, "rsa-sig\nAAAA\nBBBB\nEND\n");
1075 }
1076
1077 #[test]
1078 fn encode_remote_modify() {
1079 let wire = encode_to_string(OvpnCommand::Remote(RemoteAction::Modify {
1080 host: "vpn.example.com".to_string(),
1081 port: 1234,
1082 }));
1083 assert_eq!(wire, "remote MOD vpn.example.com 1234\n");
1084 }
1085
1086 #[test]
1087 fn encode_proxy_http_nct() {
1088 let wire = encode_to_string(OvpnCommand::Proxy(ProxyAction::Http {
1089 host: "proxy.local".to_string(),
1090 port: 8080,
1091 non_cleartext_only: true,
1092 }));
1093 assert_eq!(wire, "proxy HTTP proxy.local 8080 nct\n");
1094 }
1095
1096 #[test]
1097 fn encode_needok() {
1098 use crate::need_ok::NeedOkResponse;
1099 let wire = encode_to_string(OvpnCommand::NeedOk {
1100 name: "token-insertion-request".to_string(),
1101 response: NeedOkResponse::Ok,
1102 });
1103 assert_eq!(wire, "needok token-insertion-request ok\n");
1104 }
1105
1106 #[test]
1107 fn encode_needstr() {
1108 let wire = encode_to_string(OvpnCommand::NeedStr {
1109 name: "name".to_string(),
1110 value: "John".to_string(),
1111 });
1112 assert_eq!(wire, "needstr name \"John\"\n");
1113 }
1114
1115 #[test]
1116 fn encode_forget_passwords() {
1117 assert_eq!(
1118 encode_to_string(OvpnCommand::ForgetPasswords),
1119 "forget-passwords\n"
1120 );
1121 }
1122
1123 #[test]
1124 fn encode_hold_query() {
1125 assert_eq!(encode_to_string(OvpnCommand::HoldQuery), "hold\n");
1126 }
1127
1128 #[test]
1129 fn encode_echo_on_all() {
1130 assert_eq!(
1131 encode_to_string(OvpnCommand::Echo(StreamMode::OnAll)),
1132 "echo on all\n"
1133 );
1134 }
1135
1136 #[test]
1139 fn decode_success() {
1140 let msgs = decode_all("SUCCESS: pid=12345\n");
1141 assert_eq!(msgs.len(), 1);
1142 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "pid=12345"));
1143 }
1144
1145 #[test]
1146 fn decode_success_bare() {
1147 let msgs = decode_all("SUCCESS:\n");
1149 assert_eq!(msgs.len(), 1);
1150 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s.is_empty()));
1151 }
1152
1153 #[test]
1154 fn decode_error() {
1155 let msgs = decode_all("ERROR: unknown command\n");
1156 assert_eq!(msgs.len(), 1);
1157 assert!(matches!(&msgs[0], OvpnMessage::Error(s) if s == "unknown command"));
1158 }
1159
1160 #[test]
1161 fn decode_info_notification() {
1162 let msgs = decode_all(">INFO:OpenVPN Management Interface Version 5\n");
1163 assert_eq!(msgs.len(), 1);
1164 assert!(matches!(
1165 &msgs[0],
1166 OvpnMessage::Info(s) if s == "OpenVPN Management Interface Version 5"
1167 ));
1168 }
1169
1170 #[test]
1171 fn decode_state_notification() {
1172 let msgs = decode_all(">STATE:1234567890,CONNECTED,SUCCESS,,10.0.0.1\n");
1173 assert_eq!(msgs.len(), 1);
1174 match &msgs[0] {
1175 OvpnMessage::Notification(Notification::State {
1176 timestamp,
1177 name,
1178 description,
1179 local_ip,
1180 remote_ip,
1181 ..
1182 }) => {
1183 assert_eq!(*timestamp, 1234567890);
1184 assert_eq!(*name, OpenVpnState::Connected);
1185 assert_eq!(description, "SUCCESS");
1186 assert_eq!(local_ip, "");
1187 assert_eq!(remote_ip, "10.0.0.1");
1188 }
1189 other => panic!("unexpected: {other:?}"),
1190 }
1191 }
1192
1193 #[test]
1194 fn decode_multiline_with_command_tracking() {
1195 let msgs = encode_then_decode(
1199 OvpnCommand::Status(StatusFormat::V1),
1200 "OpenVPN CLIENT LIST\nCommon Name,Real Address\ntest,1.2.3.4:1234\nEND\n",
1201 );
1202 assert_eq!(msgs.len(), 1);
1203 match &msgs[0] {
1204 OvpnMessage::MultiLine(lines) => {
1205 assert_eq!(lines.len(), 3);
1206 assert_eq!(lines[0], "OpenVPN CLIENT LIST");
1207 assert_eq!(lines[2], "test,1.2.3.4:1234");
1208 }
1209 other => panic!("unexpected: {other:?}"),
1210 }
1211 }
1212
1213 #[test]
1214 fn decode_hold_query_success() {
1215 let msgs = encode_then_decode(OvpnCommand::HoldQuery, "SUCCESS: hold=0\n");
1217 assert_eq!(msgs.len(), 1);
1218 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "hold=0"));
1219 }
1220
1221 #[test]
1222 fn decode_bare_state_multiline() {
1223 let msgs = encode_then_decode(
1225 OvpnCommand::State,
1226 "1234567890,CONNECTED,SUCCESS,,10.0.0.1,,,,\nEND\n",
1227 );
1228 assert_eq!(msgs.len(), 1);
1229 match &msgs[0] {
1230 OvpnMessage::MultiLine(lines) => {
1231 assert_eq!(lines.len(), 1);
1232 assert!(lines[0].starts_with("1234567890"));
1233 }
1234 other => panic!("unexpected: {other:?}"),
1235 }
1236 }
1237
1238 #[test]
1239 fn decode_notification_during_multiline() {
1240 let msgs = encode_then_decode(
1243 OvpnCommand::Status(StatusFormat::V1),
1244 "header line\n>BYTECOUNT:1000,2000\ndata line\nEND\n",
1245 );
1246 assert_eq!(msgs.len(), 2);
1247 assert!(matches!(
1249 &msgs[0],
1250 OvpnMessage::Notification(Notification::ByteCount {
1251 bytes_in: 1000,
1252 bytes_out: 2000
1253 })
1254 ));
1255 match &msgs[1] {
1257 OvpnMessage::MultiLine(lines) => {
1258 assert_eq!(lines, &["header line", "data line"]);
1259 }
1260 other => panic!("unexpected: {other:?}"),
1261 }
1262 }
1263
1264 #[test]
1265 fn decode_client_connect_multiline_notification() {
1266 let input = "\
1267 >CLIENT:CONNECT,0,1\n\
1268 >CLIENT:ENV,untrusted_ip=1.2.3.4\n\
1269 >CLIENT:ENV,common_name=TestClient\n\
1270 >CLIENT:ENV,END\n";
1271 let msgs = decode_all(input);
1272 assert_eq!(msgs.len(), 1);
1273 match &msgs[0] {
1274 OvpnMessage::Notification(Notification::Client {
1275 event,
1276 cid,
1277 kid,
1278 env,
1279 }) => {
1280 assert_eq!(*event, ClientEvent::Connect);
1281 assert_eq!(*cid, 0);
1282 assert_eq!(*kid, Some(1));
1283 assert_eq!(env.len(), 2);
1284 assert_eq!(env[0], ("untrusted_ip".to_string(), "1.2.3.4".to_string()));
1285 assert_eq!(
1286 env[1],
1287 ("common_name".to_string(), "TestClient".to_string())
1288 );
1289 }
1290 other => panic!("unexpected: {other:?}"),
1291 }
1292 }
1293
1294 #[test]
1295 fn decode_client_address_single_line() {
1296 let msgs = decode_all(">CLIENT:ADDRESS,3,10.0.0.5,1\n");
1297 assert_eq!(msgs.len(), 1);
1298 match &msgs[0] {
1299 OvpnMessage::Notification(Notification::ClientAddress { cid, addr, primary }) => {
1300 assert_eq!(*cid, 3);
1301 assert_eq!(addr, "10.0.0.5");
1302 assert!(*primary);
1303 }
1304 other => panic!("unexpected: {other:?}"),
1305 }
1306 }
1307
1308 #[test]
1309 fn decode_client_disconnect() {
1310 let input = "\
1311 >CLIENT:DISCONNECT,5\n\
1312 >CLIENT:ENV,bytes_received=12345\n\
1313 >CLIENT:ENV,bytes_sent=67890\n\
1314 >CLIENT:ENV,END\n";
1315 let msgs = decode_all(input);
1316 assert_eq!(msgs.len(), 1);
1317 match &msgs[0] {
1318 OvpnMessage::Notification(Notification::Client {
1319 event,
1320 cid,
1321 kid,
1322 env,
1323 }) => {
1324 assert_eq!(*event, ClientEvent::Disconnect);
1325 assert_eq!(*cid, 5);
1326 assert_eq!(*kid, None);
1327 assert_eq!(env.len(), 2);
1328 }
1329 other => panic!("unexpected: {other:?}"),
1330 }
1331 }
1332
1333 #[test]
1334 fn decode_password_notification() {
1335 let msgs = decode_all(">PASSWORD:Need 'Auth' username/password\n");
1336 assert_eq!(msgs.len(), 1);
1337 match &msgs[0] {
1338 OvpnMessage::Notification(Notification::Password(PasswordNotification::NeedAuth {
1339 auth_type,
1340 })) => {
1341 assert_eq!(*auth_type, AuthType::Auth);
1342 }
1343 other => panic!("unexpected: {other:?}"),
1344 }
1345 }
1346
1347 #[test]
1348 fn quote_and_escape_special_chars() {
1349 assert_eq!(quote_and_escape(r#"foo"bar"#), r#""foo\"bar""#);
1350 assert_eq!(quote_and_escape(r"a\b"), r#""a\\b""#);
1351 assert_eq!(quote_and_escape("simple"), r#""simple""#);
1352 }
1353
1354 #[test]
1355 fn decode_empty_multiline() {
1356 let msgs = encode_then_decode(OvpnCommand::Status(StatusFormat::V1), "END\n");
1358 assert_eq!(msgs.len(), 1);
1359 assert!(matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines.is_empty()));
1360 }
1361
1362 #[test]
1363 fn decode_need_ok_notification() {
1364 let msgs = decode_all(
1365 ">NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your token\n",
1366 );
1367 assert_eq!(msgs.len(), 1);
1368 match &msgs[0] {
1369 OvpnMessage::Notification(Notification::NeedOk { name, message }) => {
1370 assert_eq!(name, "token-insertion-request");
1371 assert_eq!(message, "Please insert your token");
1372 }
1373 other => panic!("unexpected: {other:?}"),
1374 }
1375 }
1376
1377 #[test]
1378 fn decode_hold_notification() {
1379 let msgs = decode_all(">HOLD:Waiting for hold release\n");
1380 assert_eq!(msgs.len(), 1);
1381 match &msgs[0] {
1382 OvpnMessage::Notification(Notification::Hold { text }) => {
1383 assert_eq!(text, "Waiting for hold release");
1384 }
1385 other => panic!("unexpected: {other:?}"),
1386 }
1387 }
1388
1389 #[test]
1392 fn encode_raw_multiline() {
1393 assert_eq!(
1394 encode_to_string(OvpnCommand::RawMultiLine("custom-cmd arg".to_string())),
1395 "custom-cmd arg\n"
1396 );
1397 }
1398
1399 #[test]
1400 fn raw_multiline_expects_multiline_response() {
1401 let msgs = encode_then_decode(
1402 OvpnCommand::RawMultiLine("custom".to_string()),
1403 "line1\nline2\nEND\n",
1404 );
1405 assert_eq!(msgs.len(), 1);
1406 match &msgs[0] {
1407 OvpnMessage::MultiLine(lines) => {
1408 assert_eq!(lines, &["line1", "line2"]);
1409 }
1410 other => panic!("expected MultiLine, got: {other:?}"),
1411 }
1412 }
1413
1414 #[test]
1415 fn raw_multiline_sanitizes_newlines() {
1416 let wire = encode_to_string(OvpnCommand::RawMultiLine("cmd\ninjected".to_string()));
1417 assert_eq!(wire, "cmdinjected\n");
1418 }
1419
1420 #[test]
1423 #[should_panic(expected = "mid-accumulation")]
1424 fn encode_during_multiline_accumulation_panics() {
1425 let mut codec = OvpnCodec::new();
1426 let mut buf = BytesMut::new();
1427 codec
1429 .encode(OvpnCommand::Status(StatusFormat::V1), &mut buf)
1430 .unwrap();
1431 let mut dec = BytesMut::from("header line\n");
1433 let _ = codec.decode(&mut dec); codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1436 }
1437
1438 #[test]
1439 #[should_panic(expected = "mid-accumulation")]
1440 fn encode_during_client_notif_accumulation_panics() {
1441 let mut codec = OvpnCodec::new();
1442 let mut buf = BytesMut::new();
1443 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1445 let _ = codec.decode(&mut dec);
1446 codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1448 }
1449
1450 #[test]
1453 fn unlimited_accumulation_default() {
1454 let mut codec = OvpnCodec::new();
1455 let mut enc = BytesMut::new();
1456 codec
1457 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1458 .unwrap();
1459 let mut data = String::new();
1461 for i in 0..500 {
1462 data.push_str(&format!("line {i}\n"));
1463 }
1464 data.push_str("END\n");
1465 let mut dec = BytesMut::from(data.as_str());
1466 let mut msgs = Vec::new();
1467 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1468 msgs.push(msg);
1469 }
1470 assert_eq!(msgs.len(), 1);
1471 match &msgs[0] {
1472 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 500),
1473 other => panic!("expected MultiLine, got: {other:?}"),
1474 }
1475 }
1476
1477 #[test]
1478 fn multi_line_limit_exceeded() {
1479 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1480 let mut enc = BytesMut::new();
1481 codec
1482 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1483 .unwrap();
1484 let mut dec = BytesMut::from("a\nb\nc\nd\nEND\n");
1485 let result = loop {
1486 match codec.decode(&mut dec) {
1487 Ok(Some(msg)) => break Ok(msg),
1488 Ok(None) => continue,
1489 Err(e) => break Err(e),
1490 }
1491 };
1492 assert!(result.is_err(), "expected error when limit exceeded");
1493 let err = result.unwrap_err();
1494 assert!(
1495 err.to_string().contains("multi-line response"),
1496 "error should mention multi-line: {err}"
1497 );
1498 }
1499
1500 #[test]
1501 fn multi_line_limit_exact_boundary_passes() {
1502 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1503 let mut enc = BytesMut::new();
1504 codec
1505 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1506 .unwrap();
1507 let mut dec = BytesMut::from("a\nb\nc\nEND\n");
1509 let mut msgs = Vec::new();
1510 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1511 msgs.push(msg);
1512 }
1513 assert_eq!(msgs.len(), 1);
1514 match &msgs[0] {
1515 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 3),
1516 other => panic!("expected MultiLine, got: {other:?}"),
1517 }
1518 }
1519
1520 #[test]
1521 fn client_env_limit_exceeded() {
1522 let mut codec = OvpnCodec::new().with_max_client_env_entries(AccumulationLimit::Max(2));
1523 let mut dec = BytesMut::from(
1524 ">CLIENT:CONNECT,0,1\n\
1525 >CLIENT:ENV,a=1\n\
1526 >CLIENT:ENV,b=2\n\
1527 >CLIENT:ENV,c=3\n\
1528 >CLIENT:ENV,END\n",
1529 );
1530 let result = loop {
1531 match codec.decode(&mut dec) {
1532 Ok(Some(msg)) => break Ok(msg),
1533 Ok(None) => continue,
1534 Err(e) => break Err(e),
1535 }
1536 };
1537 assert!(
1538 result.is_err(),
1539 "expected error when client ENV limit exceeded"
1540 );
1541 let err = result.unwrap_err();
1542 assert!(
1543 err.to_string().contains("client ENV"),
1544 "error should mention client ENV: {err}"
1545 );
1546 }
1547
1548 #[test]
1551 fn utf8_error_resets_multiline_state() {
1552 let mut codec = OvpnCodec::new();
1553 let mut enc = BytesMut::new();
1554 codec
1555 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1556 .unwrap();
1557 let mut dec = BytesMut::from("header\n");
1559 assert!(codec.decode(&mut dec).unwrap().is_none());
1560 dec.extend_from_slice(b"bad \xff line\n");
1562 assert!(codec.decode(&mut dec).is_err());
1563 dec.extend_from_slice(b"SUCCESS: recovered\n");
1566 let msg = codec.decode(&mut dec).unwrap();
1567 match msg {
1568 Some(OvpnMessage::Success(ref s)) if s.contains("recovered") => {}
1569 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1570 }
1571 }
1572
1573 #[test]
1574 fn utf8_error_resets_client_notif_state() {
1575 let mut codec = OvpnCodec::new();
1576 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1578 assert!(codec.decode(&mut dec).unwrap().is_none());
1579 dec.extend_from_slice(b">CLIENT:ENV,\xff\n");
1581 assert!(codec.decode(&mut dec).is_err());
1582 dec.extend_from_slice(b"SUCCESS: ok\n");
1584 let msg = codec.decode(&mut dec).unwrap();
1585 match msg {
1586 Some(OvpnMessage::Success(_)) => {}
1587 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1588 }
1589 }
1590}