1use bytes::{BufMut, BytesMut};
2use std::io;
3use tokio_util::codec::{Decoder, Encoder};
4
5use crate::command::{OvpnCommand, ResponseKind};
6use crate::kill_target::KillTarget;
7use crate::message::{Notification, OvpnMessage};
8use crate::proxy_action::ProxyAction;
9use crate::remote_action::RemoteAction;
10use crate::status_format::StatusFormat;
11use crate::unrecognized::UnrecognizedKind;
12
13fn quote_and_escape(s: &str) -> String {
22 let mut out = String::with_capacity(s.len() + 2);
23 out.push('"');
24 for c in s.chars() {
25 match c {
26 '\\' => out.push_str("\\\\"),
27 '"' => out.push_str("\\\""),
28 _ => out.push(c),
29 }
30 }
31 out.push('"');
32 out
33}
34
35use crate::client_event::ClientEvent;
36use crate::log_level::LogLevel;
37use crate::openvpn_state::OpenVpnState;
38use crate::transport_protocol::TransportProtocol;
39
40#[derive(Debug)]
42struct ClientNotifAccum {
43 event: ClientEvent,
44 cid: u64,
45 kid: Option<u64>,
46 env: Vec<(String, String)>,
47}
48
49pub struct OvpnCodec {
57 expected: ResponseKind,
63
64 multi_line_buf: Option<Vec<String>>,
66
67 client_notif: Option<ClientNotifAccum>,
70}
71
72impl OvpnCodec {
73 pub fn new() -> Self {
76 Self {
77 expected: ResponseKind::SuccessOrError,
82 multi_line_buf: None,
83 client_notif: None,
84 }
85 }
86}
87
88impl Default for OvpnCodec {
89 fn default() -> Self {
90 Self::new()
91 }
92}
93
94impl Encoder<OvpnCommand> for OvpnCodec {
97 type Error = io::Error;
98
99 fn encode(&mut self, item: OvpnCommand, dst: &mut BytesMut) -> Result<(), Self::Error> {
100 self.expected = item.expected_response();
103
104 match item {
105 OvpnCommand::Status(StatusFormat::V1) => write_line(dst, "status"),
107 OvpnCommand::Status(ref fmt) => write_line(dst, &format!("status {fmt}")),
108 OvpnCommand::State => write_line(dst, "state"),
109 OvpnCommand::StateStream(ref m) => write_line(dst, &format!("state {}", m)),
110 OvpnCommand::Version => write_line(dst, "version"),
111 OvpnCommand::Pid => write_line(dst, "pid"),
112 OvpnCommand::Help => write_line(dst, "help"),
113 OvpnCommand::Net => write_line(dst, "net"),
114 OvpnCommand::Verb(Some(n)) => write_line(dst, &format!("verb {n}")),
115 OvpnCommand::Verb(None) => write_line(dst, "verb"),
116 OvpnCommand::Mute(Some(n)) => write_line(dst, &format!("mute {n}")),
117 OvpnCommand::Mute(None) => write_line(dst, "mute"),
118
119 OvpnCommand::Log(ref m) => write_line(dst, &format!("log {}", m)),
121 OvpnCommand::Echo(ref m) => write_line(dst, &format!("echo {}", m)),
122 OvpnCommand::ByteCount(n) => write_line(dst, &format!("bytecount {n}")),
123
124 OvpnCommand::Signal(sig) => write_line(dst, &format!("signal {sig}")),
126 OvpnCommand::Kill(KillTarget::CommonName(ref cn)) => {
127 write_line(dst, &format!("kill {cn}"))
128 }
129 OvpnCommand::Kill(KillTarget::Address { ref ip, port }) => {
130 write_line(dst, &format!("kill {ip}:{port}"))
131 }
132 OvpnCommand::HoldQuery => write_line(dst, "hold"),
133 OvpnCommand::HoldOn => write_line(dst, "hold on"),
134 OvpnCommand::HoldOff => write_line(dst, "hold off"),
135 OvpnCommand::HoldRelease => write_line(dst, "hold release"),
136
137 OvpnCommand::Username {
142 ref auth_type,
143 ref value,
144 } => {
145 let escaped = quote_and_escape(value);
149 write_line(dst, &format!("username \"{auth_type}\" {escaped}"))
150 }
151 OvpnCommand::Password {
152 ref auth_type,
153 ref value,
154 } => {
155 let escaped = quote_and_escape(value);
156 write_line(dst, &format!("password \"{auth_type}\" {escaped}"))
157 }
158 OvpnCommand::AuthRetry(mode) => write_line(dst, &format!("auth-retry {mode}")),
159 OvpnCommand::ForgetPasswords => write_line(dst, "forget-passwords"),
160
161 OvpnCommand::ChallengeResponse {
163 ref state_id,
164 ref response,
165 } => {
166 let value = format!("CRV1::{state_id}::{response}");
167 let escaped = quote_and_escape(&value);
168 write_line(dst, &format!("password \"Auth\" {escaped}"))
169 }
170 OvpnCommand::StaticChallengeResponse {
171 ref password_b64,
172 ref response_b64,
173 } => {
174 let value = format!("SCRV1:{password_b64}:{response_b64}");
175 let escaped = quote_and_escape(&value);
176 write_line(dst, &format!("password \"Auth\" {escaped}"))
177 }
178
179 OvpnCommand::NeedOk { ref name, response } => {
181 write_line(dst, &format!("needok {name} {response}"))
182 }
183 OvpnCommand::NeedStr {
184 ref name,
185 ref value,
186 } => {
187 let escaped = quote_and_escape(value);
188 write_line(dst, &format!("needstr {name} {escaped}"))
189 }
190
191 OvpnCommand::Pkcs11IdCount => write_line(dst, "pkcs11-id-count"),
193 OvpnCommand::Pkcs11IdGet(idx) => write_line(dst, &format!("pkcs11-id-get {idx}")),
194
195 OvpnCommand::RsaSig { ref base64_lines } => write_block(dst, "rsa-sig", base64_lines),
203
204 OvpnCommand::ClientAuth {
212 cid,
213 kid,
214 ref config_lines,
215 } => write_block(dst, &format!("client-auth {cid} {kid}"), config_lines),
216
217 OvpnCommand::ClientAuthNt { cid, kid } => {
218 write_line(dst, &format!("client-auth-nt {cid} {kid}"))
219 }
220
221 OvpnCommand::ClientDeny {
222 cid,
223 kid,
224 ref reason,
225 ref client_reason,
226 } => {
227 let r = quote_and_escape(reason);
228 match client_reason {
229 Some(cr) => {
230 let cr_esc = quote_and_escape(cr);
231 write_line(dst, &format!("client-deny {cid} {kid} {r} {cr_esc}"))
232 }
233 None => write_line(dst, &format!("client-deny {cid} {kid} {r}")),
234 }
235 }
236
237 OvpnCommand::ClientKill { cid } => write_line(dst, &format!("client-kill {cid}")),
238
239 OvpnCommand::ClientPf {
246 cid,
247 ref filter_lines,
248 } => write_block(dst, &format!("client-pf {cid}"), filter_lines),
249
250 OvpnCommand::LoadStats => write_line(dst, "load-stats"),
252
253 OvpnCommand::ClientPendingAuth {
255 cid,
256 kid,
257 timeout,
258 ref extra,
259 } => write_line(
260 dst,
261 &format!("client-pending-auth {cid} {kid} {timeout} {extra}"),
262 ),
263
264 OvpnCommand::ClientDenyV2 {
265 cid,
266 kid,
267 ref reason,
268 ref client_reason,
269 ref redirect_url,
270 } => {
271 let r = quote_and_escape(reason);
272 let mut cmd = format!("client-deny-v2 {cid} {kid} {r}");
273 if let Some(cr) = client_reason {
274 cmd.push(' ');
275 cmd.push_str("e_and_escape(cr));
276 if let Some(url) = redirect_url {
277 cmd.push(' ');
278 cmd.push_str("e_and_escape(url));
279 }
280 }
281 write_line(dst, &cmd)
282 }
283
284 OvpnCommand::CrResponse {
285 cid,
286 kid,
287 ref response,
288 } => write_line(dst, &format!("cr-response {cid} {kid} {response}")),
289
290 OvpnCommand::Certificate { ref pem_lines } => {
292 write_block(dst, "certificate", pem_lines)
293 }
294
295 OvpnCommand::BypassMessage(ref msg) => {
297 let escaped = quote_and_escape(msg);
298 write_line(dst, &format!("bypass-message {escaped}"))
299 }
300
301 OvpnCommand::Remote(RemoteAction::Accept) => write_line(dst, "remote ACCEPT"),
303 OvpnCommand::Remote(RemoteAction::Skip) => write_line(dst, "remote SKIP"),
304 OvpnCommand::Remote(RemoteAction::Modify { ref host, port }) => {
305 write_line(dst, &format!("remote MOD {host} {port}"))
306 }
307 OvpnCommand::Proxy(ProxyAction::None) => write_line(dst, "proxy NONE"),
308 OvpnCommand::Proxy(ProxyAction::Http {
309 ref host,
310 port,
311 non_cleartext_only,
312 }) => {
313 let nct = if non_cleartext_only { " nct" } else { "" };
314 write_line(dst, &format!("proxy HTTP {host} {port}{nct}"))
315 }
316 OvpnCommand::Proxy(ProxyAction::Socks { ref host, port }) => {
317 write_line(dst, &format!("proxy SOCKS {host} {port}"))
318 }
319
320 OvpnCommand::ManagementPassword(ref pw) => write_line(dst, pw),
324
325 OvpnCommand::Exit => write_line(dst, "exit"),
327 OvpnCommand::Quit => write_line(dst, "quit"),
328
329 OvpnCommand::Raw(ref cmd) => write_line(dst, cmd),
331 }
332
333 Ok(())
334 }
335}
336
337fn write_line(dst: &mut BytesMut, s: &str) {
339 dst.reserve(s.len() + 1);
340 dst.put_slice(s.as_bytes());
341 dst.put_u8(b'\n');
342}
343
344fn write_block(dst: &mut BytesMut, header: &str, lines: &[String]) {
346 let total: usize = header.len() + 1 + lines.iter().map(|l| l.len() + 1).sum::<usize>() + 4;
347 dst.reserve(total);
348 dst.put_slice(header.as_bytes());
349 dst.put_u8(b'\n');
350 for line in lines {
351 dst.put_slice(line.as_bytes());
352 dst.put_u8(b'\n');
353 }
354 dst.put_slice(b"END\n");
355}
356
357impl Decoder for OvpnCodec {
360 type Item = OvpnMessage;
361 type Error = io::Error;
362
363 fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
364 loop {
365 let newline_pos = match src.iter().position(|&b| b == b'\n') {
367 Some(pos) => pos,
368 None => return Ok(None), };
370
371 let line_bytes = src.split_to(newline_pos + 1);
373 let line = std::str::from_utf8(&line_bytes)
374 .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
375 .trim_end_matches(['\r', '\n'])
376 .to_owned();
377
378 if let Some(ref mut accum) = self.client_notif
387 && let Some(rest) = line.strip_prefix(">CLIENT:ENV,") {
388 if rest == "END" {
389 let finished = self.client_notif.take().unwrap();
390 return Ok(Some(OvpnMessage::Notification(Notification::Client {
391 event: finished.event,
392 cid: finished.cid,
393 kid: finished.kid,
394 env: finished.env,
395 })));
396 } else {
397 let (k, v) = match rest.split_once('=') {
399 Some((k, v)) => (k.to_owned(), v.to_owned()),
400 None => (rest.to_owned(), String::new()),
401 };
402 accum.env.push((k, v));
403 continue; }
405 }
406 if let Some(ref mut buf) = self.multi_line_buf {
411 if line == "END" {
412 let lines = self.multi_line_buf.take().unwrap();
413 return Ok(Some(OvpnMessage::MultiLine(lines)));
414 }
415 if line.starts_with('>') {
420 if let Some(msg) = self.parse_notification(&line) {
421 return Ok(Some(msg));
422 }
423 continue;
426 }
427 buf.push(line);
428 continue; }
430
431 if let Some(rest) = line.strip_prefix("SUCCESS:") {
437 return Ok(Some(OvpnMessage::Success(
438 rest.strip_prefix(' ').unwrap_or(rest).to_owned(),
439 )));
440 }
441 if let Some(rest) = line.strip_prefix("ERROR:") {
442 return Ok(Some(OvpnMessage::Error(
443 rest.strip_prefix(' ').unwrap_or(rest).to_owned(),
444 )));
445 }
446
447 if line == "ENTER PASSWORD:" {
449 return Ok(Some(OvpnMessage::PasswordPrompt));
450 }
451
452 if line.starts_with('>') {
454 if let Some(msg) = self.parse_notification(&line) {
455 return Ok(Some(msg));
456 }
457 continue;
459 }
460
461 match self.expected {
467 ResponseKind::MultiLine => {
468 if line == "END" {
469 return Ok(Some(OvpnMessage::MultiLine(Vec::new())));
471 }
472 self.multi_line_buf = Some(vec![line]);
473 continue; }
475 ResponseKind::SingleValue => {
476 if let Some(parsed) = parse_pkcs11id_entry(&line) {
477 return Ok(Some(parsed));
478 }
479 return Ok(Some(OvpnMessage::SingleValue(line)));
480 }
481 ResponseKind::SuccessOrError | ResponseKind::NoResponse => {
482 return Ok(Some(OvpnMessage::Unrecognized {
483 line,
484 kind: UnrecognizedKind::UnexpectedLine,
485 }));
486 }
487 }
488 }
489 }
490}
491
492impl OvpnCodec {
493 fn parse_notification(&mut self, line: &str) -> Option<OvpnMessage> {
497 let inner = &line[1..]; let (kind, payload) = match inner.split_once(':') {
500 Some((k, p)) => (k, p),
501 None => {
503 return Some(OvpnMessage::Unrecognized {
504 line: line.to_owned(),
505 kind: UnrecognizedKind::MalformedNotification,
506 });
507 }
508 };
509
510 if kind == "INFO" {
513 return Some(OvpnMessage::Info(payload.to_owned()));
514 }
515
516 if kind == "CLIENT" {
518 let (event, args) = match payload.split_once(',') {
519 Some((e, a)) => (e.to_owned(), a.to_owned()),
520 None => (payload.to_owned(), String::new()),
521 };
522
523 if event == "ADDRESS" {
525 let mut parts = args.splitn(3, ',');
526 let cid = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
527 let addr = parts.next().unwrap_or("").to_owned();
528 let primary = parts.next() == Some("1");
529 return Some(OvpnMessage::Notification(Notification::ClientAddress {
530 cid,
531 addr,
532 primary,
533 }));
534 }
535
536 let mut id_parts = args.splitn(3, ',');
541 let cid = id_parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
542 let kid = id_parts.next().and_then(|s| s.parse().ok());
543
544 self.client_notif = Some(ClientNotifAccum {
546 event: ClientEvent::parse(&event),
547 cid,
548 kid,
549 env: Vec::new(),
550 });
551 return None; }
553
554 let notification = match kind {
556 "STATE" => parse_state(payload),
557 "BYTECOUNT" => parse_bytecount(payload),
558 "BYTECOUNT_CLI" => parse_bytecount_cli(payload),
559 "LOG" => parse_log(payload),
560 "ECHO" => parse_echo(payload),
561 "HOLD" => Some(Notification::Hold {
562 text: payload.to_owned(),
563 }),
564 "FATAL" => Some(Notification::Fatal {
565 message: payload.to_owned(),
566 }),
567 "PKCS11ID-COUNT" => parse_pkcs11id_count(payload),
568 "NEED-OK" => parse_need_ok(payload),
569 "NEED-STR" => parse_need_str(payload),
570 "RSA_SIGN" => Some(Notification::RsaSign {
571 data: payload.to_owned(),
572 }),
573 "REMOTE" => parse_remote(payload),
574 "PROXY" => parse_proxy(payload),
575 "PASSWORD" => parse_password(payload),
576 _ => None,
577 };
578
579 Some(OvpnMessage::Notification(notification.unwrap_or(
580 Notification::Simple {
581 kind: kind.to_owned(),
582 payload: payload.to_owned(),
583 },
584 )))
585 }
586}
587
588fn parse_state(payload: &str) -> Option<Notification> {
596 let mut parts = payload.splitn(9, ',');
601 let timestamp = parts.next()?.parse().ok()?;
602 let name = OpenVpnState::parse(parts.next()?);
603 let description = parts.next()?.to_owned();
604 let local_ip = parts.next()?.to_owned();
605 let remote_ip = parts.next()?.to_owned();
606 let local_port = parts.next().unwrap_or("").to_owned();
607 let _local_addr = parts.next(); let remote_port = parts.next().unwrap_or("").to_owned();
609 Some(Notification::State {
610 timestamp,
611 name,
612 description,
613 local_ip,
614 remote_ip,
615 local_port,
616 remote_port,
617 })
618}
619
620fn parse_bytecount(payload: &str) -> Option<Notification> {
621 let (a, b) = payload.split_once(',')?;
622 Some(Notification::ByteCount {
623 bytes_in: a.parse().ok()?,
624 bytes_out: b.parse().ok()?,
625 })
626}
627
628fn parse_bytecount_cli(payload: &str) -> Option<Notification> {
629 let mut parts = payload.splitn(3, ',');
630 let cid = parts.next()?.parse().ok()?;
631 let bytes_in = parts.next()?.parse().ok()?;
632 let bytes_out = parts.next()?.parse().ok()?;
633 Some(Notification::ByteCountCli {
634 cid,
635 bytes_in,
636 bytes_out,
637 })
638}
639
640fn parse_log(payload: &str) -> Option<Notification> {
641 let (ts_str, rest) = payload.split_once(',')?;
642 let timestamp = ts_str.parse().ok()?;
643 let (level_str, message) = rest.split_once(',')?;
644 Some(Notification::Log {
645 timestamp,
646 level: LogLevel::parse(level_str),
647 message: message.to_owned(),
648 })
649}
650
651fn parse_echo(payload: &str) -> Option<Notification> {
652 let (ts_str, param) = payload.split_once(',')?;
653 let timestamp = ts_str.parse().ok()?;
654 Some(Notification::Echo {
655 timestamp,
656 param: param.to_owned(),
657 })
658}
659
660fn parse_pkcs11id_count(payload: &str) -> Option<Notification> {
661 let count = payload.trim().parse().ok()?;
662 Some(Notification::Pkcs11IdCount { count })
663}
664
665fn parse_pkcs11id_entry(line: &str) -> Option<OvpnMessage> {
667 let rest = line.strip_prefix("PKCS11ID-ENTRY:'")?;
668 let (index, rest) = rest.split_once("', ID:'")?;
669 let (id, rest) = rest.split_once("', BLOB:'")?;
670 let blob = rest.strip_suffix('\'')?;
671 Some(OvpnMessage::Pkcs11IdEntry {
672 index: index.to_owned(),
673 id: id.to_owned(),
674 blob: blob.to_owned(),
675 })
676}
677
678fn parse_need_ok(payload: &str) -> Option<Notification> {
680 let rest = payload.strip_prefix("Need '")?;
682 let (name, rest) = rest.split_once('\'')?;
683 let msg = rest.split_once("MSG:")?.1;
684 Some(Notification::NeedOk {
685 name: name.to_owned(),
686 message: msg.to_owned(),
687 })
688}
689
690fn parse_need_str(payload: &str) -> Option<Notification> {
692 let rest = payload.strip_prefix("Need '")?;
693 let (name, rest) = rest.split_once('\'')?;
694 let msg = rest.split_once("MSG:")?.1;
695 Some(Notification::NeedStr {
696 name: name.to_owned(),
697 message: msg.to_owned(),
698 })
699}
700
701fn parse_remote(payload: &str) -> Option<Notification> {
702 let mut parts = payload.splitn(3, ',');
703 let host = parts.next()?.to_owned();
704 let port = parts.next()?.parse().ok()?;
705 let protocol = TransportProtocol::parse(parts.next()?);
706 Some(Notification::Remote {
707 host,
708 port,
709 protocol,
710 })
711}
712
713fn parse_proxy(payload: &str) -> Option<Notification> {
714 let mut parts = payload.splitn(4, ',');
715 let proto_num = parts.next()?.parse().ok()?;
716 let proto_type = TransportProtocol::parse(parts.next()?);
717 let host = parts.next()?.to_owned();
718 let port = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
719 Some(Notification::Proxy {
720 proto_num,
721 proto_type,
722 host,
723 port,
724 })
725}
726
727use crate::message::PasswordNotification;
728
729use crate::auth::AuthType;
730
731fn parse_auth_type(s: &str) -> AuthType {
733 match s {
734 "Auth" => AuthType::Auth,
735 "Private Key" => AuthType::PrivateKey,
736 "HTTP Proxy" => AuthType::HttpProxy,
737 "SOCKS Proxy" => AuthType::SocksProxy,
738 other => AuthType::Custom(other.to_owned()),
739 }
740}
741
742fn parse_password(payload: &str) -> Option<Notification> {
743 if let Some(rest) = payload.strip_prefix("Verification Failed: '") {
745 let auth_type = rest.strip_suffix('\'')?;
746 return Some(Notification::Password(
747 PasswordNotification::VerificationFailed {
748 auth_type: parse_auth_type(auth_type),
749 },
750 ));
751 }
752
753 let rest = payload.strip_prefix("Need '")?;
756 let (auth_type_str, rest) = rest.split_once('\'')?;
757 let rest = rest.trim_start();
758
759 if let Some(after_up) = rest.strip_prefix("username/password") {
761 let after_up = after_up.trim_start();
762
763 if let Some(sc) = after_up.strip_prefix("SC:") {
765 let (echo_str, challenge) = sc.split_once(',')?;
766 return Some(Notification::Password(
767 PasswordNotification::StaticChallenge {
768 echo: echo_str == "1",
769 challenge: challenge.to_owned(),
770 },
771 ));
772 }
773
774 if let Some(crv1) = after_up.strip_prefix("CRV1:") {
776 let mut parts = crv1.splitn(4, ':');
777 let flags = parts.next()?.to_owned();
778 let state_id = parts.next()?.to_owned();
779 let username_b64 = parts.next()?.to_owned();
780 let challenge = parts.next()?.to_owned();
781 return Some(Notification::Password(
782 PasswordNotification::DynamicChallenge {
783 flags,
784 state_id,
785 username_b64,
786 challenge,
787 },
788 ));
789 }
790
791 return Some(Notification::Password(PasswordNotification::NeedAuth {
793 auth_type: parse_auth_type(auth_type_str),
794 }));
795 }
796
797 if rest.starts_with("password") {
799 return Some(Notification::Password(PasswordNotification::NeedPassword {
800 auth_type: parse_auth_type(auth_type_str),
801 }));
802 }
803
804 None }
806
807#[cfg(test)]
808mod tests {
809 use super::*;
810 use crate::auth::AuthType;
811 use crate::client_event::ClientEvent;
812 use crate::message::PasswordNotification;
813 use crate::signal::Signal;
814 use crate::status_format::StatusFormat;
815 use crate::stream_mode::StreamMode;
816 use bytes::BytesMut;
817 use tokio_util::codec::{Decoder, Encoder};
818
819 fn encode_to_string(cmd: OvpnCommand) -> String {
821 let mut codec = OvpnCodec::new();
822 let mut buf = BytesMut::new();
823 codec.encode(cmd, &mut buf).unwrap();
824 String::from_utf8(buf.to_vec()).unwrap()
825 }
826
827 fn decode_all(input: &str) -> Vec<OvpnMessage> {
829 let mut codec = OvpnCodec::new();
830 let mut buf = BytesMut::from(input);
831 let mut msgs = Vec::new();
832 while let Some(msg) = codec.decode(&mut buf).unwrap() {
833 msgs.push(msg);
834 }
835 msgs
836 }
837
838 fn encode_then_decode(cmd: OvpnCommand, response: &str) -> Vec<OvpnMessage> {
840 let mut codec = OvpnCodec::new();
841 let mut enc_buf = BytesMut::new();
842 codec.encode(cmd, &mut enc_buf).unwrap();
843 let mut dec_buf = BytesMut::from(response);
844 let mut msgs = Vec::new();
845 while let Some(msg) = codec.decode(&mut dec_buf).unwrap() {
846 msgs.push(msg);
847 }
848 msgs
849 }
850
851 #[test]
854 fn encode_status_v1() {
855 assert_eq!(
856 encode_to_string(OvpnCommand::Status(StatusFormat::V1)),
857 "status\n"
858 );
859 }
860
861 #[test]
862 fn encode_status_v3() {
863 assert_eq!(
864 encode_to_string(OvpnCommand::Status(StatusFormat::V3)),
865 "status 3\n"
866 );
867 }
868
869 #[test]
870 fn encode_signal() {
871 assert_eq!(
872 encode_to_string(OvpnCommand::Signal(Signal::SigUsr1)),
873 "signal SIGUSR1\n"
874 );
875 }
876
877 #[test]
878 fn encode_state_on_all() {
879 assert_eq!(
880 encode_to_string(OvpnCommand::StateStream(StreamMode::OnAll)),
881 "state on all\n"
882 );
883 }
884
885 #[test]
886 fn encode_state_recent() {
887 assert_eq!(
888 encode_to_string(OvpnCommand::StateStream(StreamMode::Recent(5))),
889 "state 5\n"
890 );
891 }
892
893 #[test]
894 fn encode_password_escaping() {
895 let wire = encode_to_string(OvpnCommand::Password {
898 auth_type: AuthType::PrivateKey,
899 value: r#"foo\"bar"#.to_owned(),
900 });
901 assert_eq!(wire, "password \"Private Key\" \"foo\\\\\\\"bar\"\n");
902 }
903
904 #[test]
905 fn encode_password_simple() {
906 let wire = encode_to_string(OvpnCommand::Password {
907 auth_type: AuthType::Auth,
908 value: "hunter2".to_owned(),
909 });
910 assert_eq!(wire, "password \"Auth\" \"hunter2\"\n");
911 }
912
913 #[test]
914 fn encode_client_auth_with_config() {
915 let wire = encode_to_string(OvpnCommand::ClientAuth {
916 cid: 42,
917 kid: 0,
918 config_lines: vec![
919 "push \"route 10.0.0.0 255.255.0.0\"".to_owned(),
920 "push \"dhcp-option DNS 10.0.0.1\"".to_owned(),
921 ],
922 });
923 assert_eq!(
924 wire,
925 "client-auth 42 0\n\
926 push \"route 10.0.0.0 255.255.0.0\"\n\
927 push \"dhcp-option DNS 10.0.0.1\"\n\
928 END\n"
929 );
930 }
931
932 #[test]
933 fn encode_client_auth_empty_config() {
934 let wire = encode_to_string(OvpnCommand::ClientAuth {
935 cid: 1,
936 kid: 0,
937 config_lines: vec![],
938 });
939 assert_eq!(wire, "client-auth 1 0\nEND\n");
940 }
941
942 #[test]
943 fn encode_client_deny_with_client_reason() {
944 let wire = encode_to_string(OvpnCommand::ClientDeny {
945 cid: 5,
946 kid: 0,
947 reason: "cert revoked".to_owned(),
948 client_reason: Some("Your access has been revoked.".to_owned()),
949 });
950 assert_eq!(
951 wire,
952 "client-deny 5 0 \"cert revoked\" \"Your access has been revoked.\"\n"
953 );
954 }
955
956 #[test]
957 fn encode_rsa_sig() {
958 let wire = encode_to_string(OvpnCommand::RsaSig {
959 base64_lines: vec!["AAAA".to_owned(), "BBBB".to_owned()],
960 });
961 assert_eq!(wire, "rsa-sig\nAAAA\nBBBB\nEND\n");
962 }
963
964 #[test]
965 fn encode_remote_modify() {
966 let wire = encode_to_string(OvpnCommand::Remote(RemoteAction::Modify {
967 host: "vpn.example.com".to_owned(),
968 port: 1234,
969 }));
970 assert_eq!(wire, "remote MOD vpn.example.com 1234\n");
971 }
972
973 #[test]
974 fn encode_proxy_http_nct() {
975 let wire = encode_to_string(OvpnCommand::Proxy(ProxyAction::Http {
976 host: "proxy.local".to_owned(),
977 port: 8080,
978 non_cleartext_only: true,
979 }));
980 assert_eq!(wire, "proxy HTTP proxy.local 8080 nct\n");
981 }
982
983 #[test]
984 fn encode_needok() {
985 use crate::need_ok::NeedOkResponse;
986 let wire = encode_to_string(OvpnCommand::NeedOk {
987 name: "token-insertion-request".to_owned(),
988 response: NeedOkResponse::Ok,
989 });
990 assert_eq!(wire, "needok token-insertion-request ok\n");
991 }
992
993 #[test]
994 fn encode_needstr() {
995 let wire = encode_to_string(OvpnCommand::NeedStr {
996 name: "name".to_owned(),
997 value: "John".to_owned(),
998 });
999 assert_eq!(wire, "needstr name \"John\"\n");
1000 }
1001
1002 #[test]
1003 fn encode_forget_passwords() {
1004 assert_eq!(
1005 encode_to_string(OvpnCommand::ForgetPasswords),
1006 "forget-passwords\n"
1007 );
1008 }
1009
1010 #[test]
1011 fn encode_hold_query() {
1012 assert_eq!(encode_to_string(OvpnCommand::HoldQuery), "hold\n");
1013 }
1014
1015 #[test]
1016 fn encode_echo_on_all() {
1017 assert_eq!(
1018 encode_to_string(OvpnCommand::Echo(StreamMode::OnAll)),
1019 "echo on all\n"
1020 );
1021 }
1022
1023 #[test]
1024 fn encode_client_pf() {
1025 let wire = encode_to_string(OvpnCommand::ClientPf {
1026 cid: 42,
1027 filter_lines: vec![
1028 "[CLIENTS ACCEPT]".to_owned(),
1029 "-accounting".to_owned(),
1030 "[SUBNETS DROP]".to_owned(),
1031 "+10.0.0.0/8".to_owned(),
1032 "[END]".to_owned(),
1033 ],
1034 });
1035 assert_eq!(
1036 wire,
1037 "client-pf 42\n\
1038 [CLIENTS ACCEPT]\n\
1039 -accounting\n\
1040 [SUBNETS DROP]\n\
1041 +10.0.0.0/8\n\
1042 [END]\n\
1043 END\n"
1044 );
1045 }
1046
1047 #[test]
1050 fn decode_success() {
1051 let msgs = decode_all("SUCCESS: pid=12345\n");
1052 assert_eq!(msgs.len(), 1);
1053 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s == "pid=12345"));
1054 }
1055
1056 #[test]
1057 fn decode_success_bare() {
1058 let msgs = decode_all("SUCCESS:\n");
1060 assert_eq!(msgs.len(), 1);
1061 assert!(matches!(&msgs[0], OvpnMessage::Success(s) if s.is_empty()));
1062 }
1063
1064 #[test]
1065 fn decode_error() {
1066 let msgs = decode_all("ERROR: unknown command\n");
1067 assert_eq!(msgs.len(), 1);
1068 assert!(matches!(&msgs[0], OvpnMessage::Error(s) if s == "unknown command"));
1069 }
1070
1071 #[test]
1072 fn decode_info_notification() {
1073 let msgs = decode_all(">INFO:OpenVPN Management Interface Version 5\n");
1074 assert_eq!(msgs.len(), 1);
1075 assert!(matches!(
1076 &msgs[0],
1077 OvpnMessage::Info(s) if s == "OpenVPN Management Interface Version 5"
1078 ));
1079 }
1080
1081 #[test]
1082 fn decode_state_notification() {
1083 let msgs = decode_all(">STATE:1234567890,CONNECTED,SUCCESS,,10.0.0.1\n");
1084 assert_eq!(msgs.len(), 1);
1085 match &msgs[0] {
1086 OvpnMessage::Notification(Notification::State {
1087 timestamp,
1088 name,
1089 description,
1090 local_ip,
1091 remote_ip,
1092 ..
1093 }) => {
1094 assert_eq!(*timestamp, 1234567890);
1095 assert_eq!(*name, OpenVpnState::Connected);
1096 assert_eq!(description, "SUCCESS");
1097 assert_eq!(local_ip, "");
1098 assert_eq!(remote_ip, "10.0.0.1");
1099 }
1100 other => panic!("unexpected: {other:?}"),
1101 }
1102 }
1103
1104 #[test]
1105 fn decode_multiline_with_command_tracking() {
1106 let msgs = encode_then_decode(
1110 OvpnCommand::Status(StatusFormat::V1),
1111 "OpenVPN CLIENT LIST\nCommon Name,Real Address\ntest,1.2.3.4:1234\nEND\n",
1112 );
1113 assert_eq!(msgs.len(), 1);
1114 match &msgs[0] {
1115 OvpnMessage::MultiLine(lines) => {
1116 assert_eq!(lines.len(), 3);
1117 assert_eq!(lines[0], "OpenVPN CLIENT LIST");
1118 assert_eq!(lines[2], "test,1.2.3.4:1234");
1119 }
1120 other => panic!("unexpected: {other:?}"),
1121 }
1122 }
1123
1124 #[test]
1125 fn decode_hold_query_single_value() {
1126 let msgs = encode_then_decode(OvpnCommand::HoldQuery, "0\n");
1128 assert_eq!(msgs.len(), 1);
1129 assert!(matches!(&msgs[0], OvpnMessage::SingleValue(s) if s == "0"));
1130 }
1131
1132 #[test]
1133 fn decode_bare_state_single_value() {
1134 let msgs = encode_then_decode(
1135 OvpnCommand::State,
1136 "1234567890,CONNECTED,SUCCESS,,10.0.0.1,,\n",
1137 );
1138 assert_eq!(msgs.len(), 1);
1139 assert!(matches!(&msgs[0], OvpnMessage::SingleValue(s) if s.starts_with("1234567890")));
1140 }
1141
1142 #[test]
1143 fn decode_notification_during_multiline() {
1144 let msgs = encode_then_decode(
1147 OvpnCommand::Status(StatusFormat::V1),
1148 "header line\n>BYTECOUNT:1000,2000\ndata line\nEND\n",
1149 );
1150 assert_eq!(msgs.len(), 2);
1151 assert!(matches!(
1153 &msgs[0],
1154 OvpnMessage::Notification(Notification::ByteCount {
1155 bytes_in: 1000,
1156 bytes_out: 2000
1157 })
1158 ));
1159 match &msgs[1] {
1161 OvpnMessage::MultiLine(lines) => {
1162 assert_eq!(lines, &["header line", "data line"]);
1163 }
1164 other => panic!("unexpected: {other:?}"),
1165 }
1166 }
1167
1168 #[test]
1169 fn decode_client_connect_multiline_notification() {
1170 let input = "\
1171 >CLIENT:CONNECT,0,1\n\
1172 >CLIENT:ENV,untrusted_ip=1.2.3.4\n\
1173 >CLIENT:ENV,common_name=TestClient\n\
1174 >CLIENT:ENV,END\n";
1175 let msgs = decode_all(input);
1176 assert_eq!(msgs.len(), 1);
1177 match &msgs[0] {
1178 OvpnMessage::Notification(Notification::Client {
1179 event,
1180 cid,
1181 kid,
1182 env,
1183 }) => {
1184 assert_eq!(*event, ClientEvent::Connect);
1185 assert_eq!(*cid, 0);
1186 assert_eq!(*kid, Some(1));
1187 assert_eq!(env.len(), 2);
1188 assert_eq!(env[0], ("untrusted_ip".to_owned(), "1.2.3.4".to_owned()));
1189 assert_eq!(env[1], ("common_name".to_owned(), "TestClient".to_owned()));
1190 }
1191 other => panic!("unexpected: {other:?}"),
1192 }
1193 }
1194
1195 #[test]
1196 fn decode_client_address_single_line() {
1197 let msgs = decode_all(">CLIENT:ADDRESS,3,10.0.0.5,1\n");
1198 assert_eq!(msgs.len(), 1);
1199 match &msgs[0] {
1200 OvpnMessage::Notification(Notification::ClientAddress { cid, addr, primary }) => {
1201 assert_eq!(*cid, 3);
1202 assert_eq!(addr, "10.0.0.5");
1203 assert!(*primary);
1204 }
1205 other => panic!("unexpected: {other:?}"),
1206 }
1207 }
1208
1209 #[test]
1210 fn decode_client_disconnect() {
1211 let input = "\
1212 >CLIENT:DISCONNECT,5\n\
1213 >CLIENT:ENV,bytes_received=12345\n\
1214 >CLIENT:ENV,bytes_sent=67890\n\
1215 >CLIENT:ENV,END\n";
1216 let msgs = decode_all(input);
1217 assert_eq!(msgs.len(), 1);
1218 match &msgs[0] {
1219 OvpnMessage::Notification(Notification::Client {
1220 event,
1221 cid,
1222 kid,
1223 env,
1224 }) => {
1225 assert_eq!(*event, ClientEvent::Disconnect);
1226 assert_eq!(*cid, 5);
1227 assert_eq!(*kid, None);
1228 assert_eq!(env.len(), 2);
1229 }
1230 other => panic!("unexpected: {other:?}"),
1231 }
1232 }
1233
1234 #[test]
1235 fn decode_password_notification() {
1236 let msgs = decode_all(">PASSWORD:Need 'Auth' username/password\n");
1237 assert_eq!(msgs.len(), 1);
1238 match &msgs[0] {
1239 OvpnMessage::Notification(Notification::Password(PasswordNotification::NeedAuth {
1240 auth_type,
1241 })) => {
1242 assert_eq!(*auth_type, AuthType::Auth);
1243 }
1244 other => panic!("unexpected: {other:?}"),
1245 }
1246 }
1247
1248 #[test]
1249 fn quote_and_escape_special_chars() {
1250 assert_eq!(quote_and_escape(r#"foo"bar"#), r#""foo\"bar""#);
1251 assert_eq!(quote_and_escape(r"a\b"), r#""a\\b""#);
1252 assert_eq!(quote_and_escape("simple"), r#""simple""#);
1253 }
1254
1255 #[test]
1256 fn decode_empty_multiline() {
1257 let msgs = encode_then_decode(OvpnCommand::Status(StatusFormat::V1), "END\n");
1259 assert_eq!(msgs.len(), 1);
1260 assert!(matches!(&msgs[0], OvpnMessage::MultiLine(lines) if lines.is_empty()));
1261 }
1262
1263 #[test]
1264 fn decode_need_ok_notification() {
1265 let msgs = decode_all(
1266 ">NEED-OK:Need 'token-insertion-request' confirmation MSG:Please insert your token\n",
1267 );
1268 assert_eq!(msgs.len(), 1);
1269 match &msgs[0] {
1270 OvpnMessage::Notification(Notification::NeedOk { name, message }) => {
1271 assert_eq!(name, "token-insertion-request");
1272 assert_eq!(message, "Please insert your token");
1273 }
1274 other => panic!("unexpected: {other:?}"),
1275 }
1276 }
1277
1278 #[test]
1279 fn decode_hold_notification() {
1280 let msgs = decode_all(">HOLD:Waiting for hold release\n");
1281 assert_eq!(msgs.len(), 1);
1282 match &msgs[0] {
1283 OvpnMessage::Notification(Notification::Hold { text }) => {
1284 assert_eq!(text, "Waiting for hold release");
1285 }
1286 other => panic!("unexpected: {other:?}"),
1287 }
1288 }
1289}