# Declare varaibles
function declare_variables
{
# BEGIN OF CONFIGURATION ######################################################

  # Do only cleanup tasks
  $script:remove_only = $False

  # Apply additional settings
  $script:additions = $True

  # Create inbound firewall rules
  $script:firewall_in = $True

  # Create outbound firewall rules (only useful when blocking outbound traffic)
  $script:firewall_out = $True

  # Create IPsec configurations
  $script:ipsec = $True

  # Set servers's FQDN (to select the correct certificate for IPsec)
  $script:local_fqdn = "radius.instituce.cz"

  # Set server's IP address (IPv4 or IPv6)
  $script:local_ip = "78.77.76.75"
  #$script:local_ip = "2001:78:77:76::75"

  # Set IP address(es) of local AP(s), examples:

    # One IP address
  #$script:ap_ips = "10.1.1.2"

    # IP addresses, subnet and range
  #$script:ap_ips = "10.1.1.2", "10.1.1.3", "10.1.2.0/24", "10.1.3.2-10.1.3.9"

    # I don't want to manage APs here (empty list)
  $script:ap_ips = @()

# END OF CONFIGURATION ########################################################

  # Set prefix for DisplayName creation
  $script:display_name_prefix = "_eduroam"

  if ($local_ip.Contains(":"))
  {
    $script:monitoring_ips = "2001:718:ff05:10b::234"
    $script:flr_ips        = "2001:718:ff05:aca::1:10", "2001:718:ff05:aca::1:11", "2001:718:ff05:aca::1:12"
    $script:icmp_version   = "ICMPv6"
  }
  else
  {
    $script:monitoring_ips = "78.128.248.234"
    $script:flr_ips        = "78.128.248.10", "78.128.248.11", "78.128.248.12"
    $script:icmp_version   = "ICMPv4"
  }

  # Output collors
  $script:group_bc = "blue"
  $script:group_fc = "white"
  $script:fail_bc  = "red"
  $script:fail_fc  = "white"
  $script:ok_bc    = "green"
  $script:ok_fc    = "black"

  # Can we continue?
  check_settings
}

# Check settings and decide if we can continue
function check_settings
{
  # Prevent accidental deletion of all firewall and IPsec rules
  if ($display_name_prefix.Length -lt 1)
  {
    Write-Host ("DisplayName prefix '{0}' is invalid! Exiting." -f $display_name_prefix) `
      -ForegroundColor $fail_fc `
      -BackgroundColor $fail_bc
    exit 1
  }

  # Is $local_ip valid IP address?
  if (-Not ($local_ip -As [IPAddress] -As [Bool]))
  {
    Write-Host ("Local IP address '{0}' is invalid! Exiting." -f $local_ip) `
      -ForegroundColor $fail_fc `
      -BackgroundColor $fail_bc
    exit 1
  }
}

# Internal verifiing function
function verify_exit_status($status)
{
  if (-Not $status)
  {
    Write-Host " FAIL! " -ForegroundColor $fail_fc -BackgroundColor $fail_bc
    exit 1
  }
  else
  {
    Write-Host " OK " -ForegroundColor $ok_fc -BackgroundColor $ok_bc
  }
}

# Remove old firewall rules
function remove_old_firewall_rules
{
  Write-Host "Remove old firewall rules:" `
    -ForegroundColor $group_fc `
    -BackgroundColor $group_bc

  Write-Host "Removing old firewall rules: " -NoNewline
  Remove-NetFirewallRule `
    -DisplayName ("{0}*" -f $display_name_prefix)
  verify_exit_status $?

  Write-Host
}

# Remove old IPsec rules
function remove_old_ipsec_rules
{
  Write-Host "Remove old IPsec rules:" `
    -ForegroundColor $group_fc `
    -BackgroundColor $group_bc

  Write-Host "Removing old IPsec MainMode rules: " -NoNewline
  Remove-NetIPsecMainModeRule `
    -DisplayName ("{0}*" -f $display_name_prefix)
  verify_exit_status $?

  Write-Host "Removing old IPsec ConSec rules: " -NoNewline
  Remove-NetIPSecRule `
    -DisplayName ("{0}*" -f $display_name_prefix)
  verify_exit_status $?

  Write-Host "Removing old IPsec MainMode crypto sets: " -NoNewline
  Remove-NetIPsecMainModeCryptoSet `
    -DisplayName ("{0}*" -f $display_name_prefix)
  verify_exit_status $?

  Write-Host "Removing old IPsec auth sets: " -NoNewline
  Remove-NetIPsecPhase1AuthSet `
    -DisplayName ("{0}*" -f $display_name_prefix)
  verify_exit_status $?

  Write-Host
}

# Apply additional settings
function apply_additional_settings
{
  Write-Host "Apply additional settings:" `
      -ForegroundColor $group_fc `
      -BackgroundColor $group_bc

  # Enable IPsec NAT-T
  Write-Host "Enabling IPsec NAT-T: " -NoNewline
  Set-NetFirewallSetting `
    -AllowIPsecThroughNAT Both
  verify_exit_status $?

  Write-Host
}

# Create new firewall inbound rules
function create_new_firewall_rules_in
{
  Write-Host "Create new firewall inbound rules:" `
    -ForegroundColor $group_fc `
    -BackgroundColor $group_bc

  Write-Host "Creating new firewall ICMP-in rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} ICMP (IN)" -f $display_name_prefix) `
    -Direction Inbound `
    -Protocol $icmp_version `
    -LocalAddress $local_ip `
    -RemoteAddress $($flr_ips + $monitoring_ips) `
    -Action Allow `
    > $null
  verify_exit_status $?

  # If the $ap_ips variable is invalid, we create a rule without it
  Write-Host "Creating new firewall RADIUS-in rule: " -NoNewline
  try
  {
    New-NetFirewallRule `
      -DisplayName ("{0} RADIUS (IN)" -f $display_name_prefix) `
      -Direction Inbound `
      -Protocol UDP `
      -LocalPort 1812, 1813 `
      -LocalAddress $local_ip `
      -RemoteAddress $($flr_ips + $monitoring_ips + $ap_ips) `
      -Action Allow `
      -ErrorAction Stop `
      > $null
  }
  catch
  {
    Write-Host " Ignoring invalid 'ap_ips' " `
      -ForegroundColor $fail_fc `
      -BackgroundColor $fail_bc `
      -NoNewline
    Write-Host " " -NoNewline

    New-NetFirewallRule `
      -DisplayName ("{0} RADIUS (IN)" -f $display_name_prefix) `
      -Direction Inbound `
      -Protocol UDP `
      -LocalPort 1812, 1813 `
      -LocalAddress $local_ip `
      -RemoteAddress $($flr_ips + $monitoring_ips) `
      -Action Allow `
      > $null
  }
  verify_exit_status $?

  Write-Host "Creating new firewall IPsec-UDP-in rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} IPsec UDP (IN)" -f $display_name_prefix) `
    -Direction Inbound `
    -Protocol UDP `
    -LocalPort 500, 4500 `
    -LocalAddress $local_ip `
    -RemoteAddress $flr_ips `
    -Action Allow `
    > $null
  verify_exit_status $?

  Write-Host "Creating new firewall IPsec-ESP-in rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} IPsec ESP (IN)" -f $display_name_prefix) `
    -Direction Inbound `
    -Protocol 50 `
    -LocalAddress $local_ip `
    -RemoteAddress $flr_ips `
    -Action Allow `
    > $null
  verify_exit_status $?

  Write-Host
}

# Create new firewall outbound rules
function create_new_firewall_rules_out
{
  Write-Host "Create new firewall outbound rules:" `
    -ForegroundColor $group_fc `
    -BackgroundColor $group_bc

  Write-Host "Creating new firewall ICMP-out rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} ICMP (OUT)" -f $display_name_prefix) `
    -Direction Outbound `
    -Protocol $icmp_version `
    -LocalAddress $local_ip `
    -RemoteAddress $($flr_ips + $monitoring_ips) `
    -Action Allow `
    > $null
  verify_exit_status $?

  Write-Host "Creating new firewall RADIUS-out rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} RADIUS (OUT)" -f $display_name_prefix) `
    -Direction Outbound `
    -Protocol UDP `
    -RemotePort 1812 `
    -LocalAddress $local_ip `
    -RemoteAddress $flr_ips `
    -Action Allow `
    > $null
  verify_exit_status $?

  Write-Host "Creating new firewall IPsec-UDP-out rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} IPsec UDP (OUT)" -f $display_name_prefix) `
    -Direction Outbound `
    -Protocol UDP `
    -RemotePort 500, 4500 `
    -LocalAddress $local_ip `
    -RemoteAddress $flr_ips `
    -Action Allow `
    > $null
  verify_exit_status $?

  Write-Host "Creating new firewall IPsec-ESP-out rule: " -NoNewline
  New-NetFirewallRule `
    -DisplayName ("{0} IPsec ESP (OUT)" -f $display_name_prefix) `
    -Direction Outbound `
    -Protocol 50 `
    -LocalAddress $local_ip `
    -RemoteAddress $flr_ips `
    -Action Allow `
    > $null
  verify_exit_status $?

  Write-Host
}

# Create new IPsec rules
function create_new_ipsec_rules
{
  Write-Host "Create new IPsec rules:" `
    -ForegroundColor $group_fc `
    -BackgroundColor $group_bc

  Write-Host ("Creating IPsec auth proposal for local certificate selection: ") -NoNewline
  $selection_proposal = New-NetIPsecAuthProposal `
                          -Machine `
                          -Cert `
                          -Authority "DC=cz, DC=cesnet-ca, O=CESNET CA, CN=eduroam CA 2" `
                          -AuthorityType Root `
                          -SubjectName $local_fqdn `
                          -SubjectNameType DomainName `
                          -SelectionCriteria
  verify_exit_status $?

  Write-Host ("Creating IPsec auth proposal for remote certificate validation: ") -NoNewline
  $validation_proposal = New-NetIPsecAuthProposal `
                           -Machine `
                           -Cert `
                           -Authority "DC=org, DC=edupki, CN=eduPKI CA G 01" `
                           -AuthorityType Root `
                           -SubjectName "flr.eduroam.cz" `
                           -SubjectNameType DomainName `
                           -ValidationCriteria
  verify_exit_status $?

  Write-Host "Creating IPsec auth set: " -NoNewline
  $auth_set = New-NetIPsecPhase1AuthSet `
                -DisplayName ("{0} AS" -f $display_name_prefix) `
                -Proposal $selection_proposal,$validation_proposal
  verify_exit_status $?

  Write-Host "Creating IPsec MainMode crypto proposal: " -NoNewline
  $main_mode_proposal = New-NetIPsecMainModeCryptoProposal `
                          -Encryption AES256 `
                          -Hash SHA256 `
                          -KeyExchange DH20
  verify_exit_status $?

  Write-Host "Creating IPsec MainMode crypto set: " -NoNewline
  $main_mode_crypto_set = New-NetIPsecMainModeCryptoSet `
                            -DisplayName ("{0} MMCS" -f $display_name_prefix) `
                            -MaxMinutes 1440 `
                            -Proposal $main_mode_proposal
  verify_exit_status $?

  Write-Host "Creating IPsec QuickMode proposal: " -NoNewline
  $quick_mode_proposal = New-NetIPsecQuickModeCryptoProposal `
                           -Encapsulation ESP `
                           -Encryption AES256 `
                           -ESPHash SHA256 `
                           -MaxKiloBytes 102400 `
                           -MaxMinutes 240
  verify_exit_status $?

  Write-Host "Creating IPsec QuickMode crypto set: " -NoNewline
  $quick_mode_crypto_set = New-NetIPsecQuickModeCryptoSet `
                             -DisplayName ("{0} QMCS" -f $display_name_prefix) `
                             -Proposal $quick_mode_proposal
  verify_exit_status $?

  Write-Host "Creating IPsec MainMode rule: " -NoNewline
  New-NetIPsecMainModeRule `
    -DisplayName ("{0} MainMode" -f $display_name_prefix) `
    -LocalAddress $local_ip `
    -RemoteAddress $flr_ips `
    -Phase1AuthSet $auth_set.Name `
    -MainModeCryptoSet $main_mode_crypto_set.Name `
    > $null
  verify_exit_status $?

  Write-Host "Creating IPsec ConSec rule: " -NoNewline
  New-NetIPSecRule `
    -DisplayName ("{0} ConSec" -f $display_name_prefix) `
    -Mode Transport `
    -KeyModule IKEv1 `
    -InboundSecurity Require `
    -OutboundSecurity Require `
    -Protocol Any `
    -LocalAddress $local_ip `
    -LocalPort Any `
    -RemoteAddress $flr_ips `
    -RemotePort Any `
    -Phase1AuthSet $auth_set.Name `
    -QuickModeCryptoSet $quick_mode_crypto_set.Name `
    > $null
  verify_exit_status $?

  Write-Host
}

# Establish run order
function main
{
  declare_variables

  remove_old_ipsec_rules
  remove_old_firewall_rules

  if ($additions    -And -Not $remove_only) { apply_additional_settings     }
  if ($firewall_in  -And -Not $remove_only) { create_new_firewall_rules_in  }
  if ($firewall_out -And -Not $remove_only) { create_new_firewall_rules_out }
  if ($ipsec        -And -Not $remove_only) { create_new_ipsec_rules        }

  Write-Host " All done " -ForegroundColor $ok_fc -BackgroundColor $ok_bc
}

main