1use bytes::{BufMut, BytesMut};
2use std::borrow::Cow;
3use std::io;
4use tokio_util::codec::{Decoder, Encoder};
5use tracing::{debug, warn};
6
7use crate::command::{OvpnCommand, ResponseKind};
8use crate::kill_target::KillTarget;
9use crate::message::{Notification, OvpnMessage};
10use crate::proxy_action::ProxyAction;
11use crate::redacted::Redacted;
12use crate::remote_action::RemoteAction;
13use crate::status_format::StatusFormat;
14use crate::unrecognized::UnrecognizedKind;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum EncoderMode {
20 #[default]
25 Sanitize,
26
27 Strict,
34}
35
36#[derive(Debug, thiserror::Error)]
43pub enum EncodeError {
44 #[error("{0} contains characters unsafe for the management protocol (\\n, \\r, or \\0)")]
46 UnsafeCharacters(&'static str),
47
48 #[error("block body line equals \"END\", which would terminate the block early")]
50 EndInBlockBody,
51}
52
53const WIRE_UNSAFE: &[char] = &['\n', '\r', '\0'];
56
57fn wire_safe<'a>(
65 s: &'a str,
66 field: &'static str,
67 mode: EncoderMode,
68) -> Result<Cow<'a, str>, io::Error> {
69 if !s.contains(WIRE_UNSAFE) {
70 return Ok(Cow::Borrowed(s));
71 }
72 match mode {
73 EncoderMode::Sanitize => Ok(Cow::Owned(
74 s.chars().filter(|c| !WIRE_UNSAFE.contains(c)).collect(),
75 )),
76 EncoderMode::Strict => Err(io::Error::other(EncodeError::UnsafeCharacters(field))),
77 }
78}
79
80fn quote_and_escape(s: &str) -> String {
92 let mut out = String::with_capacity(s.len() + 2);
93 out.push('"');
94 for c in s.chars() {
95 match c {
96 '\\' => out.push_str("\\\\"),
97 '"' => out.push_str("\\\""),
98 _ => out.push(c),
99 }
100 }
101 out.push('"');
102 out
103}
104
105use crate::client_event::ClientEvent;
106use crate::log_level::LogLevel;
107use crate::openvpn_state::OpenVpnState;
108use crate::transport_protocol::TransportProtocol;
109
110#[derive(Debug)]
112struct ClientNotifAccum {
113 event: ClientEvent,
114 cid: u64,
115 kid: Option<u64>,
116 env: Vec<(String, String)>,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122pub enum AccumulationLimit {
123 Unlimited,
125 Max(usize),
127}
128
129pub struct OvpnCodec {
151 expected: ResponseKind,
157
158 multi_line_buf: Option<Vec<String>>,
160
161 client_notif: Option<ClientNotifAccum>,
164
165 max_multi_line_lines: AccumulationLimit,
167
168 max_client_env_entries: AccumulationLimit,
170
171 encoder_mode: EncoderMode,
173}
174
175impl OvpnCodec {
176 pub fn new() -> Self {
179 Self {
180 expected: ResponseKind::SuccessOrError,
185 multi_line_buf: None,
186 client_notif: None,
187 max_multi_line_lines: AccumulationLimit::Unlimited,
188 max_client_env_entries: AccumulationLimit::Unlimited,
189 encoder_mode: EncoderMode::default(),
190 }
191 }
192
193 pub fn with_max_multi_line_lines(mut self, limit: AccumulationLimit) -> Self {
196 self.max_multi_line_lines = limit;
197 self
198 }
199
200 pub fn with_max_client_env_entries(mut self, limit: AccumulationLimit) -> Self {
203 self.max_client_env_entries = limit;
204 self
205 }
206
207 pub fn with_encoder_mode(mut self, mode: EncoderMode) -> Self {
214 self.encoder_mode = mode;
215 self
216 }
217}
218
219fn check_accumulation_limit(
220 current_len: usize,
221 limit: AccumulationLimit,
222 what: &str,
223) -> Result<(), io::Error> {
224 if let AccumulationLimit::Max(max) = limit
225 && current_len >= max
226 {
227 return Err(io::Error::other(format!(
228 "{what} accumulation limit exceeded ({max})"
229 )));
230 }
231 Ok(())
232}
233
234impl Default for OvpnCodec {
235 fn default() -> Self {
236 Self::new()
237 }
238}
239
240impl Encoder<OvpnCommand> for OvpnCodec {
243 type Error = io::Error;
244
245 fn encode(&mut self, item: OvpnCommand, dst: &mut BytesMut) -> Result<(), Self::Error> {
246 debug_assert!(
247 self.multi_line_buf.is_none() && self.client_notif.is_none(),
248 "encode() called while the decoder is mid-accumulation \
249 (multi_line_buf or client_notif is active). \
250 Drain decode() before sending a new command."
251 );
252
253 self.expected = item.expected_response();
256 let label: &'static str = (&item).into();
257 debug!(cmd = %label, expected = ?self.expected, "encoding command");
258
259 let mode = self.encoder_mode;
260
261 match item {
262 OvpnCommand::Status(StatusFormat::V1) => write_line(dst, "status"),
264 OvpnCommand::Status(ref fmt) => write_line(dst, &format!("status {fmt}")),
265 OvpnCommand::State => write_line(dst, "state"),
266 OvpnCommand::StateStream(ref m) => write_line(dst, &format!("state {m}")),
267 OvpnCommand::Version => write_line(dst, "version"),
268 OvpnCommand::Pid => write_line(dst, "pid"),
269 OvpnCommand::Help => write_line(dst, "help"),
270 OvpnCommand::Net => write_line(dst, "net"),
271 OvpnCommand::Verb(Some(n)) => write_line(dst, &format!("verb {n}")),
272 OvpnCommand::Verb(None) => write_line(dst, "verb"),
273 OvpnCommand::Mute(Some(n)) => write_line(dst, &format!("mute {n}")),
274 OvpnCommand::Mute(None) => write_line(dst, "mute"),
275
276 OvpnCommand::Log(ref m) => write_line(dst, &format!("log {m}")),
278 OvpnCommand::Echo(ref m) => write_line(dst, &format!("echo {m}")),
279 OvpnCommand::ByteCount(n) => write_line(dst, &format!("bytecount {n}")),
280
281 OvpnCommand::Signal(sig) => write_line(dst, &format!("signal {sig}")),
283 OvpnCommand::Kill(KillTarget::CommonName(ref cn)) => {
284 write_line(dst, &format!("kill {}", wire_safe(cn, "kill CN", mode)?));
285 }
286 OvpnCommand::Kill(KillTarget::Address {
287 ref protocol,
288 ref ip,
289 port,
290 }) => {
291 write_line(
292 dst,
293 &format!(
294 "kill {}:{}:{port}",
295 wire_safe(protocol, "kill address protocol", mode)?,
296 wire_safe(ip, "kill address ip", mode)?
297 ),
298 );
299 }
300 OvpnCommand::HoldQuery => write_line(dst, "hold"),
301 OvpnCommand::HoldOn => write_line(dst, "hold on"),
302 OvpnCommand::HoldOff => write_line(dst, "hold off"),
303 OvpnCommand::HoldRelease => write_line(dst, "hold release"),
304
305 OvpnCommand::Username {
310 ref auth_type,
311 ref value,
312 } => {
313 let at = quote_and_escape(&wire_safe(
317 &auth_type.to_string(),
318 "username auth_type",
319 mode,
320 )?);
321 let val = quote_and_escape(&wire_safe(value.expose(), "username value", mode)?);
322 write_line(dst, &format!("username {at} {val}"));
323 }
324 OvpnCommand::Password {
325 ref auth_type,
326 ref value,
327 } => {
328 let at = quote_and_escape(&wire_safe(
329 &auth_type.to_string(),
330 "password auth_type",
331 mode,
332 )?);
333 let val = quote_and_escape(&wire_safe(value.expose(), "password value", mode)?);
334 write_line(dst, &format!("password {at} {val}"));
335 }
336 OvpnCommand::AuthRetry(auth_retry_mode) => {
337 write_line(dst, &format!("auth-retry {auth_retry_mode}"));
338 }
339 OvpnCommand::ForgetPasswords => write_line(dst, "forget-passwords"),
340
341 OvpnCommand::ChallengeResponse {
343 ref state_id,
344 ref response,
345 } => {
346 let sid = wire_safe(state_id, "challenge-response state_id", mode)?;
347 let resp = wire_safe(response.expose(), "challenge-response response", mode)?;
348 let value = format!("CRV1::{sid}::{resp}");
349 let escaped = quote_and_escape(&value);
350 write_line(dst, &format!("password \"Auth\" {escaped}"));
351 }
352 OvpnCommand::StaticChallengeResponse {
353 ref password_b64,
354 ref response_b64,
355 } => {
356 let pw = wire_safe(password_b64.expose(), "static-challenge password_b64", mode)?;
357 let resp = wire_safe(response_b64.expose(), "static-challenge response_b64", mode)?;
358 let value = format!("SCRV1:{pw}:{resp}");
359 let escaped = quote_and_escape(&value);
360 write_line(dst, &format!("password \"Auth\" {escaped}"));
361 }
362
363 OvpnCommand::NeedOk { ref name, response } => {
365 write_line(
366 dst,
367 &format!(
368 "needok {} {response}",
369 wire_safe(name, "needok name", mode)?
370 ),
371 );
372 }
373 OvpnCommand::NeedStr {
374 ref name,
375 ref value,
376 } => {
377 let escaped = quote_and_escape(&wire_safe(value, "needstr value", mode)?);
378 write_line(
379 dst,
380 &format!(
381 "needstr {} {escaped}",
382 wire_safe(name, "needstr name", mode)?
383 ),
384 );
385 }
386
387 OvpnCommand::Pkcs11IdCount => write_line(dst, "pkcs11-id-count"),
389 OvpnCommand::Pkcs11IdGet(idx) => write_line(dst, &format!("pkcs11-id-get {idx}")),
390
391 OvpnCommand::RsaSig { ref base64_lines } => {
399 write_block(dst, "rsa-sig", base64_lines, mode)?;
400 }
401
402 OvpnCommand::ClientAuth {
410 cid,
411 kid,
412 ref config_lines,
413 } => {
414 write_block(dst, &format!("client-auth {cid} {kid}"), config_lines, mode)?;
415 }
416
417 OvpnCommand::ClientAuthNt { cid, kid } => {
418 write_line(dst, &format!("client-auth-nt {cid} {kid}"));
419 }
420
421 OvpnCommand::ClientDeny {
422 cid,
423 kid,
424 ref reason,
425 ref client_reason,
426 } => {
427 let r = quote_and_escape(&wire_safe(reason, "client-deny reason", mode)?);
428 match client_reason {
429 Some(cr) => {
430 let cr_esc =
431 quote_and_escape(&wire_safe(cr, "client-deny client_reason", mode)?);
432 write_line(dst, &format!("client-deny {cid} {kid} {r} {cr_esc}"));
433 }
434 None => write_line(dst, &format!("client-deny {cid} {kid} {r}")),
435 }
436 }
437
438 OvpnCommand::ClientKill { cid, ref message } => match message {
439 Some(msg) => write_line(
440 dst,
441 &format!(
442 "client-kill {cid} {}",
443 wire_safe(msg, "client-kill message", mode)?
444 ),
445 ),
446 None => write_line(dst, &format!("client-kill {cid}")),
447 },
448
449 OvpnCommand::LoadStats => write_line(dst, "load-stats"),
451
452 OvpnCommand::ClientPendingAuth {
459 cid,
460 kid,
461 ref extra,
462 timeout,
463 } => write_line(
464 dst,
465 &format!(
466 "client-pending-auth {cid} {kid} {} {timeout}",
467 wire_safe(extra, "client-pending-auth extra", mode)?
468 ),
469 ),
470
471 OvpnCommand::CrResponse { ref response } => {
472 write_line(
473 dst,
474 &format!(
475 "cr-response {}",
476 wire_safe(response.expose(), "cr-response", mode)?
477 ),
478 );
479 }
480
481 OvpnCommand::Certificate { ref pem_lines } => {
483 write_block(dst, "certificate", pem_lines, mode)?;
484 }
485
486 OvpnCommand::Remote(RemoteAction::Accept) => write_line(dst, "remote ACCEPT"),
488 OvpnCommand::Remote(RemoteAction::Skip) => write_line(dst, "remote SKIP"),
489 OvpnCommand::Remote(RemoteAction::Modify { ref host, port }) => {
490 write_line(
491 dst,
492 &format!(
493 "remote MOD {} {port}",
494 wire_safe(host, "remote MOD host", mode)?
495 ),
496 );
497 }
498 OvpnCommand::Proxy(ProxyAction::None) => write_line(dst, "proxy NONE"),
499 OvpnCommand::Proxy(ProxyAction::Http {
500 ref host,
501 port,
502 non_cleartext_only,
503 }) => {
504 let nct = if non_cleartext_only { " nct" } else { "" };
505 write_line(
506 dst,
507 &format!(
508 "proxy HTTP {} {port}{nct}",
509 wire_safe(host, "proxy HTTP host", mode)?
510 ),
511 );
512 }
513 OvpnCommand::Proxy(ProxyAction::Socks { ref host, port }) => {
514 write_line(
515 dst,
516 &format!(
517 "proxy SOCKS {} {port}",
518 wire_safe(host, "proxy SOCKS host", mode)?
519 ),
520 );
521 }
522
523 OvpnCommand::ManagementPassword(ref pw) => {
527 write_line(dst, &wire_safe(pw.expose(), "management password", mode)?);
528 }
529
530 OvpnCommand::Exit => write_line(dst, "exit"),
532 OvpnCommand::Quit => write_line(dst, "quit"),
533
534 OvpnCommand::Raw(ref cmd) | OvpnCommand::RawMultiLine(ref cmd) => {
536 write_line(dst, &wire_safe(cmd, "raw command", mode)?);
537 }
538 }
539
540 Ok(())
541 }
542}
543
544fn write_line(dst: &mut BytesMut, s: &str) {
546 dst.reserve(s.len() + 1);
547 dst.put_slice(s.as_bytes());
548 dst.put_u8(b'\n');
549}
550
551fn write_block(
560 dst: &mut BytesMut,
561 header: &str,
562 lines: &[String],
563 mode: EncoderMode,
564) -> Result<(), io::Error> {
565 let total: usize = header.len() + 1 + lines.iter().map(|l| l.len() + 2).sum::<usize>() + 4;
566 dst.reserve(total);
567 dst.put_slice(header.as_bytes());
568 dst.put_u8(b'\n');
569 for line in lines {
570 let clean = wire_safe(line, "block body line", mode)?;
571 if *clean == *"END" {
572 match mode {
573 EncoderMode::Sanitize => {
574 dst.put_slice(b" END");
575 dst.put_u8(b'\n');
576 continue;
577 }
578 EncoderMode::Strict => {
579 return Err(io::Error::other(EncodeError::EndInBlockBody));
580 }
581 }
582 }
583 dst.put_slice(clean.as_bytes());
584 dst.put_u8(b'\n');
585 }
586 dst.put_slice(b"END\n");
587 Ok(())
588}
589
590impl Decoder for OvpnCodec {
593 type Item = OvpnMessage;
594 type Error = io::Error;
595
596 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
597 loop {
598 let Some(newline_pos) = src.iter().position(|&b| b == b'\n') else {
600 return Ok(None); };
602
603 let line_bytes = src.split_to(newline_pos + 1);
605 let line = match std::str::from_utf8(&line_bytes) {
606 Ok(s) => s,
607 Err(e) => {
608 self.multi_line_buf = None;
611 self.client_notif = None;
612 self.expected = ResponseKind::SuccessOrError;
613 return Err(io::Error::new(io::ErrorKind::InvalidData, e));
614 }
615 }
616 .trim_end_matches(['\r', '\n'])
617 .to_string();
618
619 if let Some(ref mut accum) = self.client_notif
628 && let Some(rest) = line.strip_prefix(">CLIENT:ENV,")
629 {
630 if rest == "END" {
631 let finished = self.client_notif.take().expect("guarded by if-let");
632 debug!(event = ?finished.event, cid = finished.cid, env_count = finished.env.len(), "decoded CLIENT notification");
633 return Ok(Some(OvpnMessage::Notification(Notification::Client {
634 event: finished.event,
635 cid: finished.cid,
636 kid: finished.kid,
637 env: finished.env,
638 })));
639 } else {
640 let (k, v) = rest
642 .split_once('=')
643 .map(|(k, v)| (k.to_string(), v.to_string()))
644 .unwrap_or_else(|| (rest.to_string(), String::new()));
645 check_accumulation_limit(
646 accum.env.len(),
647 self.max_client_env_entries,
648 "client ENV",
649 )?;
650 accum.env.push((k, v));
651 continue; }
653 }
654 if let Some(ref mut buf) = self.multi_line_buf {
659 if line == "END" {
660 let lines = self.multi_line_buf.take().expect("guarded by if-let");
661 debug!(line_count = lines.len(), "decoded multi-line response");
662 return Ok(Some(OvpnMessage::MultiLine(lines)));
663 }
664 if line.starts_with('>') {
669 if let Some(msg) = self.parse_notification(&line) {
670 return Ok(Some(msg));
671 }
672 continue;
675 }
676 check_accumulation_limit(
677 buf.len(),
678 self.max_multi_line_lines,
679 "multi-line response",
680 )?;
681 buf.push(line);
682 continue; }
684
685 if let Some(rest) = line.strip_prefix("SUCCESS:") {
691 return Ok(Some(OvpnMessage::Success(
692 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
693 )));
694 }
695 if let Some(rest) = line.strip_prefix("ERROR:") {
696 return Ok(Some(OvpnMessage::Error(
697 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
698 )));
699 }
700
701 if line == "ENTER PASSWORD:" {
703 return Ok(Some(OvpnMessage::PasswordPrompt));
704 }
705
706 if line.starts_with('>') {
708 if let Some(msg) = self.parse_notification(&line) {
709 return Ok(Some(msg));
710 }
711 continue;
713 }
714
715 match self.expected {
721 ResponseKind::MultiLine => {
722 if line == "END" {
723 return Ok(Some(OvpnMessage::MultiLine(Vec::new())));
725 }
726 self.multi_line_buf = Some(vec![line]);
727 continue; }
729 ResponseKind::SuccessOrError | ResponseKind::NoResponse => {
730 warn!(line = %line, "unrecognized line from server");
731 return Ok(Some(OvpnMessage::Unrecognized {
732 line,
733 kind: UnrecognizedKind::UnexpectedLine,
734 }));
735 }
736 }
737 }
738 }
739}
740
741impl OvpnCodec {
742 fn parse_notification(&mut self, line: &str) -> Option<OvpnMessage> {
746 let inner = &line[1..]; let Some((kind, payload)) = inner.split_once(':') else {
749 warn!(line = %line, "malformed notification (no colon)");
751 return Some(OvpnMessage::Unrecognized {
752 line: line.to_string(),
753 kind: UnrecognizedKind::MalformedNotification,
754 });
755 };
756
757 if kind == "INFO" {
760 return Some(OvpnMessage::Info(payload.to_string()));
761 }
762
763 if kind == "CLIENT" {
765 let (event, args) = payload
766 .split_once(',')
767 .map(|(e, a)| (e.to_string(), a.to_string()))
768 .unwrap_or_else(|| (payload.to_string(), String::new()));
769
770 if event == "ADDRESS" {
772 let mut parts = args.splitn(3, ',');
773 let cid = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
774 let addr = parts.next().unwrap_or("").to_string();
775 let primary = parts.next() == Some("1");
776 return Some(OvpnMessage::Notification(Notification::ClientAddress {
777 cid,
778 addr,
779 primary,
780 }));
781 }
782
783 let mut id_parts = args.splitn(3, ',');
787 let cid = id_parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
788 let kid = id_parts.next().and_then(|s| s.parse().ok());
789
790 let parsed_event = if event == "CR_RESPONSE" {
791 let response = id_parts.next().unwrap_or("").to_string();
792 ClientEvent::CrResponse(response)
793 } else {
794 ClientEvent::parse(&event)
795 };
796
797 self.client_notif = Some(ClientNotifAccum {
799 event: parsed_event,
800 cid,
801 kid,
802 env: Vec::new(),
803 });
804 return None; }
806
807 let notification = match kind {
809 "STATE" => parse_state(payload),
810 "BYTECOUNT" => parse_bytecount(payload),
811 "BYTECOUNT_CLI" => parse_bytecount_cli(payload),
812 "LOG" => parse_log(payload),
813 "ECHO" => parse_echo(payload),
814 "HOLD" => Some(Notification::Hold {
815 text: payload.to_string(),
816 }),
817 "FATAL" => Some(Notification::Fatal {
818 message: payload.to_string(),
819 }),
820 "PKCS11ID-COUNT" => parse_pkcs11id_count(payload),
821 "NEED-OK" => parse_need_ok(payload),
822 "NEED-STR" => parse_need_str(payload),
823 "RSA_SIGN" => Some(Notification::RsaSign {
824 data: payload.to_string(),
825 }),
826 "REMOTE" => parse_remote(payload),
827 "PROXY" => parse_proxy(payload),
828 "PASSWORD" => parse_password(payload),
829 "PKCS11ID-ENTRY" => {
830 return parse_pkcs11id_entry_notif(payload).or_else(|| {
831 Some(OvpnMessage::Notification(Notification::Simple {
832 kind: kind.to_string(),
833 payload: payload.to_string(),
834 }))
835 });
836 }
837 _ => None,
838 };
839
840 Some(OvpnMessage::Notification(notification.unwrap_or(
841 Notification::Simple {
842 kind: kind.to_string(),
843 payload: payload.to_string(),
844 },
845 )))
846 }
847}
848
849fn parse_state(payload: &str) -> Option<Notification> {
857 let mut parts = payload.splitn(9, ',');
861 let timestamp = parts.next()?.parse().ok()?;
862 let name = OpenVpnState::parse(parts.next()?);
863 let description = parts.next()?.to_string();
864 let local_ip = parts.next()?.to_string();
865 let remote_ip = parts.next()?.to_string();
866 let remote_port = parts.next().unwrap_or("").to_string();
867 let local_addr = parts.next().unwrap_or("").to_string();
868 let local_port = parts.next().unwrap_or("").to_string();
869 let local_ipv6 = parts.next().unwrap_or("").to_string();
870 Some(Notification::State {
871 timestamp,
872 name,
873 description,
874 local_ip,
875 remote_ip,
876 remote_port,
877 local_addr,
878 local_port,
879 local_ipv6,
880 })
881}
882
883fn parse_bytecount(payload: &str) -> Option<Notification> {
884 let (a, b) = payload.split_once(',')?;
885 Some(Notification::ByteCount {
886 bytes_in: a.parse().ok()?,
887 bytes_out: b.parse().ok()?,
888 })
889}
890
891fn parse_bytecount_cli(payload: &str) -> Option<Notification> {
892 let mut parts = payload.splitn(3, ',');
893 let cid = parts.next()?.parse().ok()?;
894 let bytes_in = parts.next()?.parse().ok()?;
895 let bytes_out = parts.next()?.parse().ok()?;
896 Some(Notification::ByteCountCli {
897 cid,
898 bytes_in,
899 bytes_out,
900 })
901}
902
903fn parse_log(payload: &str) -> Option<Notification> {
904 let (ts_str, rest) = payload.split_once(',')?;
905 let timestamp = ts_str.parse().ok()?;
906 let (level_str, message) = rest.split_once(',')?;
907 Some(Notification::Log {
908 timestamp,
909 level: LogLevel::parse(level_str),
910 message: message.to_string(),
911 })
912}
913
914fn parse_echo(payload: &str) -> Option<Notification> {
915 let (ts_str, param) = payload.split_once(',')?;
916 let timestamp = ts_str.parse().ok()?;
917 Some(Notification::Echo {
918 timestamp,
919 param: param.to_string(),
920 })
921}
922
923fn parse_pkcs11id_count(payload: &str) -> Option<Notification> {
924 let count = payload.trim().parse().ok()?;
925 Some(Notification::Pkcs11IdCount { count })
926}
927
928fn parse_pkcs11id_entry_notif(payload: &str) -> Option<OvpnMessage> {
931 let rest = payload.strip_prefix('\'')?;
932 let (index, rest) = rest.split_once("', ID:'")?;
933 let (id, rest) = rest.split_once("', BLOB:'")?;
934 let blob = rest.strip_suffix('\'')?;
935 Some(OvpnMessage::Pkcs11IdEntry {
936 index: index.to_string(),
937 id: id.to_string(),
938 blob: blob.to_string(),
939 })
940}
941
942fn parse_need_ok(payload: &str) -> Option<Notification> {
944 let rest = payload.strip_prefix("Need '")?;
946 let (name, rest) = rest.split_once('\'')?;
947 let msg = rest.split_once("MSG:")?.1;
948 Some(Notification::NeedOk {
949 name: name.to_string(),
950 message: msg.to_string(),
951 })
952}
953
954fn parse_need_str(payload: &str) -> Option<Notification> {
956 let rest = payload.strip_prefix("Need '")?;
957 let (name, rest) = rest.split_once('\'')?;
958 let msg = rest.split_once("MSG:")?.1;
959 Some(Notification::NeedStr {
960 name: name.to_string(),
961 message: msg.to_string(),
962 })
963}
964
965fn parse_remote(payload: &str) -> Option<Notification> {
966 let mut parts = payload.splitn(3, ',');
967 let host = parts.next()?.to_string();
968 let port = parts.next()?.parse().ok()?;
969 let protocol = TransportProtocol::parse(parts.next()?);
970 Some(Notification::Remote {
971 host,
972 port,
973 protocol,
974 })
975}
976
977fn parse_proxy(payload: &str) -> Option<Notification> {
978 let mut parts = payload.splitn(3, ',');
980 let index = parts.next()?.parse().ok()?;
981 let proxy_type = parts.next()?.to_string();
982 let host = parts.next()?.to_string();
983 Some(Notification::Proxy {
984 index,
985 proxy_type,
986 host,
987 })
988}
989
990use crate::message::PasswordNotification;
991
992use crate::auth::AuthType;
993
994fn parse_auth_type(s: &str) -> AuthType {
996 match s {
997 "Auth" => AuthType::Auth,
998 "Private Key" => AuthType::PrivateKey,
999 "HTTP Proxy" => AuthType::HttpProxy,
1000 "SOCKS Proxy" => AuthType::SocksProxy,
1001 other => AuthType::Custom(other.to_string()),
1002 }
1003}
1004
1005fn parse_password(payload: &str) -> Option<Notification> {
1006 if let Some(token) = payload.strip_prefix("Auth-Token:") {
1009 return Some(Notification::Password(PasswordNotification::AuthToken {
1010 token: Redacted::new(token),
1011 }));
1012 }
1013
1014 if let Some(rest) = payload.strip_prefix("Verification Failed: '") {
1017 if let Some((auth_part, crv1_part)) = rest.split_once("' ['CRV1:") {
1019 let _ = auth_part; let crv1_data = crv1_part.strip_suffix("']")?;
1021 let mut parts = crv1_data.splitn(4, ':');
1022 let flags = parts.next()?.to_string();
1023 let state_id = parts.next()?.to_string();
1024 let username_b64 = parts.next()?.to_string();
1025 let challenge = parts.next()?.to_string();
1026 return Some(Notification::Password(
1027 PasswordNotification::DynamicChallenge {
1028 flags,
1029 state_id,
1030 username_b64,
1031 challenge,
1032 },
1033 ));
1034 }
1035 let auth_type = rest.strip_suffix('\'')?;
1037 return Some(Notification::Password(
1038 PasswordNotification::VerificationFailed {
1039 auth_type: parse_auth_type(auth_type),
1040 },
1041 ));
1042 }
1043
1044 let rest = payload.strip_prefix("Need '")?;
1047 let (auth_type_str, rest) = rest.split_once('\'')?;
1048 let rest = rest.trim_start();
1049
1050 if let Some(after_up) = rest.strip_prefix("username/password") {
1051 let after_up = after_up.trim_start();
1052
1053 if let Some(sc) = after_up.strip_prefix("SC:") {
1056 let (flag_str, challenge) = sc.split_once(',')?;
1057 let flags: u32 = flag_str.parse().ok()?;
1058 return Some(Notification::Password(
1059 PasswordNotification::StaticChallenge {
1060 echo: flags & 1 != 0,
1061 response_concat: flags & 2 != 0,
1062 challenge: challenge.to_string(),
1063 },
1064 ));
1065 }
1066
1067 return Some(Notification::Password(PasswordNotification::NeedAuth {
1069 auth_type: parse_auth_type(auth_type_str),
1070 }));
1071 }
1072
1073 if rest.starts_with("password") {
1075 return Some(Notification::Password(PasswordNotification::NeedPassword {
1076 auth_type: parse_auth_type(auth_type_str),
1077 }));
1078 }
1079
1080 None }
1082
1083#[cfg(test)]
1084mod tests {
1085 use super::*;
1086 use crate::auth::AuthType;
1087 use crate::client_event::ClientEvent;
1088 use crate::message::PasswordNotification;
1089 use crate::signal::Signal;
1090 use crate::status_format::StatusFormat;
1091 use crate::stream_mode::StreamMode;
1092 use bytes::BytesMut;
1093 use tokio_util::codec::{Decoder, Encoder};
1094
1095 fn encode_to_string(cmd: OvpnCommand) -> String {
1097 let mut codec = OvpnCodec::new();
1098 let mut buf = BytesMut::new();
1099 codec.encode(cmd, &mut buf).unwrap();
1100 String::from_utf8(buf.to_vec()).unwrap()
1101 }
1102
1103 fn decode_all(input: &str) -> Vec<OvpnMessage> {
1105 let mut codec = OvpnCodec::new();
1106 let mut buf = BytesMut::from(input);
1107 let mut msgs = Vec::new();
1108 while let Some(msg) = codec.decode(&mut buf).unwrap() {
1109 msgs.push(msg);
1110 }
1111 msgs
1112 }
1113
1114 fn encode_then_decode(cmd: OvpnCommand, response: &str) -> Vec<OvpnMessage> {
1116 let mut codec = OvpnCodec::new();
1117 let mut enc_buf = BytesMut::new();
1118 codec.encode(cmd, &mut enc_buf).unwrap();
1119 let mut dec_buf = BytesMut::from(response);
1120 let mut msgs = Vec::new();
1121 while let Some(msg) = codec.decode(&mut dec_buf).unwrap() {
1122 msgs.push(msg);
1123 }
1124 msgs
1125 }
1126
1127 #[test]
1130 fn encode_status_v1() {
1131 assert_eq!(
1132 encode_to_string(OvpnCommand::Status(StatusFormat::V1)),
1133 "status\n"
1134 );
1135 }
1136
1137 #[test]
1138 fn encode_status_v3() {
1139 assert_eq!(
1140 encode_to_string(OvpnCommand::Status(StatusFormat::V3)),
1141 "status 3\n"
1142 );
1143 }
1144
1145 #[test]
1146 fn encode_signal() {
1147 assert_eq!(
1148 encode_to_string(OvpnCommand::Signal(Signal::SigUsr1)),
1149 "signal SIGUSR1\n"
1150 );
1151 }
1152
1153 #[test]
1154 fn encode_state_on_all() {
1155 assert_eq!(
1156 encode_to_string(OvpnCommand::StateStream(StreamMode::OnAll)),
1157 "state on all\n"
1158 );
1159 }
1160
1161 #[test]
1162 fn encode_state_recent() {
1163 assert_eq!(
1164 encode_to_string(OvpnCommand::StateStream(StreamMode::Recent(5))),
1165 "state 5\n"
1166 );
1167 }
1168
1169 #[test]
1170 fn encode_password_escaping() {
1171 let wire = encode_to_string(OvpnCommand::Password {
1174 auth_type: AuthType::PrivateKey,
1175 value: r#"foo\"bar"#.into(),
1176 });
1177 assert_eq!(wire, "password \"Private Key\" \"foo\\\\\\\"bar\"\n");
1178 }
1179
1180 #[test]
1181 fn encode_password_simple() {
1182 let wire = encode_to_string(OvpnCommand::Password {
1183 auth_type: AuthType::Auth,
1184 value: "hunter2".into(),
1185 });
1186 assert_eq!(wire, "password \"Auth\" \"hunter2\"\n");
1187 }
1188
1189 #[test]
1190 fn encode_client_auth_with_config() {
1191 let wire = encode_to_string(OvpnCommand::ClientAuth {
1192 cid: 42,
1193 kid: 0,
1194 config_lines: vec![
1195 "push \"route 10.0.0.0 255.255.0.0\"".to_string(),
1196 "push \"dhcp-option DNS 10.0.0.1\"".to_string(),
1197 ],
1198 });
1199 assert_eq!(
1200 wire,
1201 "client-auth 42 0\n\
1202 push \"route 10.0.0.0 255.255.0.0\"\n\
1203 push \"dhcp-option DNS 10.0.0.1\"\n\
1204 END\n"
1205 );
1206 }
1207
1208 #[test]
1209 fn encode_client_auth_empty_config() {
1210 let wire = encode_to_string(OvpnCommand::ClientAuth {
1211 cid: 1,
1212 kid: 0,
1213 config_lines: vec![],
1214 });
1215 assert_eq!(wire, "client-auth 1 0\nEND\n");
1216 }
1217
1218 #[test]
1219 fn encode_client_deny_with_client_reason() {
1220 let wire = encode_to_string(OvpnCommand::ClientDeny {
1221 cid: 5,
1222 kid: 0,
1223 reason: "cert revoked".to_string(),
1224 client_reason: Some("Your access has been revoked.".to_string()),
1225 });
1226 assert_eq!(
1227 wire,
1228 "client-deny 5 0 \"cert revoked\" \"Your access has been revoked.\"\n"
1229 );
1230 }
1231
1232 #[test]
1233 fn encode_rsa_sig() {
1234 let wire = encode_to_string(OvpnCommand::RsaSig {
1235 base64_lines: vec!["AAAA".to_string(), "BBBB".to_string()],
1236 });
1237 assert_eq!(wire, "rsa-sig\nAAAA\nBBBB\nEND\n");
1238 }
1239
1240 #[test]
1241 fn encode_remote_modify() {
1242 let wire = encode_to_string(OvpnCommand::Remote(RemoteAction::Modify {
1243 host: "vpn.example.com".to_string(),
1244 port: 1234,
1245 }));
1246 assert_eq!(wire, "remote MOD vpn.example.com 1234\n");
1247 }
1248
1249 #[test]
1250 fn encode_proxy_http_nct() {
1251 let wire = encode_to_string(OvpnCommand::Proxy(ProxyAction::Http {
1252 host: "proxy.local".to_string(),
1253 port: 8080,
1254 non_cleartext_only: true,
1255 }));
1256 assert_eq!(wire, "proxy HTTP proxy.local 8080 nct\n");
1257 }
1258
1259 #[test]
1260 fn encode_needok() {
1261 use crate::need_ok::NeedOkResponse;
1262 let wire = encode_to_string(OvpnCommand::NeedOk {
1263 name: "token-insertion-request".to_string(),
1264 response: NeedOkResponse::Ok,
1265 });
1266 assert_eq!(wire, "needok token-insertion-request ok\n");
1267 }
1268
1269 #[test]
1270 fn encode_needstr() {
1271 let wire = encode_to_string(OvpnCommand::NeedStr {
1272 name: "name".to_string(),
1273 value: "John".to_string(),
1274 });
1275 assert_eq!(wire, "needstr name \"John\"\n");
1276 }
1277
1278 #[test]
1279 fn encode_forget_passwords() {
1280 assert_eq!(
1281 encode_to_string(OvpnCommand::ForgetPasswords),
1282 "forget-passwords\n"
1283 );
1284 }
1285
1286 #[test]
1287 fn encode_hold_query() {
1288 assert_eq!(encode_to_string(OvpnCommand::HoldQuery), "hold\n");
1289 }
1290
1291 #[test]
1292 fn encode_echo_on_all() {
1293 assert_eq!(
1294 encode_to_string(OvpnCommand::Echo(StreamMode::OnAll)),
1295 "echo on all\n"
1296 );
1297 }
1298
1299 #[test]
1302 fn decode_success() {
1303 let msgs = decode_all("SUCCESS: pid=12345\n");
1304 assert_eq!(msgs.len(), 1);
1305 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "pid=12345"));
1306 }
1307
1308 #[test]
1309 fn decode_success_bare() {
1310 let msgs = decode_all("SUCCESS:\n");
1312 assert_eq!(msgs.len(), 1);
1313 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s.is_empty()));
1314 }
1315
1316 #[test]
1317 fn decode_error() {
1318 let msgs = decode_all("ERROR: unknown command\n");
1319 assert_eq!(msgs.len(), 1);
1320 assert!(matches!(&msgs[0], OvpnMessage::Error(s) if s == "unknown command"));
1321 }
1322
1323 #[test]
1324 fn decode_info_notification() {
1325 let msgs = decode_all(">INFO:OpenVPN Management Interface Version 5\n");
1326 assert_eq!(msgs.len(), 1);
1327 assert!(matches!(
1328 &msgs[0],
1329 OvpnMessage::Info(s) if s == "OpenVPN Management Interface Version 5"
1330 ));
1331 }
1332
1333 #[test]
1334 fn decode_state_notification() {
1335 let msgs = decode_all(">STATE:1234567890,CONNECTED,SUCCESS,,10.0.0.1\n");
1336 assert_eq!(msgs.len(), 1);
1337 match &msgs[0] {
1338 OvpnMessage::Notification(Notification::State {
1339 timestamp,
1340 name,
1341 description,
1342 local_ip,
1343 remote_ip,
1344 ..
1345 }) => {
1346 assert_eq!(*timestamp, 1234567890);
1347 assert_eq!(*name, OpenVpnState::Connected);
1348 assert_eq!(description, "SUCCESS");
1349 assert_eq!(local_ip, "");
1350 assert_eq!(remote_ip, "10.0.0.1");
1351 }
1352 other => panic!("unexpected: {other:?}"),
1353 }
1354 }
1355
1356 #[test]
1357 fn decode_multiline_with_command_tracking() {
1358 let msgs = encode_then_decode(
1362 OvpnCommand::Status(StatusFormat::V1),
1363 "OpenVPN CLIENT LIST\nCommon Name,Real Address\ntest,1.2.3.4:1234\nEND\n",
1364 );
1365 assert_eq!(msgs.len(), 1);
1366 match &msgs[0] {
1367 OvpnMessage::MultiLine(lines) => {
1368 assert_eq!(lines.len(), 3);
1369 assert_eq!(lines[0], "OpenVPN CLIENT LIST");
1370 assert_eq!(lines[2], "test,1.2.3.4:1234");
1371 }
1372 other => panic!("unexpected: {other:?}"),
1373 }
1374 }
1375
1376 #[test]
1377 fn decode_hold_query_success() {
1378 let msgs = encode_then_decode(OvpnCommand::HoldQuery, "SUCCESS: hold=0\n");
1380 assert_eq!(msgs.len(), 1);
1381 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "hold=0"));
1382 }
1383
1384 #[test]
1385 fn decode_bare_state_multiline() {
1386 let msgs = encode_then_decode(
1388 OvpnCommand::State,
1389 "1234567890,CONNECTED,SUCCESS,,10.0.0.1,,,,\nEND\n",
1390 );
1391 assert_eq!(msgs.len(), 1);
1392 match &msgs[0] {
1393 OvpnMessage::MultiLine(lines) => {
1394 assert_eq!(lines.len(), 1);
1395 assert!(lines[0].starts_with("1234567890"));
1396 }
1397 other => panic!("unexpected: {other:?}"),
1398 }
1399 }
1400
1401 #[test]
1402 fn decode_notification_during_multiline() {
1403 let msgs = encode_then_decode(
1406 OvpnCommand::Status(StatusFormat::V1),
1407 "header line\n>BYTECOUNT:1000,2000\ndata line\nEND\n",
1408 );
1409 assert_eq!(msgs.len(), 2);
1410 assert!(matches!(
1412 &msgs[0],
1413 OvpnMessage::Notification(Notification::ByteCount {
1414 bytes_in: 1000,
1415 bytes_out: 2000
1416 })
1417 ));
1418 match &msgs[1] {
1420 OvpnMessage::MultiLine(lines) => {
1421 assert_eq!(lines, &["header line", "data line"]);
1422 }
1423 other => panic!("unexpected: {other:?}"),
1424 }
1425 }
1426
1427 #[test]
1428 fn decode_client_connect_multiline_notification() {
1429 let input = "\
1430 >CLIENT:CONNECT,0,1\n\
1431 >CLIENT:ENV,untrusted_ip=1.2.3.4\n\
1432 >CLIENT:ENV,common_name=TestClient\n\
1433 >CLIENT:ENV,END\n";
1434 let msgs = decode_all(input);
1435 assert_eq!(msgs.len(), 1);
1436 match &msgs[0] {
1437 OvpnMessage::Notification(Notification::Client {
1438 event,
1439 cid,
1440 kid,
1441 env,
1442 }) => {
1443 assert_eq!(*event, ClientEvent::Connect);
1444 assert_eq!(*cid, 0);
1445 assert_eq!(*kid, Some(1));
1446 assert_eq!(env.len(), 2);
1447 assert_eq!(env[0], ("untrusted_ip".to_string(), "1.2.3.4".to_string()));
1448 assert_eq!(
1449 env[1],
1450 ("common_name".to_string(), "TestClient".to_string())
1451 );
1452 }
1453 other => panic!("unexpected: {other:?}"),
1454 }
1455 }
1456
1457 #[test]
1458 fn decode_client_address_single_line() {
1459 let msgs = decode_all(">CLIENT:ADDRESS,3,10.0.0.5,1\n");
1460 assert_eq!(msgs.len(), 1);
1461 match &msgs[0] {
1462 OvpnMessage::Notification(Notification::ClientAddress { cid, addr, primary }) => {
1463 assert_eq!(*cid, 3);
1464 assert_eq!(addr, "10.0.0.5");
1465 assert!(*primary);
1466 }
1467 other => panic!("unexpected: {other:?}"),
1468 }
1469 }
1470
1471 #[test]
1472 fn decode_client_disconnect() {
1473 let input = "\
1474 >CLIENT:DISCONNECT,5\n\
1475 >CLIENT:ENV,bytes_received=12345\n\
1476 >CLIENT:ENV,bytes_sent=67890\n\
1477 >CLIENT:ENV,END\n";
1478 let msgs = decode_all(input);
1479 assert_eq!(msgs.len(), 1);
1480 match &msgs[0] {
1481 OvpnMessage::Notification(Notification::Client {
1482 event,
1483 cid,
1484 kid,
1485 env,
1486 }) => {
1487 assert_eq!(*event, ClientEvent::Disconnect);
1488 assert_eq!(*cid, 5);
1489 assert_eq!(*kid, None);
1490 assert_eq!(env.len(), 2);
1491 }
1492 other => panic!("unexpected: {other:?}"),
1493 }
1494 }
1495
1496 #[test]
1497 fn decode_password_notification() {
1498 let msgs = decode_all(">PASSWORD:Need 'Auth' username/password\n");
1499 assert_eq!(msgs.len(), 1);
1500 match &msgs[0] {
1501 OvpnMessage::Notification(Notification::Password(PasswordNotification::NeedAuth {
1502 auth_type,
1503 })) => {
1504 assert_eq!(*auth_type, AuthType::Auth);
1505 }
1506 other => panic!("unexpected: {other:?}"),
1507 }
1508 }
1509
1510 #[test]
1511 fn quote_and_escape_special_chars() {
1512 assert_eq!(quote_and_escape(r#"foo"bar"#), r#""foo\"bar""#);
1513 assert_eq!(quote_and_escape(r"a\b"), r#""a\\b""#);
1514 assert_eq!(quote_and_escape("simple"), r#""simple""#);
1515 }
1516
1517 #[test]
1518 fn decode_empty_multiline() {
1519 let msgs = encode_then_decode(OvpnCommand::Status(StatusFormat::V1), "END\n");
1521 assert_eq!(msgs.len(), 1);
1522 assert!(matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines.is_empty()));
1523 }
1524
1525 #[test]
1526 fn decode_need_ok_notification() {
1527 let msgs = decode_all(
1528 ">NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your token\n",
1529 );
1530 assert_eq!(msgs.len(), 1);
1531 match &msgs[0] {
1532 OvpnMessage::Notification(Notification::NeedOk { name, message }) => {
1533 assert_eq!(name, "token-insertion-request");
1534 assert_eq!(message, "Please insert your token");
1535 }
1536 other => panic!("unexpected: {other:?}"),
1537 }
1538 }
1539
1540 #[test]
1541 fn decode_hold_notification() {
1542 let msgs = decode_all(">HOLD:Waiting for hold release\n");
1543 assert_eq!(msgs.len(), 1);
1544 match &msgs[0] {
1545 OvpnMessage::Notification(Notification::Hold { text }) => {
1546 assert_eq!(text, "Waiting for hold release");
1547 }
1548 other => panic!("unexpected: {other:?}"),
1549 }
1550 }
1551
1552 #[test]
1555 fn encode_raw_multiline() {
1556 assert_eq!(
1557 encode_to_string(OvpnCommand::RawMultiLine("custom-cmd arg".to_string())),
1558 "custom-cmd arg\n"
1559 );
1560 }
1561
1562 #[test]
1563 fn raw_multiline_expects_multiline_response() {
1564 let msgs = encode_then_decode(
1565 OvpnCommand::RawMultiLine("custom".to_string()),
1566 "line1\nline2\nEND\n",
1567 );
1568 assert_eq!(msgs.len(), 1);
1569 match &msgs[0] {
1570 OvpnMessage::MultiLine(lines) => {
1571 assert_eq!(lines, &["line1", "line2"]);
1572 }
1573 other => panic!("expected MultiLine, got: {other:?}"),
1574 }
1575 }
1576
1577 #[test]
1578 fn raw_multiline_sanitizes_newlines() {
1579 let wire = encode_to_string(OvpnCommand::RawMultiLine("cmd\ninjected".to_string()));
1581 assert_eq!(wire, "cmdinjected\n");
1582 }
1583
1584 #[test]
1585 fn raw_multiline_strict_rejects_newlines() {
1586 let mut codec = OvpnCodec::new().with_encoder_mode(EncoderMode::Strict);
1587 let mut buf = BytesMut::new();
1588 let result = codec.encode(
1589 OvpnCommand::RawMultiLine("cmd\ninjected".to_string()),
1590 &mut buf,
1591 );
1592 assert!(result.is_err());
1593 }
1594
1595 #[test]
1598 #[should_panic(expected = "mid-accumulation")]
1599 fn encode_during_multiline_accumulation_panics() {
1600 let mut codec = OvpnCodec::new();
1601 let mut buf = BytesMut::new();
1602 codec
1604 .encode(OvpnCommand::Status(StatusFormat::V1), &mut buf)
1605 .unwrap();
1606 let mut dec = BytesMut::from("header line\n");
1608 let _ = codec.decode(&mut dec); codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1611 }
1612
1613 #[test]
1614 #[should_panic(expected = "mid-accumulation")]
1615 fn encode_during_client_notif_accumulation_panics() {
1616 let mut codec = OvpnCodec::new();
1617 let mut buf = BytesMut::new();
1618 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1620 let _ = codec.decode(&mut dec);
1621 codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1623 }
1624
1625 #[test]
1628 fn unlimited_accumulation_default() {
1629 let mut codec = OvpnCodec::new();
1630 let mut enc = BytesMut::new();
1631 codec
1632 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1633 .unwrap();
1634 let mut data = String::new();
1636 for i in 0..500 {
1637 data.push_str(&format!("line {i}\n"));
1638 }
1639 data.push_str("END\n");
1640 let mut dec = BytesMut::from(data.as_str());
1641 let mut msgs = Vec::new();
1642 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1643 msgs.push(msg);
1644 }
1645 assert_eq!(msgs.len(), 1);
1646 match &msgs[0] {
1647 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 500),
1648 other => panic!("expected MultiLine, got: {other:?}"),
1649 }
1650 }
1651
1652 #[test]
1653 fn multi_line_limit_exceeded() {
1654 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1655 let mut enc = BytesMut::new();
1656 codec
1657 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1658 .unwrap();
1659 let mut dec = BytesMut::from("a\nb\nc\nd\nEND\n");
1660 let result = loop {
1661 match codec.decode(&mut dec) {
1662 Ok(Some(msg)) => break Ok(msg),
1663 Ok(None) => continue,
1664 Err(e) => break Err(e),
1665 }
1666 };
1667 assert!(result.is_err(), "expected error when limit exceeded");
1668 let err = result.unwrap_err();
1669 assert!(
1670 err.to_string().contains("multi-line response"),
1671 "error should mention multi-line: {err}"
1672 );
1673 }
1674
1675 #[test]
1676 fn multi_line_limit_exact_boundary_passes() {
1677 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1678 let mut enc = BytesMut::new();
1679 codec
1680 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1681 .unwrap();
1682 let mut dec = BytesMut::from("a\nb\nc\nEND\n");
1684 let mut msgs = Vec::new();
1685 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1686 msgs.push(msg);
1687 }
1688 assert_eq!(msgs.len(), 1);
1689 match &msgs[0] {
1690 OvpnMessage::MultiLine(lines) => assert_eq!(lines.len(), 3),
1691 other => panic!("expected MultiLine, got: {other:?}"),
1692 }
1693 }
1694
1695 #[test]
1696 fn client_env_limit_exceeded() {
1697 let mut codec = OvpnCodec::new().with_max_client_env_entries(AccumulationLimit::Max(2));
1698 let mut dec = BytesMut::from(
1699 ">CLIENT:CONNECT,0,1\n\
1700 >CLIENT:ENV,a=1\n\
1701 >CLIENT:ENV,b=2\n\
1702 >CLIENT:ENV,c=3\n\
1703 >CLIENT:ENV,END\n",
1704 );
1705 let result = loop {
1706 match codec.decode(&mut dec) {
1707 Ok(Some(msg)) => break Ok(msg),
1708 Ok(None) => continue,
1709 Err(e) => break Err(e),
1710 }
1711 };
1712 assert!(
1713 result.is_err(),
1714 "expected error when client ENV limit exceeded"
1715 );
1716 let err = result.unwrap_err();
1717 assert!(
1718 err.to_string().contains("client ENV"),
1719 "error should mention client ENV: {err}"
1720 );
1721 }
1722
1723 #[test]
1726 fn utf8_error_resets_multiline_state() {
1727 let mut codec = OvpnCodec::new();
1728 let mut enc = BytesMut::new();
1729 codec
1730 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1731 .unwrap();
1732 let mut dec = BytesMut::from("header\n");
1734 assert!(codec.decode(&mut dec).unwrap().is_none());
1735 dec.extend_from_slice(b"bad \xff line\n");
1737 assert!(codec.decode(&mut dec).is_err());
1738 dec.extend_from_slice(b"SUCCESS: recovered\n");
1741 let msg = codec.decode(&mut dec).unwrap();
1742 match msg {
1743 Some(OvpnMessage::Success(ref s)) if s.contains("recovered") => {}
1744 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1745 }
1746 }
1747
1748 #[test]
1749 fn utf8_error_resets_client_notif_state() {
1750 let mut codec = OvpnCodec::new();
1751 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1753 assert!(codec.decode(&mut dec).unwrap().is_none());
1754 dec.extend_from_slice(b">CLIENT:ENV,\xff\n");
1756 assert!(codec.decode(&mut dec).is_err());
1757 dec.extend_from_slice(b"SUCCESS: ok\n");
1759 let msg = codec.decode(&mut dec).unwrap();
1760 match msg {
1761 Some(OvpnMessage::Success(_)) => {}
1762 other => panic!("expected Success after UTF-8 reset, got: {other:?}"),
1763 }
1764 }
1765}