1use std::fmt;
2use std::str::FromStr;
3
4use crate::{
5 auth::{AuthRetryMode, AuthType, ParseAuthRetryModeError},
6 kill_target::KillTarget,
7 need_ok::NeedOkResponse,
8 proxy_action::ProxyAction,
9 redacted::Redacted,
10 remote_action::RemoteAction,
11 signal::{ParseSignalError, Signal},
12 status_format::StatusFormat,
13 stream_mode::{ParseStreamModeError, StreamMode},
14 transport_protocol::TransportProtocol,
15};
16use tracing::warn;
17
18#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
20pub enum CommandParseError {
21 #[error(transparent)]
23 Signal(#[from] ParseSignalError),
24
25 #[error(transparent)]
27 StreamMode(#[from] ParseStreamModeError),
28
29 #[error(transparent)]
31 AuthRetryMode(#[from] ParseAuthRetryModeError),
32
33 #[error("{0}")]
36 Syntax(String),
37}
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum RemoteEntryRange {
42 Single(u32),
44 Range {
46 from: u32,
48 to: u32,
50 },
51 All,
53}
54
55impl fmt::Display for RemoteEntryRange {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match self {
58 Self::Single(i) => write!(f, "{i}"),
59 Self::Range { from, to } => write!(f, "{from} {to}"),
60 Self::All => f.write_str("all"),
61 }
62 }
63}
64
65#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
75#[strum(serialize_all = "kebab-case")]
76pub enum OvpnCommand {
77 Status(StatusFormat),
81
82 State,
85
86 StateStream(StreamMode),
89
90 Version,
93
94 Pid,
97
98 Help,
101
102 Verb(Option<u8>),
106
107 Mute(Option<u32>),
110
111 Net,
114
115 Log(StreamMode),
119
120 Echo(StreamMode),
123
124 ByteCount(u32),
128
129 Signal(Signal),
133
134 Kill(KillTarget),
137
138 HoldQuery,
142
143 HoldOn,
146
147 HoldOff,
150
151 HoldRelease,
155
156 Username {
160 auth_type: AuthType,
162 value: Redacted,
164 },
165
166 Password {
170 auth_type: AuthType,
172 value: Redacted,
174 },
175
176 AuthRetry(AuthRetryMode),
179
180 ForgetPasswords,
183
184 ChallengeResponse {
188 state_id: String,
190 response: Redacted,
192 },
193
194 StaticChallengeResponse {
200 password_b64: Redacted,
202 response_b64: Redacted,
204 },
205
206 NeedOk {
210 name: String,
212 response: NeedOkResponse,
214 },
215
216 NeedStr {
219 name: String,
221 value: String,
223 },
224
225 Pkcs11IdCount,
229
230 Pkcs11IdGet(u32),
233
234 RsaSig {
239 base64_lines: Vec<String>,
241 },
242
243 ClientAuth {
249 cid: u64,
251 kid: u64,
253 config_lines: Vec<String>,
255 },
256
257 ClientAuthNt {
260 cid: u64,
262 kid: u64,
264 },
265
266 ClientDeny {
269 cid: u64,
271 kid: u64,
273 reason: String,
275 client_reason: Option<String>,
277 },
278
279 ClientKill {
283 cid: u64,
285 message: Option<String>,
288 },
289
290 Remote(RemoteAction),
294
295 Proxy(ProxyAction),
298
299 LoadStats,
304
305 ClientPendingAuth {
309 cid: u64,
311 kid: u64,
313 extra: String,
315 timeout: u32,
317 },
318
319 CrResponse {
322 response: Redacted,
324 },
325
326 PkSig {
331 base64_lines: Vec<String>,
333 },
334
335 EnvFilter(u32),
341
342 RemoteEntryCount,
347
348 RemoteEntryGet(RemoteEntryRange),
352
353 PushUpdateBroad {
357 options: String,
359 },
360
361 PushUpdateCid {
364 cid: u64,
366 options: String,
368 },
369
370 Certificate {
375 pem_lines: Vec<String>,
377 },
378
379 ManagementPassword(Redacted),
385
386 Exit,
390
391 Quit,
393
394 Raw(String),
398
399 RawMultiLine(String),
407}
408
409#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub(crate) enum ResponseKind {
414 SuccessOrError,
416
417 MultiLine,
419
420 NoResponse,
422}
423
424impl OvpnCommand {
425 pub(crate) fn expected_response(&self) -> ResponseKind {
428 match self {
429 Self::Status(_)
431 | Self::Version
432 | Self::Help
433 | Self::Net
434 | Self::RemoteEntryCount
435 | Self::RemoteEntryGet(_) => ResponseKind::MultiLine,
436
437 Self::StateStream(mode) | Self::Log(mode) | Self::Echo(mode) => match mode {
439 StreamMode::All | StreamMode::OnAll | StreamMode::Recent(_) => {
440 ResponseKind::MultiLine
441 }
442 StreamMode::On | StreamMode::Off => ResponseKind::SuccessOrError,
443 },
444
445 Self::State => ResponseKind::MultiLine,
447
448 Self::RawMultiLine(_) => ResponseKind::MultiLine,
450
451 Self::Exit | Self::Quit => ResponseKind::NoResponse,
453
454 _ => ResponseKind::SuccessOrError,
456 }
457 }
458}
459
460impl FromStr for OvpnCommand {
461 type Err = CommandParseError;
462
463 fn from_str(line: &str) -> Result<Self, Self::Err> {
486 fn cmd_err<T>(msg: impl Into<String>) -> Result<T, CommandParseError> {
488 Err(CommandParseError::Syntax(msg.into()))
489 }
490
491 let line = line.trim();
492 let (cmd, args) = line
493 .split_once(char::is_whitespace)
494 .map(|(c, a)| (c, a.trim()))
495 .unwrap_or((line, ""));
496
497 match cmd {
498 "version" => Ok(Self::Version),
500 "pid" => Ok(Self::Pid),
501 "help" => Ok(Self::Help),
502 "net" => Ok(Self::Net),
503 "load-stats" => Ok(Self::LoadStats),
504
505 "status" => match args {
506 "" | "1" => Ok(Self::Status(StatusFormat::V1)),
507 "2" => Ok(Self::Status(StatusFormat::V2)),
508 "3" => Ok(Self::Status(StatusFormat::V3)),
509 _ => cmd_err(format!("invalid status format: {args} (use 1, 2, or 3)")),
510 },
511
512 "state" => match args {
513 "" => Ok(Self::State),
514 other => Ok(Self::StateStream(other.parse::<StreamMode>()?)),
515 },
516
517 "log" => Ok(Self::Log(args.parse::<StreamMode>()?)),
518 "echo" => Ok(Self::Echo(args.parse::<StreamMode>()?)),
519
520 "verb" => {
521 if args.is_empty() {
522 Ok(Self::Verb(None))
523 } else {
524 args.parse::<u8>()
525 .map(|n| Self::Verb(Some(n)))
526 .map_err(|_| {
527 CommandParseError::Syntax(format!("invalid verbosity: {args} (0-15)"))
528 })
529 }
530 }
531
532 "mute" => {
533 if args.is_empty() {
534 Ok(Self::Mute(None))
535 } else {
536 args.parse::<u32>()
537 .map(|n| Self::Mute(Some(n)))
538 .map_err(|_| {
539 CommandParseError::Syntax(format!("invalid mute value: {args}"))
540 })
541 }
542 }
543
544 "bytecount" => args.parse::<u32>().map(Self::ByteCount).map_err(|_| {
545 CommandParseError::Syntax(format!("bytecount requires a number, got: {args}"))
546 }),
547
548 "signal" => Ok(Self::Signal(args.parse::<Signal>()?)),
550
551 "kill" => {
552 if args.is_empty() {
553 return cmd_err("kill requires a target (common name or proto:ip:port)");
554 }
555 let parts: Vec<&str> = args.splitn(3, ':').collect();
556 if parts.len() == 3
557 && let Ok(port) = parts[2].parse::<u16>()
558 {
559 return Ok(Self::Kill(KillTarget::Address {
560 protocol: parts[0]
561 .parse()
562 .inspect_err(|error| warn!(%error, "unknown transport protocol"))
563 .unwrap_or_else(|_| TransportProtocol::Unknown(parts[0].to_string())),
564 ip: parts[1].to_string(),
565 port,
566 }));
567 }
568 Ok(Self::Kill(KillTarget::CommonName(args.to_string())))
569 }
570
571 "hold" => match args {
572 "" => Ok(Self::HoldQuery),
573 "on" => Ok(Self::HoldOn),
574 "off" => Ok(Self::HoldOff),
575 "release" => Ok(Self::HoldRelease),
576 _ => cmd_err(format!("invalid hold argument: {args}")),
577 },
578
579 "username" => {
581 let (auth_type, value) =
582 args.split_once(char::is_whitespace)
583 .ok_or(CommandParseError::Syntax(
584 "usage: username <auth-type> <value>".into(),
585 ))?;
586 Ok(Self::Username {
587 auth_type: auth_type
588 .parse()
589 .inspect_err(|error| warn!(%error, "unknown auth type"))
590 .unwrap_or_else(|_| AuthType::Unknown(auth_type.to_string())),
591 value: value.trim().into(),
592 })
593 }
594
595 "password" => {
596 let (auth_type, value) =
597 args.split_once(char::is_whitespace)
598 .ok_or(CommandParseError::Syntax(
599 "usage: password <auth-type> <value>".into(),
600 ))?;
601 Ok(Self::Password {
602 auth_type: auth_type
603 .parse()
604 .inspect_err(|error| warn!(%error, "unknown auth type"))
605 .unwrap_or_else(|_| AuthType::Unknown(auth_type.to_string())),
606 value: value.trim().into(),
607 })
608 }
609
610 "auth-retry" => Ok(Self::AuthRetry(args.parse::<AuthRetryMode>()?)),
611
612 "forget-passwords" => Ok(Self::ForgetPasswords),
613
614 "needok" => {
616 let (name, resp) =
617 args.rsplit_once(char::is_whitespace)
618 .ok_or(CommandParseError::Syntax(
619 "usage: needok <name> ok|cancel".into(),
620 ))?;
621 let response = match resp {
622 "ok" => NeedOkResponse::Ok,
623 "cancel" => NeedOkResponse::Cancel,
624 _ => {
625 return cmd_err(format!("invalid needok response: {resp} (use ok/cancel)"));
626 }
627 };
628 Ok(Self::NeedOk {
629 name: name.trim().to_string(),
630 response,
631 })
632 }
633
634 "needstr" => {
635 let (name, value) =
636 args.split_once(char::is_whitespace)
637 .ok_or(CommandParseError::Syntax(
638 "usage: needstr <name> <value>".into(),
639 ))?;
640 Ok(Self::NeedStr {
641 name: name.to_string(),
642 value: value.trim().to_string(),
643 })
644 }
645
646 "pkcs11-id-count" => Ok(Self::Pkcs11IdCount),
648
649 "pkcs11-id-get" => args.parse::<u32>().map(Self::Pkcs11IdGet).map_err(|_| {
650 CommandParseError::Syntax(format!("pkcs11-id-get requires a number, got: {args}"))
651 }),
652
653 "client-auth" => {
655 let mut parts = args.splitn(3, char::is_whitespace);
656 let cid = parts
657 .next()
658 .ok_or(CommandParseError::Syntax(
659 "usage: client-auth <cid> <kid> [config-lines]".into(),
660 ))?
661 .parse::<u64>()
662 .map_err(|_| CommandParseError::Syntax("cid must be a number".into()))?;
663 let kid = parts
664 .next()
665 .ok_or(CommandParseError::Syntax(
666 "usage: client-auth <cid> <kid> [config-lines]".into(),
667 ))?
668 .parse::<u64>()
669 .map_err(|_| CommandParseError::Syntax("kid must be a number".into()))?;
670 let config_lines = match parts.next() {
671 Some(rest) => rest.split(',').map(|s| s.trim().to_string()).collect(),
672 None => vec![],
673 };
674 Ok(Self::ClientAuth {
675 cid,
676 kid,
677 config_lines,
678 })
679 }
680
681 "client-auth-nt" => {
682 let (cid_s, kid_s) =
683 args.split_once(char::is_whitespace)
684 .ok_or(CommandParseError::Syntax(
685 "usage: client-auth-nt <cid> <kid>".into(),
686 ))?;
687 Ok(Self::ClientAuthNt {
688 cid: cid_s
689 .parse()
690 .map_err(|_| CommandParseError::Syntax("cid must be a number".into()))?,
691 kid: kid_s
692 .trim()
693 .parse()
694 .map_err(|_| CommandParseError::Syntax("kid must be a number".into()))?,
695 })
696 }
697
698 "client-deny" => {
699 let mut parts = args.splitn(4, char::is_whitespace);
700 let cid = parts
701 .next()
702 .ok_or(CommandParseError::Syntax(
703 "usage: client-deny <cid> <kid> <reason> [client-reason]".into(),
704 ))?
705 .parse::<u64>()
706 .map_err(|_| CommandParseError::Syntax("cid must be a number".into()))?;
707 let kid = parts
708 .next()
709 .ok_or(CommandParseError::Syntax(
710 "usage: client-deny <cid> <kid> <reason> [client-reason]".into(),
711 ))?
712 .parse::<u64>()
713 .map_err(|_| CommandParseError::Syntax("kid must be a number".into()))?;
714 let reason = parts
715 .next()
716 .ok_or(CommandParseError::Syntax(
717 "usage: client-deny <cid> <kid> <reason> [client-reason]".into(),
718 ))?
719 .to_string();
720 let client_reason = parts.next().map(|s| s.to_string());
721 Ok(Self::ClientDeny {
722 cid,
723 kid,
724 reason,
725 client_reason,
726 })
727 }
728
729 "client-kill" => {
730 let (cid_str, message) = match args.split_once(char::is_whitespace) {
731 Some((c, m)) => (c, Some(m.trim().to_string())),
732 None => (args, None),
733 };
734 let cid = cid_str.parse::<u64>().map_err(|_| {
735 CommandParseError::Syntax(format!(
736 "client-kill requires a CID number, got: {cid_str}"
737 ))
738 })?;
739 Ok(Self::ClientKill { cid, message })
740 }
741
742 "remote" => match args.split_whitespace().collect::<Vec<_>>().as_slice() {
744 ["accept" | "ACCEPT"] => Ok(Self::Remote(RemoteAction::Accept)),
745 ["skip" | "SKIP"] => Ok(Self::Remote(RemoteAction::Skip)),
746 ["mod" | "MOD", host, port] => Ok(Self::Remote(RemoteAction::Modify {
747 host: host.to_string(),
748 port: port
749 .parse()
750 .map_err(|_| CommandParseError::Syntax("port must be a number".into()))?,
751 })),
752 _ => cmd_err("usage: remote accept|skip|mod <host> <port>"),
753 },
754
755 "proxy" => match args.split_whitespace().collect::<Vec<_>>().as_slice() {
756 ["none" | "NONE"] => Ok(Self::Proxy(ProxyAction::None)),
757 ["http" | "HTTP", host, port] => Ok(Self::Proxy(ProxyAction::Http {
758 host: host.to_string(),
759 port: port
760 .parse()
761 .map_err(|_| CommandParseError::Syntax("port must be a number".into()))?,
762 non_cleartext_only: false,
763 })),
764 ["http" | "HTTP", host, port, "nct"] => Ok(Self::Proxy(ProxyAction::Http {
765 host: host.to_string(),
766 port: port
767 .parse()
768 .map_err(|_| CommandParseError::Syntax("port must be a number".into()))?,
769 non_cleartext_only: true,
770 })),
771 ["socks" | "SOCKS", host, port] => Ok(Self::Proxy(ProxyAction::Socks {
772 host: host.to_string(),
773 port: port
774 .parse()
775 .map_err(|_| CommandParseError::Syntax("port must be a number".into()))?,
776 })),
777 _ => cmd_err("usage: proxy none|http <host> <port> [nct]|socks <host> <port>"),
778 },
779
780 "env-filter" => {
782 let level = if args.is_empty() {
783 0
784 } else {
785 args.parse::<u32>().map_err(|_| {
786 CommandParseError::Syntax(format!("invalid env-filter level: {args}"))
787 })?
788 };
789 Ok(Self::EnvFilter(level))
790 }
791
792 "remote-entry-count" => Ok(Self::RemoteEntryCount),
794
795 "remote-entry-get" => {
796 if args.is_empty() {
797 return cmd_err("usage: remote-entry-get i|all [j]");
798 }
799 let range = if args == "all" {
800 RemoteEntryRange::All
801 } else {
802 let mut parts = args.splitn(2, char::is_whitespace);
803 let from = parts.next().unwrap().parse::<u32>().map_err(|_| {
804 CommandParseError::Syntax(format!(
805 "remote-entry-get index must be a number or 'all', got: {args}"
806 ))
807 })?;
808 match parts.next() {
809 Some(to_str) => {
810 let to = to_str.trim().parse::<u32>().map_err(|_| {
811 CommandParseError::Syntax(format!(
812 "remote-entry-get end index must be a number, got: {to_str}"
813 ))
814 })?;
815 RemoteEntryRange::Range { from, to }
816 }
817 None => RemoteEntryRange::Single(from),
818 }
819 };
820 Ok(Self::RemoteEntryGet(range))
821 }
822
823 "push-update-broad" => {
825 if args.is_empty() {
826 return cmd_err("usage: push-update-broad <options>");
827 }
828 Ok(Self::PushUpdateBroad {
829 options: args.to_string(),
830 })
831 }
832
833 "push-update-cid" => {
834 let (cid_str, options) =
835 args.split_once(char::is_whitespace)
836 .ok_or(CommandParseError::Syntax(
837 "usage: push-update-cid <cid> <options>".into(),
838 ))?;
839 let cid = cid_str.parse::<u64>().map_err(|_| {
840 CommandParseError::Syntax("push-update-cid: cid must be a number".into())
841 })?;
842 Ok(Self::PushUpdateCid {
843 cid,
844 options: options.trim().to_string(),
845 })
846 }
847
848 "raw-ml" => {
850 if args.is_empty() {
851 return cmd_err("usage: raw-ml <command>");
852 }
853 Ok(Self::RawMultiLine(args.to_string()))
854 }
855
856 "exit" => Ok(Self::Exit),
858 "quit" => Ok(Self::Quit),
859
860 _ => Ok(Self::Raw(line.to_string())),
862 }
863 }
864}
865
866pub fn connection_sequence(bytecount_interval: u32) -> Vec<OvpnCommand> {
908 let mut cmds = vec![
909 OvpnCommand::Log(StreamMode::OnAll),
910 OvpnCommand::StateStream(StreamMode::OnAll),
911 OvpnCommand::Pid,
912 ];
913 if bytecount_interval > 0 {
914 cmds.push(OvpnCommand::ByteCount(bytecount_interval));
915 }
916 cmds.push(OvpnCommand::HoldRelease);
917 cmds
918}
919
920#[cfg(test)]
921mod tests {
922 use super::*;
923
924 #[test]
925 fn into_static_str_labels() {
926 let label: &str = (&OvpnCommand::State).into();
927 assert_eq!(label, "state");
928
929 let label: &str = (&OvpnCommand::ForgetPasswords).into();
930 assert_eq!(label, "forget-passwords");
931
932 let label: &str = (&OvpnCommand::ByteCount(5)).into();
933 assert_eq!(label, "byte-count");
934 }
935
936 #[test]
939 fn connection_sequence_with_bytecount() {
940 let cmds = connection_sequence(5);
941 assert_eq!(
942 cmds,
943 vec![
944 OvpnCommand::Log(StreamMode::OnAll),
945 OvpnCommand::StateStream(StreamMode::OnAll),
946 OvpnCommand::Pid,
947 OvpnCommand::ByteCount(5),
948 OvpnCommand::HoldRelease,
949 ]
950 );
951 }
952
953 #[test]
954 fn connection_sequence_without_bytecount() {
955 let cmds = connection_sequence(0);
956 assert_eq!(
957 cmds,
958 vec![
959 OvpnCommand::Log(StreamMode::OnAll),
960 OvpnCommand::StateStream(StreamMode::OnAll),
961 OvpnCommand::Pid,
962 OvpnCommand::HoldRelease,
963 ]
964 );
965 }
966
967 #[test]
970 fn parse_simple_commands() {
971 assert_eq!("version".parse(), Ok(OvpnCommand::Version));
972 assert_eq!("pid".parse(), Ok(OvpnCommand::Pid));
973 assert_eq!("help".parse(), Ok(OvpnCommand::Help));
974 assert_eq!("net".parse(), Ok(OvpnCommand::Net));
975 assert_eq!("load-stats".parse(), Ok(OvpnCommand::LoadStats));
976 assert_eq!("forget-passwords".parse(), Ok(OvpnCommand::ForgetPasswords));
977 assert_eq!("pkcs11-id-count".parse(), Ok(OvpnCommand::Pkcs11IdCount));
978 assert_eq!("exit".parse(), Ok(OvpnCommand::Exit));
979 assert_eq!("quit".parse(), Ok(OvpnCommand::Quit));
980 }
981
982 #[test]
983 fn parse_status() {
984 assert_eq!("status".parse(), Ok(OvpnCommand::Status(StatusFormat::V1)));
985 assert_eq!(
986 "status 1".parse(),
987 Ok(OvpnCommand::Status(StatusFormat::V1))
988 );
989 assert_eq!(
990 "status 2".parse(),
991 Ok(OvpnCommand::Status(StatusFormat::V2))
992 );
993 assert_eq!(
994 "status 3".parse(),
995 Ok(OvpnCommand::Status(StatusFormat::V3))
996 );
997 assert!("status 4".parse::<OvpnCommand>().is_err());
998 }
999
1000 #[test]
1003 fn parse_state_bare() {
1004 assert_eq!("state".parse(), Ok(OvpnCommand::State));
1005 }
1006
1007 #[test]
1008 fn parse_state_stream_modes() {
1009 assert_eq!(
1010 "state on".parse(),
1011 Ok(OvpnCommand::StateStream(StreamMode::On))
1012 );
1013 assert_eq!(
1014 "state off".parse(),
1015 Ok(OvpnCommand::StateStream(StreamMode::Off))
1016 );
1017 assert_eq!(
1018 "state all".parse(),
1019 Ok(OvpnCommand::StateStream(StreamMode::All))
1020 );
1021 assert_eq!(
1022 "state on all".parse(),
1023 Ok(OvpnCommand::StateStream(StreamMode::OnAll))
1024 );
1025 assert_eq!(
1026 "state 5".parse(),
1027 Ok(OvpnCommand::StateStream(StreamMode::Recent(5)))
1028 );
1029 }
1030
1031 #[test]
1032 fn parse_log_and_echo() {
1033 assert_eq!("log on".parse(), Ok(OvpnCommand::Log(StreamMode::On)));
1034 assert_eq!(
1035 "log on all".parse(),
1036 Ok(OvpnCommand::Log(StreamMode::OnAll))
1037 );
1038 assert_eq!("echo off".parse(), Ok(OvpnCommand::Echo(StreamMode::Off)));
1039 assert_eq!(
1040 "echo 10".parse(),
1041 Ok(OvpnCommand::Echo(StreamMode::Recent(10)))
1042 );
1043 }
1044
1045 #[test]
1048 fn parse_verb() {
1049 assert_eq!("verb".parse(), Ok(OvpnCommand::Verb(None)));
1050 assert_eq!("verb 4".parse(), Ok(OvpnCommand::Verb(Some(4))));
1051 assert!("verb abc".parse::<OvpnCommand>().is_err());
1052 }
1053
1054 #[test]
1055 fn parse_mute() {
1056 assert_eq!("mute".parse(), Ok(OvpnCommand::Mute(None)));
1057 assert_eq!("mute 40".parse(), Ok(OvpnCommand::Mute(Some(40))));
1058 assert!("mute abc".parse::<OvpnCommand>().is_err());
1059 }
1060
1061 #[test]
1062 fn parse_bytecount() {
1063 assert_eq!("bytecount 5".parse(), Ok(OvpnCommand::ByteCount(5)));
1064 assert_eq!("bytecount 0".parse(), Ok(OvpnCommand::ByteCount(0)));
1065 assert!("bytecount".parse::<OvpnCommand>().is_err());
1066 }
1067
1068 #[test]
1071 fn parse_signal() {
1072 assert_eq!(
1073 "signal SIGHUP".parse(),
1074 Ok(OvpnCommand::Signal(Signal::SigHup))
1075 );
1076 assert_eq!(
1077 "signal SIGTERM".parse(),
1078 Ok(OvpnCommand::Signal(Signal::SigTerm))
1079 );
1080 assert_eq!(
1081 "signal SIGUSR1".parse(),
1082 Ok(OvpnCommand::Signal(Signal::SigUsr1))
1083 );
1084 assert_eq!(
1085 "signal SIGUSR2".parse(),
1086 Ok(OvpnCommand::Signal(Signal::SigUsr2))
1087 );
1088 assert!("signal SIGKILL".parse::<OvpnCommand>().is_err());
1089 }
1090
1091 #[test]
1094 fn parse_kill_common_name() {
1095 assert_eq!(
1096 "kill TestClient".parse(),
1097 Ok(OvpnCommand::Kill(KillTarget::CommonName(
1098 "TestClient".to_string()
1099 )))
1100 );
1101 }
1102
1103 #[test]
1104 fn parse_kill_address() {
1105 assert_eq!(
1106 "kill tcp:1.2.3.4:4000".parse(),
1107 Ok(OvpnCommand::Kill(KillTarget::Address {
1108 protocol: TransportProtocol::Tcp,
1109 ip: "1.2.3.4".to_string(),
1110 port: 4000,
1111 }))
1112 );
1113 }
1114
1115 #[test]
1116 fn parse_kill_empty_is_err() {
1117 assert!("kill".parse::<OvpnCommand>().is_err());
1118 }
1119
1120 #[test]
1123 fn parse_hold() {
1124 assert_eq!("hold".parse(), Ok(OvpnCommand::HoldQuery));
1125 assert_eq!("hold on".parse(), Ok(OvpnCommand::HoldOn));
1126 assert_eq!("hold off".parse(), Ok(OvpnCommand::HoldOff));
1127 assert_eq!("hold release".parse(), Ok(OvpnCommand::HoldRelease));
1128 assert!("hold bogus".parse::<OvpnCommand>().is_err());
1129 }
1130
1131 #[test]
1134 fn parse_username() {
1135 let cmd: OvpnCommand = "username Auth alice".parse().unwrap();
1136 assert_eq!(
1137 cmd,
1138 OvpnCommand::Username {
1139 auth_type: AuthType::Auth,
1140 value: "alice".into(),
1141 }
1142 );
1143 }
1144
1145 #[test]
1146 fn parse_password() {
1147 let cmd: OvpnCommand = "password PrivateKey s3cret".parse().unwrap();
1148 assert_eq!(
1149 cmd,
1150 OvpnCommand::Password {
1151 auth_type: AuthType::PrivateKey,
1152 value: "s3cret".into(),
1153 }
1154 );
1155 }
1156
1157 #[test]
1158 fn parse_username_missing_value_is_err() {
1159 assert!("username".parse::<OvpnCommand>().is_err());
1160 assert!("username Auth".parse::<OvpnCommand>().is_err());
1161 }
1162
1163 #[test]
1164 fn parse_auth_retry() {
1165 assert_eq!(
1166 "auth-retry none".parse(),
1167 Ok(OvpnCommand::AuthRetry(AuthRetryMode::None))
1168 );
1169 assert_eq!(
1170 "auth-retry interact".parse(),
1171 Ok(OvpnCommand::AuthRetry(AuthRetryMode::Interact))
1172 );
1173 assert_eq!(
1174 "auth-retry nointeract".parse(),
1175 Ok(OvpnCommand::AuthRetry(AuthRetryMode::NoInteract))
1176 );
1177 assert!("auth-retry bogus".parse::<OvpnCommand>().is_err());
1178 }
1179
1180 #[test]
1183 fn parse_needok() {
1184 assert_eq!(
1185 "needok token-insertion ok".parse(),
1186 Ok(OvpnCommand::NeedOk {
1187 name: "token-insertion".to_string(),
1188 response: NeedOkResponse::Ok,
1189 })
1190 );
1191 assert_eq!(
1192 "needok token-insertion cancel".parse(),
1193 Ok(OvpnCommand::NeedOk {
1194 name: "token-insertion".to_string(),
1195 response: NeedOkResponse::Cancel,
1196 })
1197 );
1198 assert!("needok".parse::<OvpnCommand>().is_err());
1199 assert!("needok name bogus".parse::<OvpnCommand>().is_err());
1200 }
1201
1202 #[test]
1203 fn parse_needstr() {
1204 assert_eq!(
1205 "needstr prompt-name John".parse(),
1206 Ok(OvpnCommand::NeedStr {
1207 name: "prompt-name".to_string(),
1208 value: "John".to_string(),
1209 })
1210 );
1211 assert!("needstr".parse::<OvpnCommand>().is_err());
1212 }
1213
1214 #[test]
1217 fn parse_pkcs11_id_get() {
1218 assert_eq!("pkcs11-id-get 1".parse(), Ok(OvpnCommand::Pkcs11IdGet(1)));
1219 assert!("pkcs11-id-get abc".parse::<OvpnCommand>().is_err());
1220 }
1221
1222 #[test]
1225 fn parse_client_auth() {
1226 assert_eq!(
1227 "client-auth 42 7".parse(),
1228 Ok(OvpnCommand::ClientAuth {
1229 cid: 42,
1230 kid: 7,
1231 config_lines: vec![],
1232 })
1233 );
1234 }
1235
1236 #[test]
1237 fn parse_client_auth_with_config() {
1238 let cmd: OvpnCommand = "client-auth 1 2 push route 10.0.0.0,ifconfig-push 10.0.1.1"
1239 .parse()
1240 .unwrap();
1241 assert_eq!(
1242 cmd,
1243 OvpnCommand::ClientAuth {
1244 cid: 1,
1245 kid: 2,
1246 config_lines: vec![
1247 "push route 10.0.0.0".to_string(),
1248 "ifconfig-push 10.0.1.1".to_string(),
1249 ],
1250 }
1251 );
1252 }
1253
1254 #[test]
1255 fn parse_client_auth_nt() {
1256 assert_eq!(
1257 "client-auth-nt 5 3".parse(),
1258 Ok(OvpnCommand::ClientAuthNt { cid: 5, kid: 3 })
1259 );
1260 assert!("client-auth-nt abc 3".parse::<OvpnCommand>().is_err());
1261 }
1262
1263 #[test]
1264 fn parse_client_deny() {
1265 assert_eq!(
1266 "client-deny 1 2 rejected".parse(),
1267 Ok(OvpnCommand::ClientDeny {
1268 cid: 1,
1269 kid: 2,
1270 reason: "rejected".to_string(),
1271 client_reason: None,
1272 })
1273 );
1274 assert_eq!(
1275 "client-deny 1 2 rejected sorry".parse(),
1276 Ok(OvpnCommand::ClientDeny {
1277 cid: 1,
1278 kid: 2,
1279 reason: "rejected".to_string(),
1280 client_reason: Some("sorry".to_string()),
1281 })
1282 );
1283 }
1284
1285 #[test]
1286 fn parse_client_kill() {
1287 assert_eq!(
1288 "client-kill 99".parse(),
1289 Ok(OvpnCommand::ClientKill {
1290 cid: 99,
1291 message: None,
1292 })
1293 );
1294 assert_eq!(
1295 "client-kill 99 HALT".parse(),
1296 Ok(OvpnCommand::ClientKill {
1297 cid: 99,
1298 message: Some("HALT".to_string()),
1299 })
1300 );
1301 assert!("client-kill abc".parse::<OvpnCommand>().is_err());
1302 }
1303
1304 #[test]
1307 fn parse_remote() {
1308 assert_eq!(
1309 "remote accept".parse(),
1310 Ok(OvpnCommand::Remote(RemoteAction::Accept))
1311 );
1312 assert_eq!(
1313 "remote SKIP".parse(),
1314 Ok(OvpnCommand::Remote(RemoteAction::Skip))
1315 );
1316 assert_eq!(
1317 "remote MOD example.com 443".parse(),
1318 Ok(OvpnCommand::Remote(RemoteAction::Modify {
1319 host: "example.com".to_string(),
1320 port: 443,
1321 }))
1322 );
1323 assert!("remote".parse::<OvpnCommand>().is_err());
1324 }
1325
1326 #[test]
1327 fn parse_proxy() {
1328 assert_eq!(
1329 "proxy none".parse(),
1330 Ok(OvpnCommand::Proxy(ProxyAction::None))
1331 );
1332 assert_eq!(
1333 "proxy HTTP proxy.local 8080".parse(),
1334 Ok(OvpnCommand::Proxy(ProxyAction::Http {
1335 host: "proxy.local".to_string(),
1336 port: 8080,
1337 non_cleartext_only: false,
1338 }))
1339 );
1340 assert_eq!(
1341 "proxy http proxy.local 8080 nct".parse(),
1342 Ok(OvpnCommand::Proxy(ProxyAction::Http {
1343 host: "proxy.local".to_string(),
1344 port: 8080,
1345 non_cleartext_only: true,
1346 }))
1347 );
1348 assert_eq!(
1349 "proxy socks socks.local 1080".parse(),
1350 Ok(OvpnCommand::Proxy(ProxyAction::Socks {
1351 host: "socks.local".to_string(),
1352 port: 1080,
1353 }))
1354 );
1355 assert!("proxy".parse::<OvpnCommand>().is_err());
1356 }
1357
1358 #[test]
1361 fn parse_raw_ml() {
1362 assert_eq!(
1363 "raw-ml some-cmd".parse(),
1364 Ok(OvpnCommand::RawMultiLine("some-cmd".to_string()))
1365 );
1366 assert!("raw-ml".parse::<OvpnCommand>().is_err());
1367 }
1368
1369 #[test]
1370 fn parse_unrecognized_falls_through_to_raw() {
1371 assert_eq!(
1372 "unknown-cmd foo bar".parse(),
1373 Ok(OvpnCommand::Raw("unknown-cmd foo bar".to_string()))
1374 );
1375 }
1376
1377 #[test]
1378 fn parse_trims_whitespace() {
1379 assert_eq!(" version ".parse(), Ok(OvpnCommand::Version));
1380 assert_eq!(
1381 " state on ".parse(),
1382 Ok(OvpnCommand::StateStream(StreamMode::On))
1383 );
1384 }
1385
1386 #[test]
1389 fn parse_state_invalid_stream_mode() {
1390 assert!("state bogus".parse::<OvpnCommand>().is_err());
1391 }
1392
1393 #[test]
1394 fn parse_log_invalid_stream_mode() {
1395 assert!("log bogus".parse::<OvpnCommand>().is_err());
1396 }
1397
1398 #[test]
1399 fn parse_echo_invalid_stream_mode() {
1400 assert!("echo bogus".parse::<OvpnCommand>().is_err());
1401 }
1402
1403 #[test]
1404 fn parse_kill_unknown_protocol_falls_back() {
1405 let cmd: OvpnCommand = "kill sctp:1.2.3.4:4000".parse().unwrap();
1406 assert_eq!(
1407 cmd,
1408 OvpnCommand::Kill(KillTarget::Address {
1409 protocol: TransportProtocol::Unknown("sctp".to_string()),
1410 ip: "1.2.3.4".to_string(),
1411 port: 4000,
1412 })
1413 );
1414 }
1415
1416 #[test]
1417 fn parse_username_unknown_auth_type_falls_back() {
1418 let cmd: OvpnCommand = "username MyPlugin alice".parse().unwrap();
1419 assert_eq!(
1420 cmd,
1421 OvpnCommand::Username {
1422 auth_type: AuthType::Unknown("MyPlugin".to_string()),
1423 value: "alice".into(),
1424 }
1425 );
1426 }
1427
1428 #[test]
1429 fn parse_password_unknown_auth_type_falls_back() {
1430 let cmd: OvpnCommand = "password MyPlugin s3cret".parse().unwrap();
1431 assert_eq!(
1432 cmd,
1433 OvpnCommand::Password {
1434 auth_type: AuthType::Unknown("MyPlugin".to_string()),
1435 value: "s3cret".into(),
1436 }
1437 );
1438 }
1439
1440 #[test]
1441 fn parse_password_missing_value_is_err() {
1442 assert!("password".parse::<OvpnCommand>().is_err());
1443 assert!("password Auth".parse::<OvpnCommand>().is_err());
1444 }
1445
1446 #[test]
1447 fn parse_client_auth_non_numeric_cid() {
1448 assert!("client-auth abc 1".parse::<OvpnCommand>().is_err());
1449 }
1450
1451 #[test]
1452 fn parse_client_auth_non_numeric_kid() {
1453 assert!("client-auth 1 abc".parse::<OvpnCommand>().is_err());
1454 }
1455
1456 #[test]
1457 fn parse_client_auth_nt_non_numeric_kid() {
1458 assert!("client-auth-nt 1 abc".parse::<OvpnCommand>().is_err());
1459 }
1460
1461 #[test]
1462 fn parse_client_deny_missing_args() {
1463 assert!("client-deny".parse::<OvpnCommand>().is_err());
1464 assert!("client-deny 1".parse::<OvpnCommand>().is_err());
1465 assert!("client-deny 1 2".parse::<OvpnCommand>().is_err());
1466 }
1467
1468 #[test]
1469 fn parse_client_deny_non_numeric_ids() {
1470 assert!("client-deny abc 1 reason".parse::<OvpnCommand>().is_err());
1471 assert!("client-deny 1 abc reason".parse::<OvpnCommand>().is_err());
1472 }
1473
1474 #[test]
1475 fn parse_remote_non_numeric_port() {
1476 assert!("remote mod host abc".parse::<OvpnCommand>().is_err());
1477 }
1478
1479 #[test]
1480 fn parse_proxy_non_numeric_port() {
1481 assert!("proxy http host abc".parse::<OvpnCommand>().is_err());
1482 assert!("proxy http host abc nct".parse::<OvpnCommand>().is_err());
1483 assert!("proxy socks host abc".parse::<OvpnCommand>().is_err());
1484 }
1485
1486 #[test]
1487 fn parse_pkcs11_id_get_missing_arg() {
1488 assert!("pkcs11-id-get".parse::<OvpnCommand>().is_err());
1489 }
1490
1491 #[test]
1492 fn parse_bytecount_non_numeric() {
1493 assert!("bytecount abc".parse::<OvpnCommand>().is_err());
1494 }
1495
1496 #[test]
1497 fn parse_needstr_missing_value() {
1498 assert!("needstr".parse::<OvpnCommand>().is_err());
1499 }
1500
1501 #[test]
1504 fn parse_env_filter() {
1505 assert_eq!("env-filter 2".parse(), Ok(OvpnCommand::EnvFilter(2)));
1506 assert_eq!("env-filter 0".parse(), Ok(OvpnCommand::EnvFilter(0)));
1507 assert_eq!("env-filter".parse(), Ok(OvpnCommand::EnvFilter(0)));
1508 assert!("env-filter abc".parse::<OvpnCommand>().is_err());
1509 }
1510
1511 #[test]
1512 fn parse_remote_entry_count() {
1513 assert_eq!(
1514 "remote-entry-count".parse(),
1515 Ok(OvpnCommand::RemoteEntryCount)
1516 );
1517 }
1518
1519 #[test]
1520 fn parse_remote_entry_get() {
1521 assert_eq!(
1522 "remote-entry-get 0".parse(),
1523 Ok(OvpnCommand::RemoteEntryGet(RemoteEntryRange::Single(0)))
1524 );
1525 assert_eq!(
1526 "remote-entry-get 0 3".parse(),
1527 Ok(OvpnCommand::RemoteEntryGet(RemoteEntryRange::Range {
1528 from: 0,
1529 to: 3
1530 }))
1531 );
1532 assert_eq!(
1533 "remote-entry-get all".parse(),
1534 Ok(OvpnCommand::RemoteEntryGet(RemoteEntryRange::All))
1535 );
1536 assert!("remote-entry-get".parse::<OvpnCommand>().is_err());
1537 assert!("remote-entry-get abc".parse::<OvpnCommand>().is_err());
1538 assert!("remote-entry-get 0 abc".parse::<OvpnCommand>().is_err());
1539 }
1540
1541 #[test]
1542 fn parse_push_update_broad() {
1543 let cmd: OvpnCommand = "push-update-broad route 10.0.0.0".parse().unwrap();
1544 assert_eq!(
1545 cmd,
1546 OvpnCommand::PushUpdateBroad {
1547 options: "route 10.0.0.0".to_string()
1548 }
1549 );
1550 assert!("push-update-broad".parse::<OvpnCommand>().is_err());
1551 }
1552
1553 #[test]
1554 fn parse_push_update_cid() {
1555 let cmd: OvpnCommand = "push-update-cid 42 route 10.0.0.0".parse().unwrap();
1556 assert_eq!(
1557 cmd,
1558 OvpnCommand::PushUpdateCid {
1559 cid: 42,
1560 options: "route 10.0.0.0".to_string()
1561 }
1562 );
1563 assert!("push-update-cid".parse::<OvpnCommand>().is_err());
1564 assert!("push-update-cid abc opts".parse::<OvpnCommand>().is_err());
1565 }
1566}