+ my $allow_iface = $rule_env_iface_lookup->{$rule_env};
+ die "unknown rule_env '$rule_env'\n" if !defined($allow_iface); # should not happen
+
+ my $errors = $rule->{errors} || {};
+
+ my $error_count = 0;
+
+ my $add_error = sub {
+ my ($param, $msg) = @_;
+ chomp $msg;
+ raise_param_exc({ $param => $msg }) if !$noerr;
+ $error_count++;
+ $errors->{$param} = $msg if !$errors->{$param};
+ };
+
+ my $check_ipset_or_alias_property = sub {
+ my ($name) = @_;
+
+ if (my $value = $rule->{$name}) {
+ if ($value =~ m/^\+/) {
+ if ($value =~ m/^\+(${security_group_name_pattern})$/) {
+ &$add_error($name, "no such ipset '$1'")
+ if !($cluster_conf->{ipset}->{$1} || ($fw_conf && $fw_conf->{ipset}->{$1}));
+
+ } else {
+ &$add_error($name, "invalid security group name '$value'");
+ }
+ } elsif ($value =~ m/^${ip_alias_pattern}$/){
+ my $alias = lc($value);
+ &$add_error($name, "no such alias '$value'")
+ if !($cluster_conf->{aliases}->{$alias} || ($fw_conf && $fw_conf->{aliases}->{$alias}))
+ }
+ }
+ };
+
+ my $type = $rule->{type};
+ my $action = $rule->{action};
+
+ &$add_error('type', "missing property") if !$type;
+ &$add_error('action', "missing property") if !$action;
+
+ if ($type) {
+ if ($type eq 'in' || $type eq 'out') {
+ &$add_error('action', "unknown action '$action'")
+ if $action && ($action !~ m/^(ACCEPT|DROP|REJECT)$/);
+ } elsif ($type eq 'group') {
+ &$add_error('type', "security groups not allowed")
+ if !$allow_groups;
+ &$add_error('action', "invalid characters in security group name")
+ if $action && ($action !~ m/^${security_group_name_pattern}$/);
+ } else {
+ &$add_error('type', "unknown rule type '$type'");
+ }
+ }
+
+ if ($rule->{iface}) {
+ &$add_error('type', "parameter -i not allowed for this rule type")
+ if !$allow_iface;
+ eval { PVE::JSONSchema::pve_verify_iface($rule->{iface}); };
+ &$add_error('iface', $@) if $@;
+ if ($rule_env eq 'vm') {
+ &$add_error('iface', "value does not match the regex pattern 'net\\d+'")
+ if $rule->{iface} !~ m/^net(\d+)$/;
+ } elsif ($rule_env eq 'ct') {
+ &$add_error('iface', "value does not match the regex pattern '(venet|eth\\d+)'")
+ if $rule->{iface} !~ m/^(venet|eth(\d+))$/;
+ }
+ }
+
+ if ($rule->{macro}) {
+ if (my $preferred_name = $pve_fw_preferred_macro_names->{lc($rule->{macro})}) {
+ $rule->{macro} = $preferred_name;
+ } else {
+ &$add_error('macro', "unknown macro '$rule->{macro}'");
+ }
+ }
+
+ if ($rule->{proto}) {
+ eval { pve_fw_verify_protocol_spec($rule->{proto}); };
+ &$add_error('proto', $@) if $@;
+ }
+
+ if ($rule->{dport}) {
+ eval { parse_port_name_number_or_range($rule->{dport}); };
+ &$add_error('dport', $@) if $@;
+ &$add_error('proto', "missing property - 'dport' requires this property")
+ if !$rule->{proto};
+ }
+
+ if ($rule->{sport}) {
+ eval { parse_port_name_number_or_range($rule->{sport}); };
+ &$add_error('sport', $@) if $@;
+ &$add_error('proto', "missing property - 'sport' requires this property")
+ if !$rule->{proto};
+ }
+
+ if ($rule->{source}) {
+ eval { parse_address_list($rule->{source}); };
+ &$add_error('source', $@) if $@;
+ &$check_ipset_or_alias_property('source');
+ }
+
+ if ($rule->{dest}) {
+ eval { parse_address_list($rule->{dest}); };
+ &$add_error('dest', $@) if $@;
+ &$check_ipset_or_alias_property('dest');
+ }
+
+ if ($rule->{macro} && !$error_count) {
+ eval { &$apply_macro($rule->{macro}, $rule, 1); };
+ if (my $err = $@) {
+ if (ref($err) eq "PVE::Exception" && $err->{errors}) {
+ my $eh = $err->{errors};
+ foreach my $p (keys %$eh) {
+ &$add_error($p, $eh->{$p});
+ }
+ } else {
+ &$add_error('macro', "$err");
+ }
+ }
+ }
+
+ $rule->{errors} = $errors if $error_count;
+
+ return $rule;