diff options
author | Simon Garrelou <simon@sixfoisneuf.fr> | 2023-04-16 11:46:53 +0200 |
---|---|---|
committer | Simon Garrelou <simon@sixfoisneuf.fr> | 2023-04-16 11:46:53 +0200 |
commit | 5871891af447e0a49108309c60af64fe9a6f59bc (patch) | |
tree | c0d11c6ee3c4173deeb290391cdfff8c4bdd0fc2 | |
download | wgmgr-5871891af447e0a49108309c60af64fe9a6f59bc.tar.gz wgmgr-5871891af447e0a49108309c60af64fe9a6f59bc.zip |
First commit: config file parsing
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Cargo.lock | 7 | ||||
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | src/lib.rs | 179 | ||||
-rw-r--r-- | src/main.rs | 3 |
5 files changed, 198 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore | |||
@@ -0,0 +1 @@ | |||
/target | |||
diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..7ee9b3d --- /dev/null +++ b/Cargo.lock | |||
@@ -0,0 +1,7 @@ | |||
1 | # This file is automatically @generated by Cargo. | ||
2 | # It is not intended for manual editing. | ||
3 | version = 3 | ||
4 | |||
5 | [[package]] | ||
6 | name = "wgmgr" | ||
7 | version = "0.1.0" | ||
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..433e4a6 --- /dev/null +++ b/Cargo.toml | |||
@@ -0,0 +1,8 @@ | |||
1 | [package] | ||
2 | name = "wgmgr" | ||
3 | version = "0.1.0" | ||
4 | edition = "2021" | ||
5 | |||
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
7 | |||
8 | [dependencies] | ||
diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..c9bce25 --- /dev/null +++ b/src/lib.rs | |||
@@ -0,0 +1,179 @@ | |||
1 | use std::env::join_paths; | ||
2 | use std::fs::read_to_string; | ||
3 | use std::{net::Ipv4Addr, fs, error::Error}; | ||
4 | use std::fmt::{Formatter, Display}; | ||
5 | |||
6 | #[derive(Debug)] | ||
7 | struct WgMgrError { | ||
8 | msg: String | ||
9 | } | ||
10 | |||
11 | impl WgMgrError { | ||
12 | fn new(msg: String) -> WgMgrError { | ||
13 | WgMgrError { | ||
14 | msg | ||
15 | } | ||
16 | } | ||
17 | } | ||
18 | |||
19 | impl Error for WgMgrError { | ||
20 | fn description(&self) -> &str { | ||
21 | return self.msg.as_str() | ||
22 | } | ||
23 | |||
24 | fn cause(&self) -> Option<&dyn Error> { | ||
25 | None | ||
26 | } | ||
27 | } | ||
28 | |||
29 | impl Display for WgMgrError { | ||
30 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { | ||
31 | write!(f, "{}", self.msg) | ||
32 | } | ||
33 | } | ||
34 | |||
35 | #[derive(Debug)] | ||
36 | pub struct WireguardConfig { | ||
37 | private_key: String, | ||
38 | listen_port: u32, | ||
39 | address: Ipv4Addr, | ||
40 | net_range: u32, | ||
41 | peers: Vec<Peer> | ||
42 | } | ||
43 | |||
44 | #[derive(Debug)] | ||
45 | struct Peer { | ||
46 | name: String, | ||
47 | public_key: String, | ||
48 | ip: Ipv4Addr | ||
49 | } | ||
50 | |||
51 | impl WireguardConfig { | ||
52 | pub fn new(config_path: &str) -> Result<WireguardConfig, Box<dyn Error>> { | ||
53 | let f = fs::read_to_string(config_path)?; | ||
54 | |||
55 | let mut conf = WireguardConfig{ | ||
56 | private_key: String::new(), | ||
57 | listen_port: 0, | ||
58 | address: Ipv4Addr::UNSPECIFIED, | ||
59 | net_range: 0, | ||
60 | peers: vec![] | ||
61 | }; | ||
62 | let mut current_peer = Peer { | ||
63 | ip: Ipv4Addr::UNSPECIFIED, | ||
64 | name: String::from("N/A"), | ||
65 | public_key: String::new() | ||
66 | }; | ||
67 | |||
68 | let mut in_interface = false; | ||
69 | let mut in_peer = false; | ||
70 | |||
71 | let mut prev_line: &str = "N/A"; | ||
72 | |||
73 | for line in f.lines() { | ||
74 | match line.trim() { | ||
75 | "[Interface]" => { | ||
76 | in_interface = true; | ||
77 | in_peer = false; | ||
78 | continue; | ||
79 | } | ||
80 | "[Peer]" => { | ||
81 | in_interface = false; | ||
82 | in_peer = true; | ||
83 | |||
84 | if current_peer.ip != Ipv4Addr::UNSPECIFIED { | ||
85 | conf.peers.push(current_peer); | ||
86 | } | ||
87 | |||
88 | current_peer = Peer { | ||
89 | ip: Ipv4Addr::UNSPECIFIED, | ||
90 | name: String::from(prev_line.trim_start_matches(&['#', ' '])), | ||
91 | public_key: String::new() | ||
92 | }; | ||
93 | continue; | ||
94 | } | ||
95 | "" => { | ||
96 | continue; | ||
97 | } | ||
98 | _ => {} | ||
99 | } | ||
100 | |||
101 | if in_interface { | ||
102 | if line.starts_with("PrivateKey") { | ||
103 | conf.private_key = String::from(config_value(line)?); | ||
104 | } | ||
105 | else if line.starts_with("ListenPort") { | ||
106 | conf.listen_port = config_value(line)?.parse()?; | ||
107 | } | ||
108 | else if line.starts_with("Address") { | ||
109 | let addr = config_value(line)?; | ||
110 | match addr.find("/") { | ||
111 | Some(idx) => { | ||
112 | conf.address = addr[..idx].parse()?; | ||
113 | conf.net_range = addr[idx+1..].parse()?; | ||
114 | } | ||
115 | None => { | ||
116 | return Err(Box::new(WgMgrError::new(String::from("'Interface' > 'Address' is missing a subnet")))); | ||
117 | } | ||
118 | } | ||
119 | } | ||
120 | } | ||
121 | |||
122 | else if in_peer { | ||
123 | if line.starts_with("AllowedIPs") { | ||
124 | let addr = config_value(line)?; | ||
125 | match addr.find("/") { | ||
126 | Some(idx) => { | ||
127 | current_peer.ip = addr[..idx].parse()?; | ||
128 | let net_range: i32 = addr[idx+1..].parse()?; | ||
129 | |||
130 | if net_range != 32 { | ||
131 | let msg = format!("Peer '{}' has invalid net range {}", current_peer.name, net_range); | ||
132 | return Err(Box::new(WgMgrError::new(msg))); | ||
133 | } | ||
134 | } | ||
135 | None => { | ||
136 | let msg = format!("Peer '{}' is missing net range", current_peer.name); | ||
137 | return Err(Box::new(WgMgrError::new(msg))); | ||
138 | } | ||
139 | } | ||
140 | } | ||
141 | else if line.starts_with("PublicKey") { | ||
142 | current_peer.public_key = String::from(config_value(line)?); | ||
143 | } | ||
144 | } | ||
145 | |||
146 | prev_line = line; | ||
147 | } | ||
148 | |||
149 | if current_peer.ip != Ipv4Addr::UNSPECIFIED { | ||
150 | conf.peers.push(current_peer); | ||
151 | } | ||
152 | |||
153 | Ok(conf) | ||
154 | } | ||
155 | } | ||
156 | |||
157 | impl Peer { | ||
158 | pub fn private_key(&self) -> Result<String, Box<dyn Error>> { | ||
159 | // TODO: do not hardcode where private keys are stored | ||
160 | |||
161 | let pk_folder = String::from("./private_keys/"); | ||
162 | let pk_path = join_paths(&[pk_folder, self.name.clone()])?; | ||
163 | |||
164 | let pk = read_to_string(pk_path)?; | ||
165 | |||
166 | Ok(pk) | ||
167 | } | ||
168 | } | ||
169 | |||
170 | fn config_value(line: &str) -> Result<&str, &str> { | ||
171 | match line.find("=") { | ||
172 | Some(i) => { | ||
173 | Ok(line[i+1..].trim()) | ||
174 | }, | ||
175 | None => { | ||
176 | Err("line does not seem to contain a key-value pair") | ||
177 | } | ||
178 | } | ||
179 | } \ No newline at end of file | ||
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..028e0e2 --- /dev/null +++ b/src/main.rs | |||
@@ -0,0 +1,3 @@ | |||
1 | fn main() { | ||
2 | println!("{:?}", wgmgr::WireguardConfig::new("wg.conf").unwrap()) | ||
3 | } | ||