1use std::{borrow::Cow, collections::BTreeMap, collections::VecDeque, io};
2
3use bytes::{Buf, BufMut, BytesMut};
4use tokio_util::codec::{Decoder, Encoder};
5use tracing::{debug, warn};
6
7use crate::{
8 auth::AuthType,
9 client_event::ClientEvent,
10 command::{OvpnCommand, ResponseKind},
11 kill_target::KillTarget,
12 log_level::LogLevel,
13 message::{Notification, OvpnMessage, PasswordNotification},
14 openvpn_state::OpenVpnState,
15 proxy_action::ProxyAction,
16 redacted::Redacted,
17 remote_action::RemoteAction,
18 status_format::StatusFormat,
19 transport_protocol::TransportProtocol,
20 unrecognized::UnrecognizedKind,
21};
22
23const WIRE_UNSAFE: &[char] = &['\n', '\r', '\0'];
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30pub enum EncoderMode {
31 #[default]
36 Sanitize,
37
38 Strict,
45}
46
47#[derive(Debug, thiserror::Error)]
52pub enum EncodeError {
53 #[error("{0} contains characters unsafe for the management protocol (\\n, \\r, or \\0)")]
55 UnsafeCharacters(&'static str),
56
57 #[error("block body line equals \"END\", which would terminate the block early")]
59 EndInBlockBody,
60}
61
62fn wire_safe<'a>(
70 s: &'a str,
71 field: &'static str,
72 mode: EncoderMode,
73) -> Result<Cow<'a, str>, io::Error> {
74 if !s.contains(WIRE_UNSAFE) {
75 return Ok(Cow::Borrowed(s));
76 }
77 match mode {
78 EncoderMode::Sanitize => Ok(Cow::Owned(
79 s.chars().filter(|chr| !WIRE_UNSAFE.contains(chr)).collect(),
80 )),
81 EncoderMode::Strict => Err(io::Error::other(EncodeError::UnsafeCharacters(field))),
82 }
83}
84
85fn escape(s: &str) -> String {
93 let mut out = String::with_capacity(s.len());
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
102}
103
104fn quote(s: &str) -> String {
110 format!("\"{s}\"")
111}
112
113#[derive(Debug)]
115struct ClientNotificationAccumulator {
116 event: ClientEvent,
117 cid: u64,
118 kid: Option<u64>,
119 env: BTreeMap<String, String>,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
125pub enum AccumulationLimit {
126 Unlimited,
128
129 Max(usize),
131}
132
133#[derive(better_default::Default)]
173pub struct OvpnCodec {
174 expected_queue: VecDeque<ResponseKind>,
184
185 multi_line_buf: Option<Vec<String>>,
187
188 client_notification: Option<ClientNotificationAccumulator>,
191
192 #[default(AccumulationLimit::Max(10_000))]
199 max_multi_line_lines: AccumulationLimit,
200
201 #[default(AccumulationLimit::Unlimited)]
203 max_client_env_entries: AccumulationLimit,
204
205 encoder_mode: EncoderMode,
207
208 seen_info: bool,
212}
213
214impl OvpnCodec {
215 pub fn new() -> Self {
218 Self::default()
219 }
220
221 pub fn with_max_multi_line_lines(mut self, limit: AccumulationLimit) -> Self {
224 self.max_multi_line_lines = limit;
225 self
226 }
227
228 pub fn with_max_client_env_entries(mut self, limit: AccumulationLimit) -> Self {
231 self.max_client_env_entries = limit;
232 self
233 }
234
235 pub fn with_encoder_mode(mut self, mode: EncoderMode) -> Self {
242 self.encoder_mode = mode;
243 self
244 }
245
246 fn expected_front(&self) -> ResponseKind {
250 self.expected_queue
251 .front()
252 .copied()
253 .unwrap_or(ResponseKind::SuccessOrError)
254 }
255
256 fn consume_expected(&mut self) {
259 self.expected_queue.pop_front();
260 }
261}
262
263#[derive(Debug, thiserror::Error)]
265#[error("{what} accumulation limit exceeded ({max})")]
266struct AccumulationLimitExceeded {
267 what: &'static str,
268 max: usize,
269}
270
271fn check_accumulation_limit(
272 current_len: usize,
273 limit: AccumulationLimit,
274 what: &'static str,
275) -> Result<(), io::Error> {
276 if let AccumulationLimit::Max(max) = limit
277 && current_len >= max
278 {
279 return Err(io::Error::other(AccumulationLimitExceeded { what, max }));
280 }
281 Ok(())
282}
283
284impl Encoder<OvpnCommand> for OvpnCodec {
287 type Error = io::Error;
288
289 fn encode(&mut self, item: OvpnCommand, dst: &mut BytesMut) -> Result<(), Self::Error> {
290 if self.multi_line_buf.is_some() || self.client_notification.is_some() {
291 warn!(
292 "encode() called while the decoder is mid-accumulation \
293 (multi_line_buf or client_notification is active). \
294 Drain decode() before sending a new command."
295 );
296 }
297
298 let response_kind = item.expected_response();
301 self.expected_queue.push_back(response_kind);
302 let cmd: &str = (&item).into();
303 debug!(%cmd, expected = ?response_kind, queue_depth = self.expected_queue.len(), "encoding command");
304
305 let mode = self.encoder_mode;
306
307 match item {
308 OvpnCommand::Status(StatusFormat::V1) => write_line(dst, "status"),
310 OvpnCommand::Status(ref fmt) => write_line(dst, &format!("status {fmt}")),
311 OvpnCommand::State => write_line(dst, "state"),
312 OvpnCommand::StateStream(ref m) => write_line(dst, &format!("state {m}")),
313 OvpnCommand::Version => write_line(dst, "version"),
314 OvpnCommand::SetVersion(n) => write_line(dst, &format!("version {n}")),
315 OvpnCommand::Pid => write_line(dst, "pid"),
316 OvpnCommand::Help => write_line(dst, "help"),
317 OvpnCommand::Net => write_line(dst, "net"),
318 OvpnCommand::Verb(Some(n)) => write_line(dst, &format!("verb {n}")),
319 OvpnCommand::Verb(None) => write_line(dst, "verb"),
320 OvpnCommand::Mute(Some(n)) => write_line(dst, &format!("mute {n}")),
321 OvpnCommand::Mute(None) => write_line(dst, "mute"),
322
323 OvpnCommand::Log(ref m) => write_line(dst, &format!("log {m}")),
325 OvpnCommand::Echo(ref m) => write_line(dst, &format!("echo {m}")),
326 OvpnCommand::ByteCount(n) => write_line(dst, &format!("bytecount {n}")),
327
328 OvpnCommand::Signal(sig) => write_line(dst, &format!("signal {sig}")),
330 OvpnCommand::Kill(KillTarget::CommonName(ref common_name)) => {
331 let kill = format!("kill {}", wire_safe(common_name, "kill CN", mode)?);
332 write_line(dst, &kill);
333 }
334 OvpnCommand::Kill(KillTarget::Address {
335 ref protocol,
336 ref ip,
337 port,
338 }) => {
339 let safe_ip = wire_safe(ip, "kill address ip", mode)?;
340 write_line(dst, &format!("kill {protocol}:{safe_ip}:{port}",));
341 }
342 OvpnCommand::HoldQuery => write_line(dst, "hold"),
343 OvpnCommand::HoldOn => write_line(dst, "hold on"),
344 OvpnCommand::HoldOff => write_line(dst, "hold off"),
345 OvpnCommand::HoldRelease => write_line(dst, "hold release"),
346
347 OvpnCommand::Username {
352 ref auth_type,
353 ref value,
354 } => {
355 let auth_quoted = quote(&escape(&wire_safe(
359 &auth_type.to_string(),
360 "username auth_type",
361 mode,
362 )?));
363 let val = quote(&escape(&wire_safe(value.expose(), "username value", mode)?));
364 write_line(dst, &format!("username {auth_quoted} {val}"));
365 }
366 OvpnCommand::Password {
367 ref auth_type,
368 ref value,
369 } => {
370 let auth_quoted = quote(&escape(&wire_safe(
371 &auth_type.to_string(),
372 "password auth_type",
373 mode,
374 )?));
375 let val = quote(&escape(&wire_safe(value.expose(), "password value", mode)?));
376 write_line(dst, &format!("password {auth_quoted} {val}"));
377 }
378 OvpnCommand::AuthRetry(auth_retry_mode) => {
379 write_line(dst, &format!("auth-retry {auth_retry_mode}"));
380 }
381 OvpnCommand::ForgetPasswords => write_line(dst, "forget-passwords"),
382
383 OvpnCommand::ChallengeResponse {
385 ref state_id,
386 ref response,
387 } => {
388 let sid = wire_safe(state_id, "challenge-response state_id", mode)?;
389 let resp = wire_safe(response.expose(), "challenge-response response", mode)?;
390 let value = format!("CRV1::{sid}::{resp}");
391 let escaped = quote(&escape(&value));
392 write_line(dst, &format!("password \"Auth\" {escaped}"));
393 }
394 OvpnCommand::StaticChallengeResponse {
395 ref password_b64,
396 ref response_b64,
397 } => {
398 let password =
399 wire_safe(password_b64.expose(), "static-challenge password_b64", mode)?;
400 let resp = wire_safe(response_b64.expose(), "static-challenge response_b64", mode)?;
401 let value = format!("SCRV1:{password}:{resp}");
402 let escaped = quote(&escape(&value));
403 write_line(dst, &format!("password \"Auth\" {escaped}"));
404 }
405
406 OvpnCommand::NeedOk { ref name, response } => {
408 let name = wire_safe(name, "needok name", mode)?;
409 write_line(dst, &format!("needok {name} {response}"));
410 }
411 OvpnCommand::NeedStr {
412 ref name,
413 ref value,
414 } => {
415 let name = wire_safe(name, "needstr name", mode)?;
416 let escaped = quote(&escape(&wire_safe(value, "needstr value", mode)?));
417 write_line(dst, &format!("needstr {name} {escaped}"));
418 }
419
420 OvpnCommand::Pkcs11IdCount => write_line(dst, "pkcs11-id-count"),
422 OvpnCommand::Pkcs11IdGet(idx) => write_line(dst, &format!("pkcs11-id-get {idx}")),
423
424 OvpnCommand::RsaSig { ref base64_lines } => {
432 write_block(dst, "rsa-sig", base64_lines, mode)?;
433 }
434
435 OvpnCommand::PkSig { ref base64_lines } => {
437 write_block(dst, "pk-sig", base64_lines, mode)?;
438 }
439
440 OvpnCommand::EnvFilter(level) => write_line(dst, &format!("env-filter {level}")),
442
443 OvpnCommand::RemoteEntryCount => write_line(dst, "remote-entry-count"),
445 OvpnCommand::RemoteEntryGet(ref range) => {
446 write_line(dst, &format!("remote-entry-get {range}"));
447 }
448
449 OvpnCommand::PushUpdateBroad { ref options } => {
451 let options = wire_safe(options, "push-update-broad options", mode)?;
452 let opts = quote(&escape(&options));
453 write_line(dst, &format!("push-update-broad {opts}"));
454 }
455 OvpnCommand::PushUpdateCid { cid, ref options } => {
456 let options = wire_safe(options, "push-update-cid options", mode)?;
457 let opts = quote(&escape(&options));
458 write_line(dst, &format!("push-update-cid {cid} {opts}"));
459 }
460
461 OvpnCommand::ClientAuth {
469 cid,
470 kid,
471 ref config_lines,
472 } => {
473 write_block(dst, &format!("client-auth {cid} {kid}"), config_lines, mode)?;
474 }
475
476 OvpnCommand::ClientAuthNt { cid, kid } => {
477 write_line(dst, &format!("client-auth-nt {cid} {kid}"));
478 }
479
480 OvpnCommand::ClientDeny(ref deny) => {
481 let reason_quoted = quote(&escape(&wire_safe(
482 &deny.reason,
483 "client-deny reason",
484 mode,
485 )?));
486 match deny.client_reason {
487 Some(ref client_reason_str) => {
488 let options =
489 wire_safe(client_reason_str, "client-deny client_reason", mode)?;
490 let client_reason_quoted = quote(&escape(&options));
491 write_line(
492 dst,
493 &format!(
494 "client-deny {} {} {reason_quoted} {client_reason_quoted}",
495 deny.cid, deny.kid
496 ),
497 );
498 }
499 None => write_line(
500 dst,
501 &format!("client-deny {} {} {reason_quoted}", deny.cid, deny.kid),
502 ),
503 }
504 }
505
506 OvpnCommand::ClientKill { cid, ref message } => match message {
507 Some(msg) => write_line(
508 dst,
509 &format!(
510 "client-kill {cid} {}",
511 wire_safe(msg, "client-kill message", mode)?
512 ),
513 ),
514 None => write_line(dst, &format!("client-kill {cid}")),
515 },
516
517 OvpnCommand::LoadStats => write_line(dst, "load-stats"),
519
520 OvpnCommand::ClientPendingAuth {
522 cid,
523 kid,
524 ref extra,
525 timeout,
526 } => {
527 if extra.len() > 245 {
530 warn!(
531 len = extra.len(),
532 max = 245,
533 "client-pending-auth extra exceeds 245-character limit; \
534 OpenVPN may truncate or reject it"
535 );
536 }
537 let extra = wire_safe(extra, "client-pending-auth extra", mode)?;
538 let pending_auth = format!("client-pending-auth {cid} {kid} {extra} {timeout}");
539 write_line(dst, &pending_auth)
540 }
541
542 OvpnCommand::CrResponse { ref response } => {
543 let response = wire_safe(response.expose(), "cr-response", mode)?;
544 write_line(dst, &format!("cr-response {response}"));
545 }
546
547 OvpnCommand::Certificate { ref pem_lines } => {
549 write_block(dst, "certificate", pem_lines, mode)?;
550 }
551
552 OvpnCommand::Remote(RemoteAction::Accept) => write_line(dst, "remote ACCEPT"),
554 OvpnCommand::Remote(RemoteAction::Skip) => write_line(dst, "remote SKIP"),
555 OvpnCommand::Remote(RemoteAction::SkipN(n)) => {
556 write_line(dst, &format!("remote SKIP {n}"));
557 }
558 OvpnCommand::Remote(RemoteAction::Modify { ref host, port }) => {
559 let host = wire_safe(host, "remote MOD host", mode)?;
560 write_line(dst, &format!("remote MOD {host} {port}"));
561 }
562 OvpnCommand::Proxy(ProxyAction::None) => write_line(dst, "proxy NONE"),
563 OvpnCommand::Proxy(ProxyAction::Http {
564 ref host,
565 port,
566 non_cleartext_only,
567 }) => {
568 let nct = if non_cleartext_only { " nct" } else { "" };
569 let host = wire_safe(host, "proxy HTTP host", mode)?;
570 write_line(dst, &format!("proxy HTTP {host} {port}{nct}"));
571 }
572 OvpnCommand::Proxy(ProxyAction::Socks { ref host, port }) => {
573 let host = wire_safe(host, "proxy SOCKS host", mode)?;
574 write_line(dst, &format!("proxy SOCKS {host} {port}"));
575 }
576
577 OvpnCommand::ManagementPassword(ref pw) => {
581 write_line(dst, &wire_safe(pw.expose(), "management password", mode)?);
582 }
583
584 OvpnCommand::Exit => write_line(dst, "exit"),
586 OvpnCommand::Quit => write_line(dst, "quit"),
587
588 OvpnCommand::Raw(ref cmd) | OvpnCommand::RawMultiLine(ref cmd) => {
590 write_line(dst, &wire_safe(cmd, "raw command", mode)?);
591 }
592 }
593
594 Ok(())
595 }
596}
597
598fn write_line(dst: &mut BytesMut, s: &str) {
600 dst.reserve(s.len() + 1);
601 dst.put_slice(s.as_bytes());
602 dst.put_u8(b'\n');
603}
604
605fn write_block(
614 dst: &mut BytesMut,
615 header: &str,
616 lines: &[String],
617 mode: EncoderMode,
618) -> Result<(), io::Error> {
619 let total: usize =
627 header.len() + 1 + lines.iter().map(|line| line.len() + 2).sum::<usize>() + 4;
628 dst.reserve(total);
629 dst.put_slice(header.as_bytes());
630 dst.put_u8(b'\n');
631 for line in lines {
632 let clean = wire_safe(line, "block body line", mode)?;
633 if *clean == *"END" {
634 match mode {
635 EncoderMode::Sanitize => {
636 dst.put_slice(b" END");
637 dst.put_u8(b'\n');
638 continue;
639 }
640 EncoderMode::Strict => {
641 return Err(io::Error::other(EncodeError::EndInBlockBody));
642 }
643 }
644 }
645 dst.put_slice(clean.as_bytes());
646 dst.put_u8(b'\n');
647 }
648 dst.put_slice(b"END\n");
649 Ok(())
650}
651
652const PW_PROMPT: &[u8] = b"ENTER PASSWORD:";
661
662impl Decoder for OvpnCodec {
665 type Item = OvpnMessage;
666 type Error = io::Error;
667
668 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
669 loop {
670 let Some(newline_pos) = src.iter().position(|&b| b == b'\n') else {
672 if src.starts_with(PW_PROMPT) {
679 let mut consume = PW_PROMPT.len();
680 if src.get(consume) == Some(&b'\r') {
681 consume += 1;
682 }
683 src.advance(consume);
684 return Ok(Some(OvpnMessage::PasswordPrompt));
685 }
686 if src.capacity() - src.len() < 256 {
689 src.reserve(256);
690 }
691 return Ok(None); };
693
694 let line_bytes = src.split_to(newline_pos + 1);
696 let line = match std::str::from_utf8(&line_bytes) {
697 Ok(text) => text,
698 Err(error) => {
699 self.multi_line_buf = None;
702 self.client_notification = None;
703 self.expected_queue.clear();
704 return Err(io::Error::new(io::ErrorKind::InvalidData, error));
705 }
706 }
707 .trim_end_matches(['\r', '\n'])
708 .to_string();
709
710 if line.is_empty()
717 && self.multi_line_buf.is_none()
718 && self.client_notification.is_none()
719 && !matches!(self.expected_front(), ResponseKind::MultiLine)
720 {
721 continue;
722 }
723
724 if let Some(ref mut accum) = self.client_notification
733 && let Some(rest) = line.strip_prefix(">CLIENT:ENV,")
734 {
735 if rest == "END" {
736 let finished = self.client_notification.take().expect("guarded by if-let");
737 debug!(event = ?finished.event, cid = finished.cid, env_count = finished.env.len(), "decoded CLIENT notification");
738 return Ok(Some(OvpnMessage::Notification(Notification::Client {
739 event: finished.event,
740 cid: finished.cid,
741 kid: finished.kid,
742 env: finished.env,
743 })));
744 } else {
745 let (k, v) = rest
747 .split_once('=')
748 .map(|(k, v)| (k.to_string(), v.to_string()))
749 .unwrap_or_else(|| (rest.to_string(), String::new()));
750 check_accumulation_limit(
751 accum.env.len(),
752 self.max_client_env_entries,
753 "client ENV",
754 )?;
755 accum.env.insert(k, v);
756 continue; }
758 }
759 if let Some(ref mut buf) = self.multi_line_buf {
764 if line == "END" {
765 let lines = self.multi_line_buf.take().expect("guarded by if-let");
766 self.consume_expected();
767 debug!(line_count = lines.len(), "decoded multi-line response");
768 return Ok(Some(OvpnMessage::MultiLine(lines)));
769 }
770 if line.starts_with('>') {
775 if let Some(msg) = self.parse_notification(&line) {
776 return Ok(Some(msg));
777 }
778 continue;
781 }
782 check_accumulation_limit(
783 buf.len(),
784 self.max_multi_line_lines,
785 "multi-line response",
786 )?;
787 buf.push(line);
788 continue; }
790
791 if let Some(rest) = line.strip_prefix("SUCCESS:") {
797 self.consume_expected();
798 return Ok(Some(OvpnMessage::Success(
799 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
800 )));
801 }
802 if let Some(rest) = line.strip_prefix("ERROR:") {
803 self.consume_expected();
804 return Ok(Some(OvpnMessage::Error(
805 rest.strip_prefix(' ').unwrap_or(rest).to_string(),
806 )));
807 }
808
809 if line == "ENTER PASSWORD:" {
811 return Ok(Some(OvpnMessage::PasswordPrompt));
812 }
813
814 if line.starts_with('>') {
816 if let Some(msg) = self.parse_notification(&line) {
817 return Ok(Some(msg));
818 }
819 continue;
821 }
822
823 match self.expected_front() {
829 ResponseKind::MultiLine => {
830 if line == "END" {
831 self.consume_expected();
833 return Ok(Some(OvpnMessage::MultiLine(Vec::new())));
834 }
835 self.multi_line_buf = Some(vec![line]);
836 continue; }
838 ResponseKind::SuccessOrError | ResponseKind::NoResponse => {
839 self.consume_expected();
840 warn!(line = %line, "unrecognized line from server");
841 return Ok(Some(OvpnMessage::Unrecognized {
842 line,
843 kind: UnrecognizedKind::UnexpectedLine,
844 }));
845 }
846 }
847 }
848 }
849}
850
851impl OvpnCodec {
852 fn parse_notification(&mut self, line: &str) -> Option<OvpnMessage> {
856 let inner = &line[1..]; let Some((kind, payload)) = inner.split_once(':') else {
859 warn!(line = %line, "malformed notification (no colon)");
861 return Some(OvpnMessage::Unrecognized {
862 line: line.to_string(),
863 kind: UnrecognizedKind::MalformedNotification,
864 });
865 };
866
867 if kind == "INFO" {
871 if !self.seen_info {
872 self.seen_info = true;
873 return Some(OvpnMessage::Info(payload.to_string()));
874 }
875 return Some(OvpnMessage::Notification(Notification::Info {
876 message: payload.to_string(),
877 }));
878 }
879
880 if kind == "CLIENT" {
882 let (event, args) = payload
883 .split_once(',')
884 .map(|(event_str, args_str)| (event_str.to_string(), args_str.to_string()))
885 .unwrap_or_else(|| (payload.to_string(), String::new()));
886
887 if event == "ADDRESS" {
889 let mut parts = args.splitn(3, ',');
890 let cid = parts
891 .next()
892 .and_then(|field| parse_field(field, "client address cid"))
893 .unwrap_or(0);
894 let addr = parts.next().unwrap_or("").to_string();
895 let primary = parts.next() == Some("1");
896 return Some(OvpnMessage::Notification(Notification::ClientAddress {
897 cid,
898 addr,
899 primary,
900 }));
901 }
902
903 let mut id_parts = args.splitn(3, ',');
907 let cid = id_parts
908 .next()
909 .and_then(|field| parse_field(field, "client cid"))
910 .unwrap_or(0);
911 let kid = id_parts
912 .next()
913 .and_then(|field| parse_field(field, "client kid"));
914
915 let parsed_event = if event == "CR_RESPONSE" {
916 let response = id_parts.next().unwrap_or("").to_string();
917 ClientEvent::CrResponse(response)
918 } else {
919 event
920 .parse()
921 .inspect_err(|error| warn!(%error, "unknown client event"))
922 .unwrap_or_else(|_| ClientEvent::Unknown(event.clone()))
923 };
924
925 self.client_notification = Some(ClientNotificationAccumulator {
927 event: parsed_event,
928 cid,
929 kid,
930 env: BTreeMap::new(),
931 });
932 return None; }
934
935 let notification = match kind {
937 "STATE" => parse_state(payload),
938 "BYTECOUNT" => parse_bytecount(payload),
939 "BYTECOUNT_CLI" => parse_bytecount_cli(payload),
940 "LOG" => parse_log(payload),
941 "ECHO" => parse_echo(payload),
942 "HOLD" => Some(Notification::Hold {
943 text: payload.to_string(),
944 }),
945 "FATAL" => Some(Notification::Fatal {
946 message: payload.to_string(),
947 }),
948 "PKCS11ID-COUNT" => parse_pkcs11id_count(payload),
949 "NEED-OK" => parse_need_ok(payload),
950 "NEED-STR" => parse_need_str(payload),
951 "RSA_SIGN" => Some(Notification::RsaSign {
952 data: payload.to_string(),
953 }),
954 "PK_SIGN" => parse_pk_sign(payload),
955 "INFOMSG" => Some(Notification::InfoMsg {
956 extra: payload.to_string(),
957 }),
958 "NEED-CERTIFICATE" => Some(Notification::NeedCertificate {
959 hint: payload.to_string(),
960 }),
961 "REMOTE" => parse_remote(payload),
962 "PROXY" => parse_proxy(payload),
963 "PASSWORD" => parse_password(payload),
964 "PKCS11ID-ENTRY" => {
965 return parse_pkcs11id_entry_notif(payload).or_else(|| {
966 Some(OvpnMessage::Notification(Notification::Simple {
967 kind: kind.to_string(),
968 payload: payload.to_string(),
969 }))
970 });
971 }
972 _ => None,
973 };
974
975 Some(OvpnMessage::Notification(notification.unwrap_or(
976 Notification::Simple {
977 kind: kind.to_string(),
978 payload: payload.to_string(),
979 },
980 )))
981 }
982}
983
984fn parse_optional_port(input: &str) -> Option<u16> {
995 let trimmed = input.trim();
996 if trimmed.is_empty() {
997 return None;
998 }
999 trimmed
1000 .parse()
1001 .inspect_err(
1002 |error| warn!(%error, port = trimmed, "non-numeric port in STATE notification"),
1003 )
1004 .ok()
1005}
1006
1007fn parse_field<T: std::str::FromStr>(value: &str, field: &str) -> Option<T>
1012where
1013 T::Err: std::fmt::Display,
1014{
1015 value
1016 .parse()
1017 .inspect_err(|error| warn!(%error, value, field, "failed to parse notification field"))
1018 .ok()
1019}
1020
1021fn parse_state(payload: &str) -> Option<Notification> {
1022 let mut parts = payload.splitn(9, ',');
1026 let timestamp = parse_field(parts.next()?, "state timestamp")?;
1027 let state_str = parts.next()?;
1028 let name = state_str
1029 .parse()
1030 .inspect_err(|error| warn!(%error, "unknown OpenVPN state"))
1031 .unwrap_or_else(|_| OpenVpnState::Unknown(state_str.to_string()));
1032 let description = parts.next()?.to_string();
1033 let local_ip = parts.next()?.to_string();
1034 let remote_ip = parts.next()?.to_string();
1035 let remote_port = parse_optional_port(parts.next().unwrap_or(""));
1036 let local_addr = parts.next().unwrap_or("").to_string();
1037 let local_port = parse_optional_port(parts.next().unwrap_or(""));
1038 let local_ipv6 = parts.next().unwrap_or("").to_string();
1039 Some(Notification::State {
1040 timestamp,
1041 name,
1042 description,
1043 local_ip,
1044 remote_ip,
1045 remote_port,
1046 local_addr,
1047 local_port,
1048 local_ipv6,
1049 })
1050}
1051
1052fn parse_bytecount(payload: &str) -> Option<Notification> {
1053 let (a, b) = payload.split_once(',')?;
1054 Some(Notification::ByteCount {
1055 bytes_in: parse_field(a, "bytecount bytes_in")?,
1056 bytes_out: parse_field(b, "bytecount bytes_out")?,
1057 })
1058}
1059
1060fn parse_bytecount_cli(payload: &str) -> Option<Notification> {
1061 let mut parts = payload.splitn(3, ',');
1062 let cid = parse_field(parts.next()?, "bytecount_cli cid")?;
1063 let bytes_in = parse_field(parts.next()?, "bytecount_cli bytes_in")?;
1064 let bytes_out = parse_field(parts.next()?, "bytecount_cli bytes_out")?;
1065 Some(Notification::ByteCountCli {
1066 cid,
1067 bytes_in,
1068 bytes_out,
1069 })
1070}
1071
1072fn parse_log(payload: &str) -> Option<Notification> {
1073 let (ts_str, rest) = payload.split_once(',')?;
1074 let timestamp = parse_field(ts_str, "log timestamp")?;
1075 let (level_str, message) = rest.split_once(',')?;
1076 Some(Notification::Log {
1077 timestamp,
1078 level: level_str
1079 .parse()
1080 .inspect_err(|error| warn!(%error, "unknown log level"))
1081 .unwrap_or_else(|_| LogLevel::Unknown(level_str.to_string())),
1082 message: message.to_string(),
1083 })
1084}
1085
1086fn parse_echo(payload: &str) -> Option<Notification> {
1087 let (ts_str, param) = payload.split_once(',')?;
1088 let timestamp = parse_field(ts_str, "echo timestamp")?;
1089 Some(Notification::Echo {
1090 timestamp,
1091 param: param.to_string(),
1092 })
1093}
1094
1095fn parse_pkcs11id_count(payload: &str) -> Option<Notification> {
1096 let count = parse_field(payload.trim(), "pkcs11id_count")?;
1097 Some(Notification::Pkcs11IdCount { count })
1098}
1099
1100fn parse_pkcs11id_entry_notif(payload: &str) -> Option<OvpnMessage> {
1103 let rest = payload.strip_prefix('\'')?;
1104 let (index, rest) = rest.split_once("', ID:'")?;
1105 let (id, rest) = rest.split_once("', BLOB:'")?;
1106 let blob = rest.strip_suffix('\'')?;
1107 Some(OvpnMessage::Pkcs11IdEntry {
1108 index: index.to_string(),
1109 id: id.to_string(),
1110 blob: blob.to_string(),
1111 })
1112}
1113
1114fn parse_need_ok(payload: &str) -> Option<Notification> {
1116 let rest = payload.strip_prefix("Need '")?;
1118 let (name, rest) = rest.split_once('\'')?;
1119 let msg = rest.split_once("MSG:")?.1;
1120 Some(Notification::NeedOk {
1121 name: name.to_string(),
1122 message: msg.to_string(),
1123 })
1124}
1125
1126fn parse_need_str(payload: &str) -> Option<Notification> {
1128 let rest = payload.strip_prefix("Need '")?;
1129 let (name, rest) = rest.split_once('\'')?;
1130 let msg = rest.split_once("MSG:")?.1;
1131 Some(Notification::NeedStr {
1132 name: name.to_string(),
1133 message: msg.to_string(),
1134 })
1135}
1136
1137fn parse_pk_sign(payload: &str) -> Option<Notification> {
1145 if payload.is_empty() {
1146 return None;
1147 }
1148 let (data, algorithm) = match payload.split_once(',') {
1149 Some((d, a)) => (d.to_string(), Some(a.to_string())),
1150 None => (payload.to_string(), None),
1151 };
1152 Some(Notification::PkSign { data, algorithm })
1153}
1154
1155fn parse_remote(payload: &str) -> Option<Notification> {
1156 let mut parts = payload.splitn(3, ',');
1157 let host = parts.next()?.to_string();
1158 let port = parse_field(parts.next()?, "remote port")?;
1159 let proto_str = parts.next()?;
1160 let protocol = proto_str
1161 .parse()
1162 .inspect_err(|error| warn!(%error, "unknown transport protocol"))
1163 .unwrap_or_else(|_| TransportProtocol::Unknown(proto_str.to_string()));
1164 Some(Notification::Remote {
1165 host,
1166 port,
1167 protocol,
1168 })
1169}
1170
1171fn parse_proxy(payload: &str) -> Option<Notification> {
1172 let mut parts = payload.splitn(3, ',');
1174 let index = parse_field(parts.next()?, "proxy index")?;
1175 let pt_str = parts.next()?;
1176 let proxy_type = pt_str
1177 .parse()
1178 .inspect_err(|error| warn!(%error, "unknown proxy type"))
1179 .unwrap_or_else(|_| TransportProtocol::Unknown(pt_str.to_string()));
1180 let host = parts.next()?.to_string();
1181 Some(Notification::Proxy {
1182 index,
1183 proxy_type,
1184 host,
1185 })
1186}
1187
1188fn parse_auth_type(s: &str) -> AuthType {
1190 s.parse()
1191 .inspect_err(|error| warn!(%error, "unknown auth type"))
1192 .unwrap_or_else(|_| AuthType::Unknown(s.to_string()))
1193}
1194
1195fn parse_password(payload: &str) -> Option<Notification> {
1196 if let Some(token) = payload.strip_prefix("Auth-Token:") {
1199 return Some(Notification::Password(PasswordNotification::AuthToken {
1200 token: Redacted::new(token),
1201 }));
1202 }
1203
1204 if let Some(rest) = payload.strip_prefix("Verification Failed: '") {
1207 if let Some((auth_part, crv1_part)) = rest.split_once("' ['CRV1:") {
1209 debug_assert_eq!(auth_part, "Auth", "CRV1 auth type should always be 'Auth'");
1210 let crv1_data = crv1_part.strip_suffix("']")?;
1211 let mut parts = crv1_data.splitn(4, ':');
1212 let flags = parts.next()?.to_string();
1213 let state_id = parts.next()?.to_string();
1214 let username_b64 = parts.next()?.to_string();
1215 let challenge = parts.next()?.to_string();
1216 return Some(Notification::Password(
1217 PasswordNotification::DynamicChallenge {
1218 flags,
1219 state_id,
1220 username_b64,
1221 challenge,
1222 },
1223 ));
1224 }
1225 let auth_type = rest.strip_suffix('\'')?;
1227 return Some(Notification::Password(
1228 PasswordNotification::VerificationFailed {
1229 auth_type: parse_auth_type(auth_type),
1230 },
1231 ));
1232 }
1233
1234 let rest = payload.strip_prefix("Need '")?;
1237 let (auth_type_str, rest) = rest.split_once('\'')?;
1238 let rest = rest.trim_start();
1239
1240 if let Some(after_up) = rest.strip_prefix("username/password") {
1241 let after_up = after_up.trim_start();
1242
1243 if let Some(sc) = after_up.strip_prefix("SC:") {
1246 let (flag_str, challenge) = sc.split_once(',')?;
1247 let flags: u32 = parse_field(flag_str, "static challenge flags")?;
1248 return Some(Notification::Password(
1249 PasswordNotification::StaticChallenge {
1250 echo: flags & 1 != 0,
1251 response_concat: flags & 2 != 0,
1252 challenge: challenge.to_string(),
1253 },
1254 ));
1255 }
1256
1257 return Some(Notification::Password(PasswordNotification::NeedAuth {
1259 auth_type: parse_auth_type(auth_type_str),
1260 }));
1261 }
1262
1263 if rest.starts_with("password") {
1265 return Some(Notification::Password(PasswordNotification::NeedPassword {
1266 auth_type: parse_auth_type(auth_type_str),
1267 }));
1268 }
1269
1270 None }
1272
1273#[cfg(test)]
1274mod tests {
1275 use super::*;
1276
1277 use crate::{
1278 auth::AuthType, client_deny::ClientDeny, client_event::ClientEvent,
1279 message::PasswordNotification, signal::Signal, status_format::StatusFormat,
1280 stream_mode::StreamMode,
1281 };
1282
1283 use bytes::BytesMut;
1284 use tokio_util::codec::{Decoder, Encoder};
1285 use tracing_test::traced_test;
1286
1287 fn encode_to_string(cmd: OvpnCommand) -> String {
1289 let mut codec = OvpnCodec::new();
1290 let mut buf = BytesMut::new();
1291 codec.encode(cmd, &mut buf).unwrap();
1292 String::from_utf8(buf.to_vec()).unwrap()
1293 }
1294
1295 fn decode_all(input: &str) -> Vec<OvpnMessage> {
1297 let mut codec = OvpnCodec::new();
1298 let mut buf = BytesMut::from(input);
1299 let mut msgs = Vec::new();
1300 while let Some(msg) = codec.decode(&mut buf).unwrap() {
1301 msgs.push(msg);
1302 }
1303 msgs
1304 }
1305
1306 fn encode_then_decode(cmd: OvpnCommand, response: &str) -> Vec<OvpnMessage> {
1308 let mut codec = OvpnCodec::new();
1309 let mut enc_buf = BytesMut::new();
1310 codec.encode(cmd, &mut enc_buf).unwrap();
1311 let mut dec_buf = BytesMut::from(response);
1312 let mut msgs = Vec::new();
1313 while let Some(msg) = codec.decode(&mut dec_buf).unwrap() {
1314 msgs.push(msg);
1315 }
1316 msgs
1317 }
1318
1319 #[test]
1322 fn encode_status_v1() {
1323 assert_eq!(
1324 encode_to_string(OvpnCommand::Status(StatusFormat::V1)),
1325 "status\n"
1326 );
1327 }
1328
1329 #[test]
1330 fn encode_status_v3() {
1331 assert_eq!(
1332 encode_to_string(OvpnCommand::Status(StatusFormat::V3)),
1333 "status 3\n"
1334 );
1335 }
1336
1337 #[test]
1338 fn encode_signal() {
1339 assert_eq!(
1340 encode_to_string(OvpnCommand::Signal(Signal::SigUsr1)),
1341 "signal SIGUSR1\n"
1342 );
1343 }
1344
1345 #[test]
1346 fn encode_state_on_all() {
1347 assert_eq!(
1348 encode_to_string(OvpnCommand::StateStream(StreamMode::OnAll)),
1349 "state on all\n"
1350 );
1351 }
1352
1353 #[test]
1354 fn encode_state_recent() {
1355 assert_eq!(
1356 encode_to_string(OvpnCommand::StateStream(StreamMode::Recent(5))),
1357 "state 5\n"
1358 );
1359 }
1360
1361 #[test]
1362 fn encode_password_escaping() {
1363 let wire = encode_to_string(OvpnCommand::Password {
1366 auth_type: AuthType::PrivateKey,
1367 value: r#"foo\"bar"#.into(),
1368 });
1369 assert_eq!(wire, "password \"Private Key\" \"foo\\\\\\\"bar\"\n");
1370 }
1371
1372 #[test]
1373 fn encode_password_simple() {
1374 let wire = encode_to_string(OvpnCommand::Password {
1375 auth_type: AuthType::Auth,
1376 value: "hunter2".into(),
1377 });
1378 assert_eq!(wire, "password \"Auth\" \"hunter2\"\n");
1379 }
1380
1381 #[test]
1382 fn encode_client_auth_with_config() {
1383 let wire = encode_to_string(OvpnCommand::ClientAuth {
1384 cid: 42,
1385 kid: 0,
1386 config_lines: vec![
1387 "push \"route 10.0.0.0 255.255.0.0\"".to_string(),
1388 "push \"dhcp-option DNS 10.0.0.1\"".to_string(),
1389 ],
1390 });
1391 assert_eq!(
1392 wire,
1393 "client-auth 42 0\n\
1394 push \"route 10.0.0.0 255.255.0.0\"\n\
1395 push \"dhcp-option DNS 10.0.0.1\"\n\
1396 END\n"
1397 );
1398 }
1399
1400 #[test]
1401 fn encode_client_auth_empty_config() {
1402 let wire = encode_to_string(OvpnCommand::ClientAuth {
1403 cid: 1,
1404 kid: 0,
1405 config_lines: vec![],
1406 });
1407 assert_eq!(wire, "client-auth 1 0\nEND\n");
1408 }
1409
1410 #[test]
1411 fn encode_client_deny_with_client_reason() {
1412 let wire = encode_to_string(OvpnCommand::ClientDeny(ClientDeny {
1413 cid: 5,
1414 kid: 0,
1415 reason: "cert revoked".to_string(),
1416 client_reason: Some("Your access has been revoked.".to_string()),
1417 }));
1418 assert_eq!(
1419 wire,
1420 "client-deny 5 0 \"cert revoked\" \"Your access has been revoked.\"\n"
1421 );
1422 }
1423
1424 #[test]
1425 fn encode_client_pending_auth() {
1426 let wire = encode_to_string(OvpnCommand::ClientPendingAuth {
1427 cid: 42,
1428 kid: 1,
1429 extra: "WEB_AUTH::https://example.com".to_string(),
1430 timeout: 120,
1431 });
1432 assert_eq!(
1433 wire,
1434 "client-pending-auth 42 1 WEB_AUTH::https://example.com 120\n"
1435 );
1436 }
1437
1438 #[test]
1439 #[traced_test]
1440 fn encode_client_pending_auth_long_extra_warns() {
1441 let long_extra = "W".repeat(246);
1442 let wire = encode_to_string(OvpnCommand::ClientPendingAuth {
1443 cid: 1,
1444 kid: 0,
1445 extra: long_extra.clone(),
1446 timeout: 60,
1447 });
1448 assert_eq!(wire, format!("client-pending-auth 1 0 {long_extra} 60\n"));
1449 assert!(logs_contain("exceeds 245-character limit"));
1450 }
1451
1452 #[test]
1453 #[traced_test]
1454 fn encode_client_pending_auth_at_limit_no_warning() {
1455 let extra = "W".repeat(245);
1456 encode_to_string(OvpnCommand::ClientPendingAuth {
1457 cid: 1,
1458 kid: 0,
1459 extra,
1460 timeout: 60,
1461 });
1462 assert!(!logs_contain("exceeds 245-character limit"));
1463 }
1464
1465 #[test]
1466 fn encode_rsa_sig() {
1467 let wire = encode_to_string(OvpnCommand::RsaSig {
1468 base64_lines: vec!["AAAA".to_string(), "BBBB".to_string()],
1469 });
1470 assert_eq!(wire, "rsa-sig\nAAAA\nBBBB\nEND\n");
1471 }
1472
1473 #[test]
1474 fn encode_remote_modify() {
1475 let wire = encode_to_string(OvpnCommand::Remote(RemoteAction::Modify {
1476 host: "vpn.example.com".to_string(),
1477 port: 1234,
1478 }));
1479 assert_eq!(wire, "remote MOD vpn.example.com 1234\n");
1480 }
1481
1482 #[test]
1483 fn encode_pk_sig() {
1484 let wire = encode_to_string(OvpnCommand::PkSig {
1485 base64_lines: vec!["AAAA".to_string(), "BBBB".to_string()],
1486 });
1487 assert_eq!(wire, "pk-sig\nAAAA\nBBBB\nEND\n");
1488 }
1489
1490 #[test]
1491 fn encode_env_filter() {
1492 assert_eq!(
1493 encode_to_string(OvpnCommand::EnvFilter(2)),
1494 "env-filter 2\n"
1495 );
1496 }
1497
1498 #[test]
1499 fn encode_remote_entry_count() {
1500 assert_eq!(
1501 encode_to_string(OvpnCommand::RemoteEntryCount),
1502 "remote-entry-count\n"
1503 );
1504 }
1505
1506 #[test]
1507 fn encode_remote_entry_get() {
1508 use crate::command::RemoteEntryRange;
1509 assert_eq!(
1510 encode_to_string(OvpnCommand::RemoteEntryGet(RemoteEntryRange::Single(0))),
1511 "remote-entry-get 0\n"
1512 );
1513 assert_eq!(
1514 encode_to_string(OvpnCommand::RemoteEntryGet(RemoteEntryRange::Range {
1515 from: 0,
1516 end: 3
1517 })),
1518 "remote-entry-get 0 3\n"
1519 );
1520 assert_eq!(
1521 encode_to_string(OvpnCommand::RemoteEntryGet(RemoteEntryRange::All)),
1522 "remote-entry-get all\n"
1523 );
1524 }
1525
1526 #[test]
1527 fn encode_push_update_broad() {
1528 let wire = encode_to_string(OvpnCommand::PushUpdateBroad {
1529 options: "route 10.0.0.0".to_string(),
1530 });
1531 assert_eq!(wire, "push-update-broad \"route 10.0.0.0\"\n");
1532 }
1533
1534 #[test]
1535 fn encode_push_update_cid() {
1536 let wire = encode_to_string(OvpnCommand::PushUpdateCid {
1537 cid: 42,
1538 options: "route 10.0.0.0".to_string(),
1539 });
1540 assert_eq!(wire, "push-update-cid 42 \"route 10.0.0.0\"\n");
1541 }
1542
1543 #[test]
1544 fn encode_proxy_http_nct() {
1545 let wire = encode_to_string(OvpnCommand::Proxy(ProxyAction::Http {
1546 host: "proxy.local".to_string(),
1547 port: 8080,
1548 non_cleartext_only: true,
1549 }));
1550 assert_eq!(wire, "proxy HTTP proxy.local 8080 nct\n");
1551 }
1552
1553 #[test]
1554 fn encode_needok() {
1555 use crate::need_ok::NeedOkResponse;
1556 let wire = encode_to_string(OvpnCommand::NeedOk {
1557 name: "token-insertion-request".to_string(),
1558 response: NeedOkResponse::Ok,
1559 });
1560 assert_eq!(wire, "needok token-insertion-request ok\n");
1561 }
1562
1563 #[test]
1564 fn encode_needstr() {
1565 let wire = encode_to_string(OvpnCommand::NeedStr {
1566 name: "name".to_string(),
1567 value: "John".to_string(),
1568 });
1569 assert_eq!(wire, "needstr name \"John\"\n");
1570 }
1571
1572 #[test]
1573 fn encode_forget_passwords() {
1574 assert_eq!(
1575 encode_to_string(OvpnCommand::ForgetPasswords),
1576 "forget-passwords\n"
1577 );
1578 }
1579
1580 #[test]
1581 fn encode_hold_query() {
1582 assert_eq!(encode_to_string(OvpnCommand::HoldQuery), "hold\n");
1583 }
1584
1585 #[test]
1586 fn encode_echo_on_all() {
1587 assert_eq!(
1588 encode_to_string(OvpnCommand::Echo(StreamMode::OnAll)),
1589 "echo on all\n"
1590 );
1591 }
1592
1593 #[test]
1596 fn decode_success() {
1597 let msgs = decode_all("SUCCESS: pid=12345\n");
1598 assert_eq!(msgs.len(), 1);
1599 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "pid=12345"));
1600 }
1601
1602 #[test]
1603 fn decode_success_bare() {
1604 let msgs = decode_all("SUCCESS:\n");
1606 assert_eq!(msgs.len(), 1);
1607 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s.is_empty()));
1608 }
1609
1610 #[test]
1611 fn decode_error() {
1612 let msgs = decode_all("ERROR: unknown command\n");
1613 assert_eq!(msgs.len(), 1);
1614 assert!(matches!(&msgs[0], OvpnMessage::Error(s) if s == "unknown command"));
1615 }
1616
1617 #[test]
1618 fn decode_info_notification() {
1619 let msgs = decode_all(">INFO:OpenVPN Management Interface Version 5\n");
1620 assert_eq!(msgs.len(), 1);
1621 assert!(matches!(
1622 &msgs[0],
1623 OvpnMessage::Info(s) if s == "OpenVPN Management Interface Version 5"
1624 ));
1625 }
1626
1627 #[test]
1628 fn decode_state_notification() {
1629 let msgs = decode_all(">STATE:1234567890,CONNECTED,SUCCESS,,10.0.0.1\n");
1630 assert_eq!(msgs.len(), 1);
1631 assert!(matches!(
1632 &msgs[0],
1633 OvpnMessage::Notification(Notification::State {
1634 timestamp: 1234567890,
1635 name: OpenVpnState::Connected,
1636 description,
1637 local_ip,
1638 remote_ip,
1639 ..
1640 }) if description == "SUCCESS" && local_ip.is_empty() && remote_ip == "10.0.0.1"
1641 ));
1642 }
1643
1644 #[test]
1645 fn decode_multiline_with_command_tracking() {
1646 let msgs = encode_then_decode(
1650 OvpnCommand::Status(StatusFormat::V1),
1651 "OpenVPN CLIENT LIST\nCommon Name,Real Address\ntest,1.2.3.4:1234\nEND\n",
1652 );
1653 assert_eq!(msgs.len(), 1);
1654 assert!(matches!(
1655 &msgs[0],
1656 OvpnMessage::MultiLine(lines)
1657 if lines.len() == 3
1658 && lines[0] == "OpenVPN CLIENT LIST"
1659 && lines[2] == "test,1.2.3.4:1234"
1660 ));
1661 }
1662
1663 #[test]
1664 fn decode_hold_query_success() {
1665 let msgs = encode_then_decode(OvpnCommand::HoldQuery, "SUCCESS: hold=0\n");
1667 assert_eq!(msgs.len(), 1);
1668 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "hold=0"));
1669 }
1670
1671 #[test]
1672 fn decode_bare_state_multiline() {
1673 let msgs = encode_then_decode(
1675 OvpnCommand::State,
1676 "1234567890,CONNECTED,SUCCESS,,10.0.0.1,,,,\nEND\n",
1677 );
1678 assert_eq!(msgs.len(), 1);
1679 assert!(matches!(
1680 &msgs[0],
1681 OvpnMessage::MultiLine(lines)
1682 if lines.len() == 1 && lines[0].starts_with("1234567890")
1683 ));
1684 }
1685
1686 #[test]
1687 fn decode_notification_during_multiline() {
1688 let msgs = encode_then_decode(
1691 OvpnCommand::Status(StatusFormat::V1),
1692 "header line\n>BYTECOUNT:1000,2000\ndata line\nEND\n",
1693 );
1694 assert_eq!(msgs.len(), 2);
1695 assert!(matches!(
1697 &msgs[0],
1698 OvpnMessage::Notification(Notification::ByteCount {
1699 bytes_in: 1000,
1700 bytes_out: 2000
1701 })
1702 ));
1703 assert!(matches!(
1705 &msgs[1],
1706 OvpnMessage::MultiLine(lines) if lines == &["header line", "data line"]
1707 ));
1708 }
1709
1710 #[test]
1711 fn decode_client_connect_multiline_notification() {
1712 let input = "\
1713 >CLIENT:CONNECT,0,1\n\
1714 >CLIENT:ENV,untrusted_ip=1.2.3.4\n\
1715 >CLIENT:ENV,common_name=TestClient\n\
1716 >CLIENT:ENV,END\n";
1717 let msgs = decode_all(input);
1718 assert_eq!(msgs.len(), 1);
1719 assert!(matches!(
1720 &msgs[0],
1721 OvpnMessage::Notification(Notification::Client {
1722 event: ClientEvent::Connect,
1723 cid: 0,
1724 kid: Some(1),
1725 env,
1726 }) if env.len() == 2
1727 && env.get("untrusted_ip").map(String::as_str) == Some("1.2.3.4")
1728 && env.get("common_name").map(String::as_str) == Some("TestClient")
1729 ));
1730 }
1731
1732 #[test]
1733 fn decode_client_address_single_line() {
1734 let msgs = decode_all(">CLIENT:ADDRESS,3,10.0.0.5,1\n");
1735 assert_eq!(msgs.len(), 1);
1736 assert!(matches!(
1737 &msgs[0],
1738 OvpnMessage::Notification(Notification::ClientAddress {
1739 cid: 3,
1740 addr,
1741 primary: true,
1742 }) if addr == "10.0.0.5"
1743 ));
1744 }
1745
1746 #[test]
1747 fn decode_client_disconnect() {
1748 let input = "\
1749 >CLIENT:DISCONNECT,5\n\
1750 >CLIENT:ENV,bytes_received=12345\n\
1751 >CLIENT:ENV,bytes_sent=67890\n\
1752 >CLIENT:ENV,END\n";
1753 let msgs = decode_all(input);
1754 assert_eq!(msgs.len(), 1);
1755 assert!(matches!(
1756 &msgs[0],
1757 OvpnMessage::Notification(Notification::Client {
1758 event: ClientEvent::Disconnect,
1759 cid: 5,
1760 kid: None,
1761 env,
1762 }) if env.len() == 2
1763 ));
1764 }
1765
1766 #[test]
1767 fn decode_password_prompt_no_newline_with_cr() {
1768 let msgs = decode_all("ENTER PASSWORD:\r");
1772 assert_eq!(msgs.len(), 1);
1773 assert_eq!(msgs[0], OvpnMessage::PasswordPrompt);
1774 }
1775
1776 #[test]
1777 fn decode_password_prompt_with_cr_fully_consumes_buffer() {
1778 let mut codec = OvpnCodec::new();
1779 let mut buf = BytesMut::from("ENTER PASSWORD:\r");
1780 let msg = codec.decode(&mut buf).unwrap();
1781 assert_eq!(msg, Some(OvpnMessage::PasswordPrompt));
1782 assert!(
1783 buf.is_empty(),
1784 "trailing \\r was not consumed; {remaining} bytes remain",
1785 remaining = buf.len(),
1786 );
1787 }
1788
1789 #[test]
1790 fn decode_password_prompt_no_newline_without_cr() {
1791 let msgs = decode_all("ENTER PASSWORD:");
1792 assert_eq!(msgs.len(), 1);
1793 assert_eq!(msgs[0], OvpnMessage::PasswordPrompt);
1794 }
1795
1796 #[test]
1797 fn decode_password_notification() {
1798 let msgs = decode_all(">PASSWORD:Need 'Auth' username/password\n");
1799 assert_eq!(msgs.len(), 1);
1800 assert!(matches!(
1801 &msgs[0],
1802 OvpnMessage::Notification(Notification::Password(PasswordNotification::NeedAuth {
1803 auth_type: AuthType::Auth,
1804 }))
1805 ));
1806 }
1807
1808 #[test]
1809 fn escape_and_quote_special_chars() {
1810 assert_eq!(quote(&escape(r#"foo"bar"#)), r#""foo\"bar""#);
1811 assert_eq!(quote(&escape(r"a\b")), r#""a\\b""#);
1812 assert_eq!(quote(&escape("simple")), r#""simple""#);
1813 }
1814
1815 #[test]
1816 fn decode_empty_multiline() {
1817 let msgs = encode_then_decode(OvpnCommand::Status(StatusFormat::V1), "END\n");
1819 assert_eq!(msgs.len(), 1);
1820 assert!(matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines.is_empty()));
1821 }
1822
1823 #[test]
1824 fn decode_need_ok_notification() {
1825 let msgs = decode_all(
1826 ">NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your token\n",
1827 );
1828 assert_eq!(msgs.len(), 1);
1829 assert!(matches!(
1830 &msgs[0],
1831 OvpnMessage::Notification(Notification::NeedOk { name, message })
1832 if name == "token-insertion-request" && message == "Please insert your token"
1833 ));
1834 }
1835
1836 #[test]
1837 fn decode_hold_notification() {
1838 let msgs = decode_all(">HOLD:Waiting for hold release\n");
1839 assert_eq!(msgs.len(), 1);
1840 assert!(matches!(
1841 &msgs[0],
1842 OvpnMessage::Notification(Notification::Hold { text })
1843 if text == "Waiting for hold release"
1844 ));
1845 }
1846
1847 #[test]
1850 fn encode_raw_multiline() {
1851 assert_eq!(
1852 encode_to_string(OvpnCommand::RawMultiLine("custom-cmd arg".to_string())),
1853 "custom-cmd arg\n"
1854 );
1855 }
1856
1857 #[test]
1858 fn raw_multiline_expects_multiline_response() {
1859 let msgs = encode_then_decode(
1860 OvpnCommand::RawMultiLine("custom".to_string()),
1861 "line1\nline2\nEND\n",
1862 );
1863 assert_eq!(msgs.len(), 1);
1864 assert!(matches!(
1865 &msgs[0],
1866 OvpnMessage::MultiLine(lines) if lines == &["line1", "line2"]
1867 ));
1868 }
1869
1870 #[test]
1871 fn raw_multiline_sanitizes_newlines() {
1872 let wire = encode_to_string(OvpnCommand::RawMultiLine("cmd\ninjected".to_string()));
1874 assert_eq!(wire, "cmdinjected\n");
1875 }
1876
1877 #[test]
1878 fn raw_multiline_strict_rejects_newlines() {
1879 let mut codec = OvpnCodec::new().with_encoder_mode(EncoderMode::Strict);
1880 let mut buf = BytesMut::new();
1881 let result = codec.encode(
1882 OvpnCommand::RawMultiLine("cmd\ninjected".to_string()),
1883 &mut buf,
1884 );
1885 assert!(result.is_err());
1886 }
1887
1888 #[test]
1891 #[traced_test]
1892 fn encode_during_multiline_accumulation_warns_but_succeeds() {
1893 let mut codec = OvpnCodec::new();
1894 let mut buf = BytesMut::new();
1895 codec
1897 .encode(OvpnCommand::Status(StatusFormat::V1), &mut buf)
1898 .unwrap();
1899 let mut dec = BytesMut::from("header line\n");
1901 codec.decode(&mut dec).unwrap(); codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1904 assert_eq!(
1905 codec.expected_queue.len(),
1906 2,
1907 "both pending: first mid-accumulation, second queued"
1908 );
1909 assert!(logs_contain("mid-accumulation"));
1910 }
1911
1912 #[test]
1913 #[traced_test]
1914 fn encode_during_client_notif_accumulation_warns_but_succeeds() {
1915 let mut codec = OvpnCodec::new();
1916 let mut buf = BytesMut::new();
1917 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
1919 codec.decode(&mut dec).unwrap();
1920 codec.encode(OvpnCommand::Pid, &mut buf).unwrap();
1922 assert_eq!(codec.expected_queue.len(), 1);
1923 assert!(logs_contain("mid-accumulation"));
1924 }
1925
1926 #[test]
1929 fn pipelined_commands_decode_correctly() {
1930 let mut codec = OvpnCodec::new();
1931 let mut enc = BytesMut::new();
1932 codec
1934 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1935 .unwrap();
1936 codec.encode(OvpnCommand::Pid, &mut enc).unwrap();
1937 assert_eq!(codec.expected_queue.len(), 2);
1938
1939 let mut dec = BytesMut::from("TITLE\nheader\ndata\nEND\nSUCCESS: pid=42\n");
1941 let mut msgs = Vec::new();
1942 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1943 msgs.push(msg);
1944 }
1945 assert_eq!(msgs.len(), 2);
1946 assert!(
1947 matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines == &["TITLE", "header", "data"]),
1948 "first response should be MultiLine, got {:?}",
1949 msgs[0]
1950 );
1951 assert!(
1952 matches!(&msgs[1], OvpnMessage::Success(s) if s == "pid=42"),
1953 "second response should be Success, got {:?}",
1954 msgs[1]
1955 );
1956 assert!(codec.expected_queue.is_empty());
1957 }
1958
1959 #[test]
1962 fn default_accumulation_limit_allows_reasonable_responses() {
1963 let mut codec = OvpnCodec::new();
1964 let mut enc = BytesMut::new();
1965 codec
1966 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1967 .unwrap();
1968 let mut data = String::new();
1970 for i in 0..500 {
1971 data.push_str(&format!("line {i}\n"));
1972 }
1973 data.push_str("END\n");
1974 let mut dec = BytesMut::from(data.as_str());
1975 let mut msgs = Vec::new();
1976 while let Some(msg) = codec.decode(&mut dec).unwrap() {
1977 msgs.push(msg);
1978 }
1979 assert_eq!(msgs.len(), 1);
1980 assert!(matches!(
1981 &msgs[0],
1982 OvpnMessage::MultiLine(lines) if lines.len() == 500
1983 ));
1984 }
1985
1986 #[test]
1987 fn multi_line_limit_exceeded() {
1988 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
1989 let mut enc = BytesMut::new();
1990 codec
1991 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
1992 .unwrap();
1993 let mut dec = BytesMut::from("a\nb\nc\nd\nEND\n");
1994 let result = loop {
1995 match codec.decode(&mut dec) {
1996 Ok(Some(msg)) => break Ok(msg),
1997 Ok(None) => continue,
1998 Err(error) => break Err(error),
1999 }
2000 };
2001 assert!(result.is_err(), "expected error when limit exceeded");
2002 let err = result.unwrap_err();
2003 assert!(
2004 err.to_string().contains("multi-line response"),
2005 "error should mention multi-line: {err}"
2006 );
2007 }
2008
2009 #[test]
2010 fn multi_line_limit_exact_boundary_passes() {
2011 let mut codec = OvpnCodec::new().with_max_multi_line_lines(AccumulationLimit::Max(3));
2012 let mut enc = BytesMut::new();
2013 codec
2014 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
2015 .unwrap();
2016 let mut dec = BytesMut::from("a\nb\nc\nEND\n");
2018 let mut msgs = Vec::new();
2019 while let Some(msg) = codec.decode(&mut dec).unwrap() {
2020 msgs.push(msg);
2021 }
2022 assert_eq!(msgs.len(), 1);
2023 assert!(matches!(
2024 &msgs[0],
2025 OvpnMessage::MultiLine(lines) if lines.len() == 3
2026 ));
2027 }
2028
2029 #[test]
2030 fn client_env_limit_exceeded() {
2031 let mut codec = OvpnCodec::new().with_max_client_env_entries(AccumulationLimit::Max(2));
2032 let mut dec = BytesMut::from(
2033 ">CLIENT:CONNECT,0,1\n\
2034 >CLIENT:ENV,a=1\n\
2035 >CLIENT:ENV,b=2\n\
2036 >CLIENT:ENV,c=3\n\
2037 >CLIENT:ENV,END\n",
2038 );
2039 let result = loop {
2040 match codec.decode(&mut dec) {
2041 Ok(Some(msg)) => break Ok(msg),
2042 Ok(None) => continue,
2043 Err(error) => break Err(error),
2044 }
2045 };
2046 assert!(
2047 result.is_err(),
2048 "expected error when client ENV limit exceeded"
2049 );
2050 let err = result.unwrap_err();
2051 assert!(
2052 err.to_string().contains("client ENV"),
2053 "error should mention client ENV: {err}"
2054 );
2055 }
2056
2057 #[test]
2060 fn utf8_error_resets_multiline_state() {
2061 let mut codec = OvpnCodec::new();
2062 let mut enc = BytesMut::new();
2063 codec
2064 .encode(OvpnCommand::Status(StatusFormat::V1), &mut enc)
2065 .unwrap();
2066 let mut dec = BytesMut::from("header\n");
2068 assert!(codec.decode(&mut dec).unwrap().is_none());
2069 dec.extend_from_slice(b"bad \xff line\n");
2071 assert!(codec.decode(&mut dec).is_err());
2072 dec.extend_from_slice(b"SUCCESS: recovered\n");
2075 let msg = codec
2076 .decode(&mut dec)
2077 .unwrap()
2078 .expect("should produce a message");
2079 assert!(
2080 matches!(&msg, OvpnMessage::Success(s) if s.contains("recovered")),
2081 "expected Success containing 'recovered', got {msg:?}"
2082 );
2083 }
2084
2085 #[test]
2086 fn utf8_error_resets_client_notif_state() {
2087 let mut codec = OvpnCodec::new();
2088 let mut dec = BytesMut::from(">CLIENT:CONNECT,0,1\n");
2090 assert!(codec.decode(&mut dec).unwrap().is_none());
2091 dec.extend_from_slice(b">CLIENT:ENV,\xff\n");
2093 assert!(codec.decode(&mut dec).is_err());
2094 dec.extend_from_slice(b"SUCCESS: ok\n");
2096 let msg = codec
2097 .decode(&mut dec)
2098 .unwrap()
2099 .expect("should produce a message");
2100 assert!(
2101 matches!(&msg, OvpnMessage::Success(_)),
2102 "expected Success after UTF-8 reset, got {msg:?}"
2103 );
2104 }
2105
2106 #[test]
2107 fn encode_set_version() {
2108 assert_eq!(encode_to_string(OvpnCommand::SetVersion(2)), "version 2\n");
2109 assert_eq!(encode_to_string(OvpnCommand::SetVersion(4)), "version 4\n");
2110 }
2111
2112 #[test]
2113 fn encode_remote_skip_n() {
2114 assert_eq!(
2115 encode_to_string(OvpnCommand::Remote(RemoteAction::SkipN(3))),
2116 "remote SKIP 3\n"
2117 );
2118 }
2119}