openmoat_proxy/decide/
ports.rs1use openmoat_core::{
12 AtomicAction, CompiledPolicy, Decision, Defaults, Kind, Policy, RuleGroup, Verdict,
13 port_pattern,
14};
15
16pub const RULE_PORT: &str = "proxy-port";
18
19pub const DEFAULT_PORTS: [u16; 2] = [443, 80];
21
22#[must_use]
29pub fn port_policy(policy: &Policy) -> Policy {
30 let lists = |groups: &[RuleGroup], ported_only: bool| -> Vec<RuleGroup> {
31 groups
32 .iter()
33 .map(|g| RuleGroup {
34 id: g.id.clone(),
35 reason: g.reason.clone(),
36 net: with_ports(g.patterns(Kind::Net), ported_only),
37 fetch: with_ports(g.patterns(Kind::Fetch), ported_only),
38 ..Default::default()
39 })
40 .collect()
41 };
42 Policy {
43 defaults: Defaults::All(Verdict::Defer),
44 deny: lists(&policy.deny, false),
45 repo_ask: lists(&policy.repo_ask, false),
46 allow: lists(&policy.allow, true),
47 ask: Vec::new(),
48 defer: Vec::new(),
49 ..policy.clone()
50 }
51}
52
53pub(super) fn with_ports(list: &[String], ported_only: bool) -> Vec<String> {
57 let kept = list
58 .iter()
59 .filter(|p| !ported_only || port_pattern(p.strip_prefix('!').unwrap_or(p)).is_some());
60 let excluded = list
61 .iter()
62 .filter_map(|p| p.strip_prefix('!'))
63 .filter(|host| !host.contains(':'))
64 .map(|host| format!("!{host}:*"));
65 kept.cloned().chain(excluded).collect()
66}
67
68fn ported(host: &str, port: u16) -> Option<String> {
70 (!host.contains(':')).then(|| format!("{host}:{port}"))
71}
72
73#[must_use]
84pub fn host_port(
85 policy: &CompiledPolicy<'_>,
86 ports: &CompiledPolicy<'_>,
87 host: &str,
88 port: u16,
89) -> Decision {
90 let decide = |subject: String| ports.evaluate_atomic(&AtomicAction::Fetch { host: subject });
91 let refused = |d: &Decision| matches!(d.verdict, Verdict::Deny | Verdict::Ask);
92 if super::metadata(host).is_some() || decide(host.to_owned()).is_some_and(|d| refused(&d)) {
93 return super::host(policy, host);
94 }
95 match ported(host, port).and_then(decide) {
96 Some(d) if d.verdict == Verdict::Allow => return d,
97 Some(mut d) if refused(&d) => {
98 if d.verdict == Verdict::Ask {
99 d.verdict = Verdict::Deny;
100 d.reasons.push(
101 "the proxy cannot ask, so a host and port it would ask for is refused"
102 .to_owned(),
103 );
104 }
105 return d;
106 }
107 _ => {}
108 }
109 let decision = super::host(policy, host);
110 if decision.verdict != Verdict::Allow || DEFAULT_PORTS.contains(&port) {
111 return decision;
112 }
113 let how = if host.contains(':') {
114 "an IPv6 address is reached on those ports only".to_owned()
115 } else {
116 format!("a `net` rule `{host}:{port}` allows this port")
117 };
118 Decision::single(
119 Verdict::Deny,
120 RULE_PORT,
121 format!("{host} is allowed on ports 443 and 80 only, not {port}; {how}"),
122 )
123}
124
125#[cfg(test)]
126mod tests;