#  ██████  ██ ▄█▀▒███████▒     █     █░  ▄████ 
#▒██    ▒  ██▄█▒ ▒ ▒ ▒ ▄▀░    ▓█░ █ ░█░ ██▒ ▀█▒
#░ ▓██▄   ▓███▄░ ░ ▒ ▄▀▒░ ░█▒░ █ ▒█░ █░█ ▒ ██░▄▄▄░
#  ▒   ██▒▓██ █▄   ▄▀▒   ░    ░█░ █ ░█ ░▓█  ██▓
#▒██████▒▒▒██▒ █▄▒███████▒    ░░██▒██▓ ░▒▓███▀▒
#▒ ▒▓▒ ▒ ░▒ ▒▒ ▓▒░▒▒ ▓░▒░▒    ░ ▓░▒ ▒   ░▒   ▒ 
#░ ░▒  ░ https://git.sk4.nz/sk4nz/skz-wg ░   ░ 
#      ░  ░        ░ ░            ░          ░ 

.PHONY: clean deps info restart 
#.SILENT:

# ░▒▓█ CONFIGURATION █▓▒░

# Change this list to always create clients 
# CLIENTS=	client1 client2
CLIENTS?=
.poison empty (CLIENTS)

# Install packages, always keep this line before the next block (for curl)
INSTALLPKG!=	[ -f .installed ] || (pkg_add -I wireguard-tools libqrencode curl && touch .installed)

# Network configuration
SERVER?!=	curl -s ifconfig.co
WG_PORT= 	5353
WG_LAN=		10.10.10.1/24
WAN!=		route -n show -inet | grep default | awk '{print $$NF}'

# Packet Filter configuration for allowed ports
OUT_TCP?=	http ftp whois https ssh
IN_TCP?=	http https ssh
OUT_UDP?=	domain ntp $(WG_PORT)
IN_UDP?=	$(WG_PORT)
VPN_TCP?=	$(IN_TCP)
VPN_UDP?=	domain ntp https

# Server private key
WG_KEY!=	cat server.key 2> /dev/null || wg genkey
.poison empty $(WG_KEY)

# Ads lists
ADS_URLS=	https://winhelp2002.mvps.org/hosts.txt
ADS_URLS+=	https://pgl.yoyo.org/as/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext
ADS_URLS+=	https://adaway.org/hosts.txt
ADS_URLS+=	https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts

# ░▒▓█ TARGETS █▓▒░

all: /etc/pf.wg /var/unbound/etc/unbound.conf
	# Enable IPv4 forwarding permanently, if not already
	grep forwarding /etc/sysctl.conf 2> /dev/null || (printf 'net.inet.ip.forwarding=1\n' >> /etc/sysctl.conf && sysctl net.inet.ip.forwarding=1)
	# Write new pf.conf 
	grep 'pf.header' /etc/pf.conf || printf 'include "/etc/pf.header"\n' > /etc/pf.conf
	grep 'pf.wg' /etc/pf.conf || printf 'include "/etc/pf.wg"\n' >> /etc/pf.conf
	# Validate and start new pf.conf 
	pfctl -nf /etc/pf.conf && pfctl -f /etc/pf.conf 
	# Restart the WireGuard interface
	$(MAKE) restart 
	$(MAKE) info

restart:
	sh /etc/netstart wg0

/etc/pf.bogons:
	# Reserved IP addresses to drop, see https://en.wikipedia.org/wiki/Reserved_IP_addresses
	printf '0.0.0.0\
		\n10.0.0.0/8\
		\n100.64.0.0/10\
		\n127.0.0.0/8\
		\n128.0.0.0/16\
		\n169.254.0.0/16\
		\n172.16.0.0/12\
		\n191.255.0.0/16\
		\n192.0.0.0/24\
		\n192.0.2.0/24\
		\n192.88.99.0/24\
		\n192.168.0.0/16\
		\n198.18.0.0/15\
		\n198.51.100.0/24\
		\n203.0.113.0/24\
		\n223.255.255.0/24\
		\n224.0.0.0/4\
		\n240.0.0.0/4\
		\n255.255.255.255\n' > $@ 

/etc/pf.abuse_ssh /etc/pf.abuse_tcp:
	# Anti-DDoS persistent tables
	touch $@

/etc/pf.header:
	# The original pf header 
	printf 'set skip on lo\nblock return\n#pass\
		\nblock return in on ! lo0 proto tcp to port 6000:6010\
		\nblock return out log proto {tcp udp} user _pbuild\n' > $@

/etc/pf.wg: /etc/pf.bogons /etc/pf.abuse_ssh /etc/pf.abuse_tcp /etc/pf.header /etc/pf.conf.orig 
	# Define allowed PF ports 
	printf '# PF.WG\
		\n### Allowed Ports\
		\nout_tcp_ports = "{ $(OUT_TCP) }"\
		\nout_udp_ports = "{ $(OUT_UDP) }"\
		\nvpn_tcp_ports = "{ $(VPN_TCP) }"\
		\nvpn_udp_ports = "{ $(VPN_UDP) }"\
		\nin_tcp_ports = "{ $(IN_TCP) }"\
		\nin_udp_ports = "{ $(IN_UDP) }"\n' > $@
	# Rules
	printf '### Macros \
		\n# statefull tracking options - sto\
		\n# SSH is considered under abuse when there are more than 10 simultaneous connections or 1 connections per 2 second \
		\nssh_sto = "(max-src-conn 100, max-src-conn-rate 6/60, overload <t_abuse_ssh> flush global)"\
		\n# TCP is considered under abuse when there are more than 300 simultaneous connections or 600 per minute\
		\ntcp_sto = "(max-src-conn 300, max-src-conn-rate 600/60, overload <t_abuse_tcp> flush global)"\
		\n# TCP flags \
		\nflag_syn = "flags S/SA modulate state"\
		\n# define e (egress) and i (ingress) macros\
		\nedropin = "block drop in quick on egress from"\
		\nedropout = "block drop out quick on egress from"\
		\nepassout = "pass out on egress proto tcp to any port"\
		\ninblocktcp = "block in quick proto tcp from"\
		\ninpasstcp = "pass in on egress proto tcp to any port"\
		\ninvpntcp = "pass in on wg0 proto tcp to any port"\
		\ninvpnudp = "pass in on wg0 proto udp to any port"\
		\n# Persitent tables\
		\ntable <t_bogons> persist file "/etc/pf.bogons"\
		\ntable <t_abuse_tcp> persist file "/etc/pf.abuse_tcp"\
		\ntable <t_abuse_ssh> persist file "/etc/pf.abuse_ssh"\
		\n# Various security-related options\
		\nset block-policy return\
		\nset optimization conservative\
		\nset reassemble yes\
		\nset syncookies adaptive (start 25%%, end 12%%)\
		\nset ruleset-optimization basic\
		\nset skip on lo\
		\nantispoof for { lo, egress }\
		\nmatch in all scrub (max-mss 1440 no-df random-id reassemble tcp)\
		\n### Rules \
		\n# Prevent dns leaks \
		\n#block in log quick on egress inet proto { tcp udp } from any to ! egress port 53\
		\n# Block bogons - DEACTIVATED \
		\n# TODO : change pf.bogons to allow LAN\
		\n#$$edropin { <t_bogons> } to any\
		\n#$$edropout any to { <t_bogons> }\
		\n# block abusers\
		\n$$inblocktcp <t_abuse_ssh> to any port ssh\
		\n$$inblocktcp <t_abuse_tcp> to any port $$in_tcp_ports\
		\n# Default rule : block stateless traffic\
		\n# block\
		\n# Enable ICMP echo request, reply, unreach\
		\npass quick inet proto icmp all icmp-type { echoreq, echorep, unreach }\
		\n# Enable IPv4 traceroute\
		\npass out on egress proto udp to port 33433:33626\
		\n# Pass ingress \
		\n$$inpasstcp ssh $$flag_syn $$ssh_sto\
		\n$$inpasstcp http $$flag_syn $$tcp_sto\
		\n$$inpasstcp https $$flag_syn $$tcp_sto\
		\n$$invpntcp $$vpn_tcp_ports\
		\n$$invpnudp $$vpn_udp_ports\
		\npass in on egress proto udp to any port $$in_udp_ports\
		\n# Pass egress\
		\n$$epassout $$out_tcp_ports $$flag_syn\
		\npass out quick on egress proto udp to any port $$out_udp_ports allow-opts\
		\n# Pass Wireguard-LAN\
		\npass out on wg0 proto tcp to any port $$vpn_tcp_ports\
		\npass out on wg0 proto udp to any port $$vpn_udp_ports\
		\npass in on wg0 proto udp to any port $$out_udp_ports allow-opts\n' >> $@
	# NAT the WireGuard interface to WAN
	printf 'pass out on $(WAN) inet from wg0:network to any nat-to ($(WAN)) static-port\n' >> $@

ad-blacklist:
	rm -rf /var/unbound/ad-blacklist.conf
	$(MAKE) /var/unbound/ad-blacklist.conf

/var/unbound/ad-blacklist.conf:
	# For the lying DNS resolver
	for url in "$(ADS_URLS)"; do curl -s $$url >> /tmp/ads-daily; done
	awk -v white='/(api.solvemedia.com)/' '$$1 ~ /^127\.|^0\./ && $$2 !~white {gsub("\r",""); print tolower($$2)}' /tmp/ads-daily | sort | uniq | awk '{printf "server:\n", $$1; printf "local-data: \"%s A 127.0.0.1\"\n", $$1}' > $@
	rm -rf /tmp/ads-daily
	# Update lists daily
	grep 'ad-blacklist' /etc/daily.local || printf 'make -C $(PWD) ad-blacklist\nrcctl restart unbound\n' >> /etc/daily.local

/etc/pf.conf.orig:
	# Save original pf.conf 
	cp /etc/pf.conf $@

/etc/hostname.wg0: server.key $(CLIENTS)
	# The WireGuard interface configuration 
	printf 'wgkey "$(WG_KEY)"\
		\nwgport $(WG_PORT)\
		\ninet $(WG_LAN)\n' > $@
	# Add peers
	for cli in $(CLIENTS); do \
		N=$$(sha256 -qs $$cli | cut -b 1-2);\
		PUB=$$(cat $$cli/$$cli.pub);\
		PSK=$$(cat $$cli/$$cli.psk);\
		printf "# $$cli\nwgpeer \"$$PUB\" wgpsk \"$$PSK\" wgpka 25 wgaip $(WG_LAN:H:S/.1$/./)"$$(printf $$((0x$$N)))"/32\n" >> $@;\
		done
	printf 'up\n' >> $@
	chmod 640 $@
	# Start the wg interface
	sh /etc/netstart wg0

/var/unbound/etc/unbound.conf: /etc/hostname.wg0 ad-blacklist
	# Unbound configuration, with lying DNS and DNSSEC validation
	printf 'server:\
		\n\tinterface: 127.0.0.1\
		\n\tinterface: $(WG_LAN:H)\
		\n\tdo-ip6: no\
		\n\taccess-control: 0.0.0.0/0 refuse\
		\n\taccess-control: 127.0.0.0/8 allow\
		\n\taccess-control: $(WG_LAN) allow\
		\n\tauto-trust-anchor-file: "/var/unbound/db/root.key"\
		\n\taggressive-nsec: yes\
		\n\tinclude: /var/unbound/ad-blacklist.conf\
		\n\nremote-control:\
		\n\tcontrol-enable: yes\
		\n\tcontrol-interface: /var/run/unbound.sock\n' > $@
	rcctl enable unbound
	rcctl start unbound

info:
	printf '\033[1mWireguard: \033[32m$(SERVER):$(WG_PORT) \033[0m$(WG_LAN)\n'
	T=$$(wg show all transfer); for cli in $(CLIENTS); do\
		N=$$(sha256 -qs $$cli | cut -b 1-2);\
		PUB=$$(cat $$cli/$$cli.pub);\
		IN=$$(printf "$$T\n" | grep $$PUB | awk '{print $$(NF-1)}');\
		OUT=$$(printf "$$T\n" | grep $$PUB | awk '{print $$(NF)}');\
		printf '- \033[1m\033[33m%15s\033[0m $(WG_LAN:H:S/.1$/./)%-3s in: %-10s out: '$$OUT'\n' $$cli $$(printf $$((0x$$N))) $$IN;\
		done

server.conf: server.pub
	# Generate the server configuration header (deprecated)
	printf '[Interface] # VOID - Tunnel gateway: $(WG_LAN:H)\n#Address      = $(WG_LAN:H)\nListenPort   = $(WG_PORT)\nPrivateKey   = '$$(cat server.key)'\n' > server.conf;

server.key:
	# Generate server private key if not already present
	printf $(WG_KEY) > $@
	chmod 600 $@

server.pub: server.key
	# Generate server pub key if not already present
	cat server.key | wg pubkey > $@
	chmod 600 $@

$(CLIENTS): server.conf
	mkdir -p $@
	# Generate priv/pub keys
	wg genkey | tee $@/$@.key | wg pubkey > $@/$@.pub 
	# Generate PSK
	touch $@/$@.psk && chmod 600 $@/$@.psk
	wg genpsk > $@/$@.psk
	# Append to server.conf (deprecated) and the client configuration
	PSK=$$(cat $@/$@.psk); PUB=$$(cat $@/$@.pub); N=$$(sha256 -qs $@ | cut -b 1-2);\
		echo -e '[Peer] # '$@'\nAllowedIPs = $(WG_LAN:H:S/.1$/./)'$$(echo $$((0x$$N)))'/24\nPublicKey = '$$PUB'\nPresharedKey = '$$PSK'\n' >> server.conf;\
		echo -e '[Interface] # Local tunnel address: $(WG_LAN:H:S/.1$/./)'$$(echo $$((0x$$N))) router: $(WG_LAN:H)'\nAddress = $(WG_LAN:H:S/.1$/./)'$$(echo $$((0x$$N)))/24'\nPrivateKey = '$$(cat $@/$@.key)'\nDNS = $(WG_LAN:H)\n[Peer] # Tunnel endpoint: $(SERVER)\nEndpoint = $(SERVER):$(WG_PORT)\nPublicKey = '$$(cat server.pub)'\nPresharedKey = '$$PSK'\nAllowedIPs = 0.0.0.0/0' > $@/$@.conf
	cat $@/$@.conf | qrencode -t ansiutf8 -o $@/$@.qr
	chmod -R 600 $@

clean:
	-ifconfig wg0 down
	-rcctl stop unbound
	-rcctl disable unbound
	-rm -rf $(CLIENTS) *.key *.pub server.conf /var/unblound/ad-blacklist.sh /etc/pf.wg /etc/hostname.wg0 /var/unbound/etc/unbound.conf /etc/pf.header
	-sed -i '/ad-blacklist/d' /etc/daily.local
	-sed -i '/rcctl restart unbound/d' /etc/daily.local
	-cp /etc/pf.conf.orig /etc/pf.conf
	pfctl -nf /etc/pf.conf && pfctl -f /etc/pf.conf

