#! /usr/local/bin/ruby -w

# Usage : $0 access_list_file
#         $0 < access_list

# Ce script prend en entrée une access list dans un format Cisco
# modifié : les addresses IP sont spécifiées au format CIDR plutôt qu'au format classique
# (préhistorique) de Cisco avec "wildcard mask".

# Donc une entrée d'access list du type :
# permit ip 212.43.195.0/27 any
# sera modifiée pour être au format Cisco standard avec "wildcard mask" :
# permit ip 212.43.195.0 0.0.0.31 any

# Fonction utilitaire pour génération d'exceptions dans la translation
def translation_error(line_count, token, msg)
  raise "Line #{line_count} token #{token}: #{msg}"
end

def translate(tokens, line_count)
  # Entrée : une access list Cisco modifiée (sous forme d'un tableau de tokens)
  # Sortie : une access list Cisco (sous forme d'un tableau de tokens)

  # On parse l'access list de façon très basique. On se contente de repérer
  # les addresses IP dans l'access list et de les remplacer par le format Cisco
  # avec "wildcard mask".

  new_tokens = []

  token = tokens.shift
  if token =~ /^(permit|deny)$/ then
    new_tokens << token
  else
    translation_error(line_count, token, "permit or deny expected")
  end

  token = tokens.shift
  if token =~ /^(ip|tcp|udp|icmp)$/ then
    new_tokens << token
  else
    translation_error(line_count, token, "unexpected protocol")
  end

  token = tokens.shift
  begin
    new_tokens << parse_address(token)
  rescue
    translation_error(line_count, token, "can't parse source address")
  end

  token = tokens.shift

  if token == "eq"
    new_tokens << token
    token = tokens.shift
    if token =~ /^(\d+|\w+)$/ then
      new_tokens << token
    else
      translation_error(line_count, token, "can't parse source port")
    end
    token = tokens.shift

  elsif token == "range"
    new_tokens << token
    token = tokens.shift
    if token =~ /^\d+/ then
      new_tokens << token
    else
      translation_error(line_count, token, "can't parse range first port")
    end
    token = tokens.shift
    if token =~ /^\d+/ then
      new_tokens << token
    else
      translation_error(line_count, token, "can't parse range last port")
    end
    token = tokens.shift

  end

  begin
    new_tokens << parse_address(token)
  rescue
    translation_error(line_count, token, "can't parse destination address")
  end

  # On a repère les addresses source et destination, tout le reste n'est pas modifié
  while tokens.size > 0 do
    new_tokens << tokens.shift
  end

  return new_tokens

end

# Cette fonction prend en entrée une addresse au format CIDR et renvoie une addresse
# au format Cisco avec "wildcard mask".
def parse_address(token)

  if token == "any" then
    return token

  elsif token =~ /(\d+\.\d+\.\d+\.\d+)\/(\d+)/ then
    ip = $1
    mask_length = $2.to_i
    
    raise "wrong mask length" unless (mask_length >= 0 and mask_length <= 32)
    all_ones = 0xffffffff
    wildcard_mask = all_ones >> mask_length

    # dot notation
    wildcard_mask = [24, 16, 8, 0].collect {|b| (wildcard_mask >> b) & 255}.join('.')
    
    return "#{ip} #{wildcard_mask}"
    
  elsif token =~ /\d+\.\d+\.\d+\.\d+/ then
    return "host #{token}"

  else
    raise "unexpected format for IP address (#{token})"
  end

end

line_count = 0
ARGF.each do |line|
  line_count += 1
  line.chomp!

  tokens = line.split
  new_tokens = translate(tokens, line_count)
  puts new_tokens.join(" ")

end

