python

import re

# Chemin vers le fichier ns.conf
NS_CONF_PATH = r"C:\tempmax\ns.conf"

# Dictionnaire de correspondance entre type d'objet (en minuscule) et chemin dans l'interface Citrix ADC
object_paths = {
    "authentication policy": "Citrix Gateway > Policies > Authentication > Authentication Policies",
    "authentication policylabel": "Citrix Gateway > Policies > Authentication > Authentication Policy Labels",
    "vpn sessionpolicy": "Citrix Gateway > Policies > Session Policies",
    "vpn trafficpolicy": "Citrix Gateway > Policies > Traffic Policies, Profiles and Form SSO Profiles",
    "rewrite policy": "Citrix Gateway > Policies > Rewrite Policies",
    "responder policy": "Citrix Gateway > Policies > Responder Policies",
    "cache policy": "Citrix Gateway > Policies > Cache Policies",
    "lb policy": "Citrix Gateway > Policies > Load Balancing Policies",
    "cs policy": "Citrix Gateway > Policies > Content Switching Policies",
    "aaa policy": "Citrix Gateway > Policies > AAA Policies",
    "dns policy": "Citrix Gateway > Policies > DNS Policies",
    "appfw policy": "Citrix Gateway > Policies > Application Firewall Policies",
    "videooptimization policy": "Citrix Gateway > Policies > Video Optimization Policies",
    "contentinspection policy": "Citrix Gateway > Policies > Content Inspection Policies",
    "servicegroup": "Citrix Gateway > Traffic Management > Load Balancing > Service Groups",
    "ssl certkey": "Citrix Gateway > Traffic Management > SSL > Certificates and Keys",
    "server": "Citrix Gateway > Traffic Management > Servers"
}

def load_ns_conf(file_path):
    """Charge le fichier ns.conf et retourne son contenu sous forme de liste de lignes."""
    try:
        with open(file_path, "r", encoding="utf-8") as file:
            return file.readlines()
    except FileNotFoundError:
        print(f"Erreur : Le fichier {file_path} est introuvable.")
        return []

def find_defined_objects(config_lines, object_type):
    """
    Trouve tous les objets définis dans le fichier (policies, policylabels, sessionPolicy, trafficPolicy, etc.).
    Capture le nom (le premier token après la commande add) entre guillemets ou non.
    
    Exemple pour ssl certKey :
      add ssl certKey ns-server-certificate -cert s02vx9903197.fr.net.intra.crt
    """
    pattern = re.compile(rf'add {object_type} ("[^"]+"|\S+)', re.IGNORECASE)
    return set(match.group(1).strip('"') for match in pattern.finditer("\n".join(config_lines)))

def find_bound_objects(config_lines, object_type):
    """
    Trouve tous les objets bindés dans le fichier.

    Pour certains types, on analyse les lignes de binding spécifiques :
      - authentication policy :
          * Bindée directement sur un AAA_AuthVserver via '-policy'
          * OU associée via un policy label (lié par '-nextFactor' sur un AAA_AuthVserver).
      - authentication policylabel :
          Bindé si référencé via '-nextFactor' sur un AAA_AuthVserver.
      - vpn sessionPolicy et vpn trafficPolicy :
          Recherche des lignes bindant un vpn vserver via l'option '-policy'.
      - ssl certKey :
          Recherche des bindings dans les commandes "bind ssl service ..." ou "bind ssl vserver ..." via l'option '-certkeyName'.
      - Pour les autres, une recherche générique est appliquée.
    """
    config_str = "\n".join(config_lines)
    
    # --- Cas authentication policy ---
    if object_type.lower() == "authentication policy":
        direct_pattern = re.compile(
            r'bind authentication vserver\s+(\S+).*?-policy\s+("([^"]+)"|\S+)',
            re.IGNORECASE | re.DOTALL
        )
        direct_bound = set()
        for match in direct_pattern.finditer(config_str):
            vserver = match.group(1).strip('"')
            if vserver.upper().startswith("AAA_AUTHVSERVER"):
                policy = match.group(2).strip('"')
                direct_bound.add(policy)
                
        next_pattern = re.compile(
            r'bind authentication vserver\s+(\S+).*?-nextFactor\s+("([^"]+)"|\S+)',
            re.IGNORECASE | re.DOTALL
        )
        labels_bound = set()
        for match in next_pattern.finditer(config_str):
            vserver = match.group(1).strip('"')
            if vserver.upper().startswith("AAA_AUTHVSERVER"):
                label = match.group(2).strip('"')
                labels_bound.add(label)
                
        policylabel_pattern = re.compile(
            r'bind authentication policylabel\s+(\S+)\s+-policyName\s+("([^"]+)"|\S+)',
            re.IGNORECASE
        )
        label_bound_policies = set()
        for match in policylabel_pattern.finditer(config_str):
            policy_label = match.group(1).strip('"')
            policy = match.group(2).strip('"')
            if policy_label in labels_bound:
                label_bound_policies.add(policy)
                
        return direct_bound.union(label_bound_policies)
    
    # --- Cas authentication policylabel ---
    elif object_type.lower() == "authentication policylabel":
        next_pattern = re.compile(
            r'bind authentication vserver\s+(\S+).*?-nextFactor\s+("([^"]+)"|\S+)',
            re.IGNORECASE | re.DOTALL
        )
        labels_bound = set()
        for match in next_pattern.finditer(config_str):
            vserver = match.group(1).strip('"')
            if vserver.upper().startswith("AAA_AUTHVSERVER"):
                label = match.group(2).strip('"')
                labels_bound.add(label)
        return labels_bound

    # --- Cas vpn sessionPolicy ---
    elif object_type.lower() == "vpn sessionpolicy":
        pattern = re.compile(
            r'bind vpn vserver\s+\S+.*?-policy\s+("([^"]+)"|\S+)',
            re.IGNORECASE | re.DOTALL
        )
        bound_objects = set(match.group(1).strip('"') for match in pattern.finditer(config_str))
        return bound_objects

    # --- Cas vpn trafficPolicy ---
    elif object_type.lower() == "vpn trafficpolicy":
        pattern = re.compile(
            r'bind vpn vserver\s+\S+.*?-policy\s+("([^"]+)"|\S+)',
            re.IGNORECASE | re.DOTALL
        )
        bound_objects = set(match.group(1).strip('"') for match in pattern.finditer(config_str))
        return bound_objects

    # --- Cas ssl certKey ---
    elif object_type.lower() == "ssl certkey":
        # Recherche des bindings dans les lignes :
        # bind ssl service <nom_service> ... -certkeyName <nom_cert>
        # ou
        # bind ssl vserver <nom_vserver> ... -certkeyName <nom_cert>
        pattern = re.compile(
            r'bind ssl (?:service|vserver)\s+\S+.*?-certkeyName\s+("([^"]+)"|\S+)',
            re.IGNORECASE | re.DOTALL
        )
        bound_objects = set(match.group(1).strip('"') for match in pattern.finditer(config_str))
        return bound_objects

    # --- Cas générique pour les autres objets ---
    else:
        pattern_direct = re.compile(rf'bind {object_type} ("[^"]+"|\S+)', re.IGNORECASE)
        pattern_label = re.compile(rf'bind {object_type} policylabel [^\s]+ -policyName ("[^"]+"|\S+)', re.IGNORECASE)
        bound_objects = set(match.group(1).strip('"') for match in pattern_direct.finditer(config_str))
        bound_objects.update(match.group(1).strip('"') for match in pattern_label.finditer(config_str))
        return bound_objects

def find_servers_bound_to_service_groups(config_lines):
    """Trouve tous les serveurs liés à un serviceGroup ou à un vserver."""
    config_str = "\n".join(config_lines)
    pattern = re.compile(r'bind serviceGroup\s+("[^"]+"|\S+)\s+("[^"]+"|\S+)', re.IGNORECASE)
    bound_servers = set(match.group(2).strip('"') for match in pattern.finditer(config_str))
    pattern_vserver = re.compile(r'bind lb vserver\s+("[^"]+"|\S+)\s+("[^"]+"|\S+)', re.IGNORECASE)
    bound_servers.update(match.group(2).strip('"') for match in pattern_vserver.finditer(config_str))
    return bound_servers

def find_unbound_objects(file_path, object_types):
    """Identifie, pour chaque type d'objet, ceux qui sont définis mais non bindés."""
    config_lines = load_ns_conf(file_path)
    if not config_lines:
        return {}

    unbound_objects = {}

    for object_type in object_types:
        defined_objects = find_defined_objects(config_lines, object_type)
        bound_objects = find_bound_objects(config_lines, object_type)
        # Calcul des objets non bindés
        unbound = defined_objects - bound_objects
        unbound_objects[object_type] = unbound

    # Exclusion spéciale pour les serveurs liés à un serviceGroup ou à un vserver
    if "server" in unbound_objects:
        servers_in_service_groups = find_servers_bound_to_service_groups(config_lines)
        unbound_objects["server"] -= servers_in_service_groups

    return unbound_objects

def main():
    # Liste des types d'objets à analyser
    object_types = [
        "authentication policy",
        "authentication policylabel",
        "vpn sessionPolicy",     # Session Policy VPN
        "vpn trafficPolicy",     # Traffic Policy VPN
        "rewrite policy",
        "responder policy",
        "cache policy",
        "lb policy",
        "cs policy",
        "aaa policy",
        "dns policy",
        "appfw policy",
        "videooptimization policy",
        "contentinspection policy",
        "serviceGroup",   # Groupes de services
        "ssl certKey",    # Certificats SSL
        "server"          # Serveurs
    ]

    print(f"Analyse des objets dans le fichier '{NS_CONF_PATH}'...\n")
    unbound_objects = find_unbound_objects(NS_CONF_PATH, object_types)

    # Pour chaque type d'objet, afficher le chemin puis la liste (triée alphabétiquement) des objets non bindés
    for object_type in object_types:
        # Recherche du chemin associé (en utilisant la version en minuscules)
        path = object_paths.get(object_type.lower(), "Chemin non défini")
        print(f"Emplacement : {path}")
        print(f"Type d'objet : {object_type}")
        objects = unbound_objects.get(object_type, set())
        if objects:
            print("  Objets non bindés :")
            for obj in sorted(objects, key=str.lower):
                print(f"    - {obj}")
        else:
            print("  Tous les objets sont bindés.")
        print()

if __name__ == "__main__":
    main()