#!/usr/bin/env bash # commands/block/helpers.sh # cmd::block::_impl # # function cmd::block::_impl() { local name="$1" identity="$2" type="$3" block_name="$4" reason="$5" local quiet="$6" force="$7" local -n _ips_ref="$8" local -n _subnets_ref="$9" local -n _ports_ref="${10}" local -n _services_ref="${11}" # --identity: block all peers for this identity if [[ -n "$identity" ]]; then cmd::block::_block_identity "$identity" "$quiet" \ "${_ips_ref[@]+"${_ips_ref[@]}"}" || return 1 return 0 fi [[ -z "$name" ]] && { log::error "Missing required flag: --name or --identity" return 1 } name=$(peers::resolve_and_require "$name" "$type") || return 1 local client_ip client_ip=$(peers::get_ip "$name") || return 1 # Full block if no specific targets if [[ ${#_ips_ref[@]} -eq 0 && ${#_ports_ref[@]} -eq 0 && \ ${#_subnets_ref[@]} -eq 0 && ${#_services_ref[@]} -eq 0 ]]; then if peers::is_blocked "$name"; then log::wg_warning "Client is already blocked: ${name}" return 0 fi monitor::update_endpoint_cache cmd::block::_block_all "$name" "$client_ip" "$quiet" cmd::block::_record_history "$name" "full" "manual" "$reason" return 0 fi # Specific rules if block::has_file "$name"; then local direct direct=$(block::is_blocked_direct "$name") if [[ "$direct" == "true" ]]; then log::wg_warning "${name} is fully blocked — unblock first to add specific rules" return 1 fi fi local changed=false for ip in "${_ips_ref[@]:-}"; do ip::require_valid "$ip" fw::block_ip "$client_ip" "$ip" block::add_rule "$name" "$client_ip" "ip" "${block_name:-}" "$ip" $quiet || log::wg_success "${ip} has been blocked for ${name}" done for subnet in "${_subnets_ref[@]:-}"; do ip::require_valid "$subnet" fw::block_subnet "$client_ip" "$subnet" block::add_rule "$name" "$client_ip" "subnet" "${block_name:-}" "$subnet" $quiet || log::wg_success "${subnet} has been blocked for ${name}" done for entry in "${_ports_ref[@]:-}"; do local b_target b_port b_proto IFS=":" read -r b_target b_port b_proto <<< "$entry" ip::require_valid "$b_target" fw::block_port "$client_ip" "$b_target" "$b_port" "${b_proto:-tcp}" block::add_rule "$name" "$client_ip" "port" "${block_name:-}" \ "$b_target" "$b_port" "${b_proto:-tcp}" $quiet || log::wg_success "${b_target}:${b_port}:${b_proto:-tcp} has been blocked for ${name}" done for svc in "${_services_ref[@]:-}"; do local resolved_lines=() mapfile -t resolved_lines < <(net::resolve "$svc" 2>/dev/null) if [[ ${#resolved_lines[@]} -eq 0 ]]; then log::error "Service not found or has no ports: ${svc}" return 1 fi local already_blocked=true for resolved in "${resolved_lines[@]}"; do if [[ "$resolved" == *:*:* ]]; then local b_ip b_port b_proto IFS=":" read -r b_ip b_port b_proto <<< "$resolved" fw::has_block_rule "$client_ip" "$b_ip" "$b_port" "$b_proto" 2>/dev/null || \ { already_blocked=false; break; } else fw::has_block_rule "$client_ip" "$resolved" 2>/dev/null || \ { already_blocked=false; break; } fi done if $already_blocked; then $quiet || log::wg_warning "${svc} is already blocked for ${name}" continue fi for resolved in "${resolved_lines[@]}"; do if [[ "$resolved" == *:*:* ]]; then local b_ip b_port b_proto IFS=":" read -r b_ip b_port b_proto <<< "$resolved" fw::block_port "$client_ip" "$b_ip" "$b_port" "$b_proto" block::add_rule "$name" "$client_ip" "port" "$svc" \ "$b_ip" "$b_port" "$b_proto" else fw::block_ip "$client_ip" "$resolved" block::add_rule "$name" "$client_ip" "ip" "$svc" "$resolved" fi done changed=true $quiet || log::wg_success "${svc} has been blocked for ${name}" done [[ ${#_ips_ref[@]} -gt 0 || ${#_ports_ref[@]} -gt 0 || \ ${#_subnets_ref[@]} -gt 0 ]] && changed=true if $changed; then local peer_rule peer_rule=$(peers::get_meta "$name" "rule") if [[ -n "$peer_rule" ]] && rule::exists "$peer_rule"; then fw::flush_peer "$client_ip" rule::apply "$peer_rule" "$client_ip" "$name" block::restore_rules_for "$name" "$client_ip" fi fi local btype="specific" [[ ${#_services_ref[@]} -gt 0 ]] && btype="${_services_ref[0]}" [[ ${#_ips_ref[@]} -gt 0 ]] && btype="ip" [[ ${#_subnets_ref[@]} -gt 0 ]] && btype="subnet" [[ ${#_ports_ref[@]} -gt 0 ]] && btype="port" cmd::block::_record_history "$name" "$btype" "manual" "$reason" return 0 } # ── Helpers ─────────────────────────────────────────────────────────────────── function cmd::block::_block_identity() { local identity_name="${1:-}" quiet="${2:-false}" shift 2 || true identity::require_exists "$identity_name" || return 1 identity::require_has_peers "$identity_name" || return 1 local peers blocked=0 failed=0 peers=$(identity::peers "$identity_name") while IFS= read -r peer_name; do [[ -z "$peer_name" ]] && continue if peers::is_blocked "$peer_name"; then $quiet || log::wg_warning "${peer_name} is already blocked" continue fi local client_ip client_ip=$(peers::get_ip "$peer_name") || continue monitor::update_endpoint_cache if cmd::block::_block_all "$peer_name" "$client_ip" true; then blocked=$(( blocked + 1 )) else failed=$(( failed + 1 )) fi done <<< "$peers" log::ok "Blocked ${blocked} peer(s) for identity '${identity_name}'" [[ $failed -gt 0 ]] && log::warn "${failed} peer(s) failed to block" return 0 } function cmd::block::_get_endpoint() { local name="$1" public_key="$2" local endpoint endpoint=$(monitor::endpoint_for_key "$public_key") if [[ -z "$endpoint" || "$endpoint" == "(none)" ]]; then endpoint=$(monitor::get_cached_endpoint "$name") fi echo "$endpoint" } function cmd::block::_block_all() { local name="${1:?name required}" local client_ip="${2:?client_ip required}" local quiet="${3:-false}" block::apply_full "$name" "$client_ip" block::set_direct "$name" "$client_ip" "true" $quiet || log::wg_success "${name} has been blocked." } function cmd::block::_record_history() { local name="${1:-}" block_type="${2:-full}" \ triggered_by="${3:-manual}" reason="${4:-}" local endpoint endpoint=$(json::peer_history_lookup "$name" 2>/dev/null || true) # endpoint_cache lookup local ep_cache ep_cache=$(json::endpoint_cache_get "$(ctx::endpoint_cache)" "$name" 2>/dev/null || true) json::block_history_record \ "$(ctx::block_history)" \ "$name" \ "$block_type" \ "$triggered_by" \ "$reason" \ "${ep_cache:-}" \ 2>/dev/null > /dev/null || true }