Partager via


Créer un fournisseur d’authentification FTP avec des restrictions d’adresse IP dynamique

par Robert McMurray

Microsoft a créé un service FTP entièrement réécrit pour Windows Server® 2008. Ce nouveau service FTP intègre de nombreuses nouvelles fonctionnalités qui permettent aux auteurs Web de publier du contenu plus facilement qu’auparavant. Il offre également aux administrateurs Web davantage d’options de sécurité et de déploiement.

Le nouveau service FTP 7.5 prend en charge l'extensibilité qui vous permet d'étendre les fonctionnalités intégrées au service FTP. Plus précisément, FTP 7.5 prend en charge de la création de vos propres fournisseurs d'authentification. Vous pouvez également créer des fournisseurs pour la journalisation FTP personnalisée et pour déterminer les informations de répertoire de base de vos utilisateurs FTP.

Cette procédure pas à pas vous guide tout au long des étapes d’utilisation du code managé vers un fournisseur d’authentification FTP qui prend en charge les restrictions IP dynamiques qui utilisent une base de données SQL Server pour stocker les informations de compte. Ce fournisseur implémente cette logique en journalisant le nombre d’échecs à partir d’adresses IP distantes, puis en utilisant ces informations pour bloquer les adresses IP qui ne parviennent pas à se connecter au serveur dans un délai donné.

Important

La dernière version du service FTP 7.5 doit être installée pour utiliser le fournisseur dans cette procédure pas à pas. Une version FTP 7.5 a été publiée le 3 août 2009 qui a résolu un problème où les adresses IP locales et distantes dans la méthode IFtpLogProvider.Log() étaient incorrectes. Pour cette raison, l’utilisation d’une version antérieure du service FTP empêche ce fournisseur de fonctionner.

Prérequis

Les éléments suivants sont nécessaires pour effectuer les procédures décrites dans cet article :

  1. IIS 7.0 ou version ultérieure doit être installé sur votre serveur Windows Server 2008, et le Gestionnaire Internet Information Services (IIS) doit également être installé.

  2. Le nouveau service FTP 7.5 doit être installé.

    Important

    Comme mentionné précédemment dans ce guide, la dernière version du service FTP 7.5 doit être installée pour pouvoir utiliser le fournisseur de ce guide. Une version FTP 7.5 a été publiée le 3 août 2009 qui a résolu un problème où les adresses IP locales et distantes dans la méthode IFtpLogProvider.Log() étaient incorrectes. Pour cette raison, l’utilisation d’une version antérieure du service FTP empêche ce fournisseur de fonctionner.

  3. Vous devez activer la publication FTP pour un site.

  4. Vous devez utiliser Visual Studio 2008.

    Remarque

    Si vous utilisez une version antérieure de Visual Studio, certaines des étapes décrites dans cette procédure détaillée peuvent ne pas être correctes.

  5. Vous devez utiliser une base de données SQL Server pour la liste des comptes d’utilisateur et les listes de restrictions associées ; cet exemple ne peut pas être utilisé avec l’authentification FTP de base. La section « Informations supplémentaires » de cette procédure pas à pas contient un script pour SQL Server qui crée les tables nécessaires pour cet exemple.

  6. Vous aurez besoin de Gacutil.exe sur votre ordinateur IIS ; cela est nécessaire pour ajouter les assemblys à votre Global Assembly Cache (GAC).

Important

Pour améliorer les performances des demandes d’authentification, le service FTP met en cache les informations d’identification des connexions réussies pendant 15 minutes par défaut. Ce fournisseur d’authentification refuse immédiatement les demandes d’un attaquant, mais si l’attaquant a réussi à deviner le mot de passe d’un utilisateur récemment connecté, il pourrait obtenir un accès via les informations d’identification mises en cache. Cela peut avoir la conséquence involontaire de permettre à un utilisateur malveillant d’attaquer votre serveur après que ce fournisseur ait bloqué son adresse IP. Pour atténuer ce risque d’attaque, vous devez désactiver la mise en cache des informations d’identification pour le service FTP. Pour cela, utilisez les étapes suivantes :

  1. Ouvrez une invite de commandes.

  2. Tapez les commandes suivantes :

    cd /d "%SystemRoot%\System32\Inetsrv"
    Appcmd.exe set config -section:system.ftpServer/caching /credentialsCache.enabled:"False" /commit:apphost
    Net stop FTPSVC
    Net start FTPSVC
    
  3. Fermez l'invite de commande.

Une fois ces modifications effectuées, le fournisseur d’authentification de cet exemple peut refuser immédiatement toutes les demandes d’un attaquant potentiel.

Entrez une description du fournisseur

Cette procédure pas à pas contient plusieurs points qui nécessitent d’être discutés. Les attaques Internet exploitent souvent un serveur FTP dans la tentative d’obtenir le nom d’utilisateur et le mot de passe d’un compte sur un système. La détection de ce comportement est possible via l’analyse des journaux d’activité FTP, l’examen des adresses IP utilisées pour attaquer votre système, et le blocage de ces adresses afin d’empêcher un accès futur. Malheureusement, il s’agit d’un processus manuel, et même si ce processus est automatisé, il ne sera pas en temps réel.

Le service FTP contient une fonctionnalité permettant de restreindre les connexions basées sur des adresses IP, mais la liste des adresses IP est stockée dans les fichiers de configuration IIS et nécessite un accès administratif pour être mise à jour. Le processus d’extensibilité pour le service FTP s’exécute en tant que compte à privilèges inférieurs qui n’a pas les autorisations nécessaires pour mettre à jour les paramètres requis des fichiers de configuration IIS. Vous pourriez écrire un fournisseur de journalisation FTP qui détecte les inondations de nom d’utilisateur et écrit ces informations dans un magasin de données et un service distinct qui s’exécute en tant que compte à privilèges plus élevés qui peut mettre à jour les fichiers de configuration IIS, mais cela nécessite une plus grande connaissance de l’architecture système ainsi qu’un certain nombre de détails d’implémentation difficiles. Pour cette raison, un autre magasin de données est nécessaire.

Une base de données fait un choix idéal en raison de la facilité d’accès aux données et de la disponibilité générale des outils permettant de manipuler des données dans une base de données. Le défi suivant consiste à utiliser les interfaces d’extensibilité FTP existantes pour implémenter la logique nécessaire permettant de détecter les inondations de connexion qu’un attaquant utiliserait. Les interfaces d’extensibilité disponibles sont les suivantes :

Vous pourriez facilement écrire un fournisseur qui tire parti de toutes ces interfaces afin de renforcer la sécurité à un degré plus élevé, mais le fournisseur de cette procédure pas à pas utilise uniquement les interfaces suivantes :

  • IFtpAuthenticationProvider : le fournisseur utilise cette interface pour autoriser ou refuser l’accès au serveur FTP.
  • IFtpLogProvider : le fournisseur utilise cette interface comme écouteur d’événements générique.

Le service FTP n’a pas de notifications d’événements réelles auxquelles un fournisseur peut s’inscrire, mais vous pouvez écrire des fournisseurs qui utilisent la méthode IFtpLogProvider.Log() pour fournir un traitement post-événement. Par exemple, toutes les tentatives de connexion ayant échoué journalisent la commande « PASS » avec un code d’état autre que « 230 », qui est le code d’état d’une connexion FTP réussie. En capturant des informations supplémentaires sur une tentative de connexion ayant échoué, telles que l’adresse IP du client qui n’a pas pu se connecter, il devient possible d’utiliser ces informations pour fournir des fonctionnalités supplémentaires, telles que le blocage des adresses IP d’accès à votre serveur FTP à l’avenir.

Architecture et logique du fournisseur

Les descriptions suivantes résument le comportement de ce fournisseur d’authentification :

  • Lorsque vous inscrivez le fournisseur sur votre système, vous spécifiez la connexion de base de données à utiliser et les valeurs du nombre de tentatives d’ouverture de session ayant échoué et du délai d’expiration des inondations dans vos fichiers de configuration IIS.

  • Lorsque le service FTP charge votre fournisseur, il fournit les valeurs de vos fichiers de configuration IIS à la méthode Initialize() de votre fournisseur. Une fois ces valeurs stockées dans des paramètres globaux, la méthode Initialize() effectue un nettoyage de la mémoire initial pour nettoyer les informations des sessions FTP précédentes qui pourraient se trouver dans la base de données.

  • Lorsqu’un client FTP se connecte au serveur FTP, la méthode Log() du fournisseur reçoit le message « ControlChannelOpened » par le service FTP. La méthode Log() examine la base de données pour voir si l’adresse IP du client a été bloquée ; si c’est le cas, elle signale la session dans la base de données.

  • Lorsque l’utilisateur entre son nom d’utilisateur et son mot de passe, le service FTP appelle la méthode AuthenticateUser() du fournisseur, qui vérifie si la session est marquée. Si la session est marquée par un indicateur, le fournisseur retourne false, ce qui signifie que l’utilisateur n’a pas pu se connecter. Si la session n’est pas marquée, le nom d’utilisateur et le mot de passe sont examinés dans la base de données pour voir s’ils sont valides. S’ils sont valides, la méthode retourne la valeur true, indiquant que l’utilisateur est valide et peut se connecter.

  • Si l’utilisateur ne parvient pas à entrer un nom d’utilisateur et un mot de passe valides, la méthode Log() est appelée par le service FTP et la méthode exécute un nettoyage de la mémoire périodique pour s’assurer que le nombre d’échecs est inférieur au délai d’expiration des inondations. Ensuite, la méthode vérifie si le nombre restant d’échecs est inférieur au nombre maximal d’échecs :

    • Si le nombre maximal d’échecs n’a pas été atteint, la méthode ajoute une notification d’échec pour l’adresse IP du client à la base de données.
    • Si le nombre maximal d’échecs a été atteint, la méthode ajoute l’adresse IP du client à la liste des adresses IP bloquées dans la base de données.
  • Lorsqu’un client FTP se déconnecte du serveur, le service FTP appelle la méthode Log() du fournisseur et envoie le message « ControlChannelClosed ». La méthode Log() tire parti de cette notification pour effectuer un nettoyage de la mémoire pour la session.

Remarques supplémentaires

  • Ce fournisseur expose les fonctionnalités de validation d’adresses IP et utilisateur, mais ne fournit pas d’implémentation pour les recherches de rôle. Cela dit, il serait relativement facile d’ajouter une table supplémentaire pour les mappages utilisateur-à-rôle et d’ajouter la méthode IFtpRoleProvider.IsUserInRole() au fournisseur, mais cela ne concerne pas cette procédure pas à pas.
  • Ce fournisseur effectue un petit nombre d’appels au serveur de base de données SQL pendant le processus d’authentification. Grâce à la consolidation de quelques instructions SQL dans des requêtes composées uniques ou des procédures stockées, vous pouvez réduire davantage le nombre d’allers-retours vers la base de données, mais cela ne concerne pas cette procédure pas à pas.

Étape 1 : configurer l'environnement de projet

Dans cette étape, vous allez créer un projet dans Visual Studio 2008 pour le fournisseur de version de démonstration.

  1. Ouvrez Microsoft Visual Studio 2008.

  2. Cliquez sur le menu Fichier, puis Nouveau, puis Projet.

  3. Dans la boîte de dialogue Nouveau projet :

    • Choisissez Visual C# comme type de projet.
    • Choisissez Bibliothèque de classes comme modèle.
    • Tapez FtpAddressRestrictionAuthentication comme nom du projet.
    • Cliquez sur OK.
  4. Lorsque le projet s’ouvre, ajoutez un chemin d’accès de référence à la bibliothèque d’extensibilité FTP :

    • Cliquez sur Project, puis sur Propriétés FtpAddressRestrictionAuthentication.

    • Cliquez sur l’onglet Chemins d’accès de référence.

    • Saisissez le chemin d'accès à l'assembly d'extensibilité FTP pour votre version de Windows, où le disque local C: est votre lecteur de système d'exploitation.

      • Pour Windows Server 2008 et Windows Vista :

        C:\Windows\assembly\GAC_MSIL\Microsoft.Web.FtpServer\7.5.0.0__31bf3856ad364e35
        
      • Pour Windows 7 :

        C:\Program Files\Reference Assemblies\Microsoft\IIS
        
    • Cliquez sur Ajouter un dossier.

  5. Ajoutez une clé de nom fort au projet :

    • Cliquez sur Project, puis sur Propriétés FtpAddressRestrictionAuthentication.
    • Cliquez sur l'onglet Signature .
    • Sélectionnez la case à cocher Signer l'assembly.
    • Choisissez <Nouveau...> dans la liste déroulante du nom fort de la clé.
    • Entrez FtpAddressRestrictionAuthenticationKey comme nom du fichier de clé.
    • Si vous le souhaitez, saisissez un mot de passe pour le fichier de clé. Au cas contraire, décochez la case Protéger mon fichier de clé avec un mot de passe.
    • Cliquez sur OK.
  6. Facultatif : vous pouvez ajouter un événement de build personnalisé pour ajouter automatiquement la DLL au Global Assembly Cache (GAC) sur votre ordinateur de développement :

    • Cliquez sur Project, puis sur Propriétés FtpAddressRestrictionAuthentication.

    • Cliquez sur l’onglet Événements de build.

    • Dans la boîte de dialogue Ligne de commande de l'événement post-build, saisissez la commande suivante :

      net stop ftpsvc
      call "%VS90COMNTOOLS%\vsvars32.bat">null
      gacutil.exe /if "$(TargetPath)"
      net start ftpsvc
      
  7. Enregistrez le projet.

Étape 2 : Créer la classe d'extensibilité

Dans cette étape, vous allez implémenter l'interface d'extensibilité pour le fournisseur de démonstration.

  1. Ajoutez une référence à la bibliothèque d'extensibilité FTP pour le projet :

    • Cliquez sur Projet, puis sur Ajouter une référence...
    • Sous l’onglet .NET, cliquez sur Microsoft.Web.FtpServer.
    • Cliquez sur OK.
  2. Ajoutez une référence à System.Web pour le projet :

    • Cliquez sur Projet, puis sur Ajouter une référence...
    • Sous l’onglet .NET, cliquez sur System.Web.
    • Cliquez sur OK.
  3. Ajoutez une référence à System.Configuration pour le projet :

    • Cliquez sur Projet, puis sur Ajouter une référence...
    • Sous l’onglet .NET, cliquez sur System.Configuration.
    • Cliquez sur OK.
  4. Ajoutez une référence à System.Data pour le projet :

    • Cliquez sur Project, puis sur Ajouter une référence.
    • Sous l’onglet .NET, cliquez sur System.Web.
    • Cliquez sur OK.
  5. Ajoutez le code de la classe d'authentification :

    • Dans l’Explorateur de solutions, double-cliquez sur le fichier Class1.cs.

    • Supprimez le code existant.

    • Collez le code suivant dans l’éditeur :

      using System;
      using System.Collections.Generic;
      using System.Collections.Specialized;
      using System.Configuration.Provider;
      using System.Data;
      using System.Data.SqlClient;
      using System.Text;
      using Microsoft.Web.FtpServer;
      
      public class FtpAddressRestrictionAuthentication :
        BaseProvider,
        IFtpLogProvider,
        IFtpAuthenticationProvider
      {
        // Define the default values - these are only
        // used if the configuration settings are not set.
        const int defaultLogonAttempts = 5;
        const int defaultFloodSeconds = 30;
      
        // Define a connection string with no default.
        private static string _connectionString;
      
        // Initialize the private variables with the default values.
        private static int _logonAttempts = defaultLogonAttempts;
        private static int _floodSeconds = defaultFloodSeconds;
      
        // Flag the application as uninitialized.
        private static bool _initialized = false;
      
        // Define a list that will contain the list of flagged sessions.
        private static List<string> _flaggedSessions;
      
        // Initialize the provider.
        protected override void Initialize(StringDictionary config)
        {
          // Test if the application has already been initialized.
          if (_initialized == false)
          {
            // Create the flagged sessions list.
            _flaggedSessions = new List<string>();
      
            // Retrieve the connection string for the database connection.
            _connectionString = config["connectionString"];
            if (string.IsNullOrEmpty(_connectionString))
            {
              // Raise an exception if the connection string is missing or empty.
              throw new ArgumentException(
                "Missing connectionString value in configuration.");
            }
            else
            {
              // Determine whether the database is a Microsoft Access database.
              if (_connectionString.Contains("Microsoft.Jet"))
              {
                // Throw an exception if the database is a Microsoft Access database.
                throw new ProviderException("Microsoft Access databases are not supported.");
              }
            }
      
            // Retrieve the number of failures before an IP
            // address is locked out - or use the default value.
            if (int.TryParse(config["logonAttempts"], out _logonAttempts) == false)
            {
              // Set to the default if the number of logon attempts is not valid.
              _logonAttempts = defaultLogonAttempts;
            }
      
            // Retrieve the number of seconds for flood
            // prevention - or use the default value.
            if (int.TryParse(config["floodSeconds"], out _floodSeconds) == false)
            {
              // Set to the default if the number of logon attempts is not valid.
              _floodSeconds = defaultFloodSeconds;
            }
      
            // Test if the number is a positive integer and less than 10 minutes.
            if ((_floodSeconds <= 0) || (_floodSeconds > 600))
            {
              // Set to the default if the number of logon attempts is not valid.
              _floodSeconds = defaultFloodSeconds;
            }
      
            // Initial garbage collection.
            GarbageCollection(true);
            // Flag the provider as initialized.
            _initialized = true;
          }
        }
      
        // Dispose of the provider.
        protected override void Dispose(bool disposing)
        {
          base.Dispose(disposing);
      
          // Test if the application has already been uninitialized.
          if (_initialized == true)
          {
            // Final garbage collection.
            GarbageCollection(true);
            // Flag the provider as uninitialized.
            _initialized = false;
          }
        }
      
        // Authenticate a user.
        bool IFtpAuthenticationProvider.AuthenticateUser(
          string sessionId,
          string siteName,
          string userName,
          string userPassword,
          out string canonicalUserName)
        {
          // Define the canonical user name.
          canonicalUserName = userName;
      
          // Check if the session is flagged.
          if (IsSessionFlagged(sessionId) == true)
          {
            // Return false (authentication failed) if the session is flagged.
            return false;
          }
      
          // Check the user credentials and return the status.
          return IsValidUser(userName, userPassword);
        }
      
        // Implement custom actions by using the Log() method.
        void IFtpLogProvider.Log(FtpLogEntry loggingParameters)
        {
          // Test if the control channel was opened or the USER command was sent.
          if ((String.Compare(loggingParameters.Command,
            "ControlChannelOpened", true) == 0)
            || (String.Compare(loggingParameters.Command,
            "USER", true) == 0))
          {
            // Check if the IP address is banned.
            if (IsAddressBanned(loggingParameters.RemoteIPAddress) == true)
            {
              // If the IP is banned, flag the session.
              FlagSession(loggingParameters.SessionId);
              return;
            }
          }
          // Test if the PASS command was sent.
          if (String.Compare(loggingParameters.Command,
            "PASS", true) == 0)
          {
            // Check for password failures (230 is a success).
            if (loggingParameters.FtpStatus != 230)
            {
              // Periodic garbage collection - remove authentication
              // failures that are older than the flood timeout.
              GarbageCollection(false);
      
              // Test if the existing number of failures exceeds the maximum logon attempts.
              if (GetRecordCountByCriteria("[Failures]",
                "[IPAddress]='" + loggingParameters.RemoteIPAddress +
                "'") < _logonAttempts)
              {
                // Add the failure to the list of failures.
                InsertDataIntoTable("[Failures]",
                  "[IPAddress],[FailureDateTime]",
                  "'" + loggingParameters.RemoteIPAddress +
                  "','" + DateTime.Now.ToString() + "'");
              }
              else
              {
                // Ban the IP address if authentication has failed
                // from that IP more than the defined number of failures.
                BanAddress(loggingParameters.RemoteIPAddress);
                FlagSession(loggingParameters.SessionId);
              }
              return;
            }
          }
          // Test if the control channel was closed.
          if (String.Compare(loggingParameters.Command,
            "ControlChannelClosed", true) == 0)
          {
            // Session-based garbage collection - remove the
            // current session from the list of flagged sessions.
            _flaggedSessions.Remove(loggingParameters.SessionId);
            return;
          }
        }
      
        // Check for a valid username/password.
        private static bool IsValidUser(
          string userName,
          string userPassword)
        {
          // Define the initial status as the credentials are not valid.
          try
          {
            // Create a new SQL connection object.
            using (SqlConnection connection = new SqlConnection(_connectionString))
            {
              // Create a new SQL command object.
              using (SqlCommand command = new SqlCommand())
              {
                // Specify the connection for the command object.
                command.Connection = connection;
                // Specify a text command type.
                command.CommandType = CommandType.Text;
      
                // Specify the SQL text for the command object.
                command.CommandText = "SELECT COUNT(*) AS [NumRecords] " +
                  "FROM [Users] WHERE [UID]=@UID AND [PWD]=@PWD AND [Locked]=0";
      
                // Add parameters for the user name and password.
                command.Parameters.Add("@UID", SqlDbType.NVarChar).Value = userName;
                command.Parameters.Add("@PWD", SqlDbType.NVarChar).Value = userPassword;
      
                // Open the database connection.
                connection.Open();
                // Return the valid status for the credentials.
                return ((int)command.ExecuteScalar() > 0);
              }
            }
          }
          catch (Exception ex)
          {
            // Raise an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
        }
      
        // Check if the IP is banned.
        private bool IsAddressBanned(string ipAddress)
        {
          // Return whether the IP address was found in the banned addresses table.
          return (GetRecordCountByCriteria("[BannedAddresses]",
            "[IPAddress]='" + ipAddress + "'") != 0);
        }
      
        // Check if the session is flagged.
        private bool IsSessionFlagged(string sessionId)
        {
          // Return whether the session ID was found in the flagged sessions table.
          return _flaggedSessions.Contains(sessionId);
        }
      
        // Mark a session as flagged.
        private void FlagSession(string sessionId)
        {
          // Check if the session is already flagged.
          if (IsSessionFlagged(sessionId) == false)
          {
            // Flag the session if it is not already flagged.
            _flaggedSessions.Add(sessionId);
          }
        }
      
        // Mark an IP address as banned.
        private void BanAddress(string ipAddress)
        {
          // Check if the IP address is already banned.
          if (IsAddressBanned(ipAddress) == false)
          {
            // Ban the IP address if it is not already banned.
            InsertDataIntoTable("[BannedAddresses]",
              "[IPAddress]", "'" + ipAddress + "'");
          }
        }
      
        // Perform garbage collection tasks.
        private void GarbageCollection(bool deleteSessions)
        {
          // Remove any authentication failures that are older than the flood timeout.
          DeleteRecordsByCriteria("[Failures]",
            String.Format("DATEDIFF(second,[FailureDateTime],'{0}')>{1}",
            DateTime.Now.ToString(),_floodSeconds.ToString()));
      
          // Test if flagged sessions should be deleted.
          if (deleteSessions == true)
          {
            // Remove any sessions from the list of flagged sessions.
            _flaggedSessions.Clear();
          }
        }
      
        // Retrieve the count of records based on definable criteria.
        private int GetRecordCountByCriteria(
          string tableName,
          string criteria)
        {
          // Create a SQL string to retrieve the count of records 
          // that are found in a table based on the criteria.
          StringBuilder sqlString = new StringBuilder();
          sqlString.Append("SELECT COUNT(*) AS [NumRecords]");
          sqlString.Append(String.Format(
            " FROM {0}",tableName));
          sqlString.Append(String.Format(
            " WHERE {0}",criteria));
          // Execute the query.
          return ExecuteQuery(true, sqlString.ToString());
        }
      
        // Insert records into a database table.
        private void InsertDataIntoTable(
          string tableName,
          string fieldNames,
          string fieldValues)
        {
          // Create a SQL string to insert data into a table.
          StringBuilder sqlString = new StringBuilder();
          sqlString.Append(String.Format(
            "INSERT INTO {0}",tableName));
          sqlString.Append(String.Format(
            "({0}) VALUES({1})",fieldNames, fieldValues));
          // Execute the query.
          ExecuteQuery(false, sqlString.ToString());
        }
      
        // Remove records from a table based on criteria.
        private void DeleteRecordsByCriteria(
          string tableName,
          string queryCriteria)
        {
          // Create a SQL string to delete data from a table.
          StringBuilder sqlString = new StringBuilder();
          sqlString.Append(String.Format(
            "DELETE FROM {0}",tableName));
          // Test if any criteria is specified.
          if (string.IsNullOrEmpty(queryCriteria) == false)
          {
            // Append the criteria to the SQL string.
            sqlString.Append(String.Format(
              " WHERE {0}",queryCriteria));
          }
          // Execute the query.
          ExecuteQuery(false, sqlString.ToString());
        }
      
        // Execute SQL queries.
        private int ExecuteQuery(bool returnRecordCount, string sqlQuery)
        {
          try
          {
            // Create a new SQL connection object.
            using (SqlConnection connection =
              new SqlConnection(_connectionString))
            {
              // Create a new SQL command object.
              using (SqlCommand command =
                new SqlCommand(sqlQuery, connection))
              {
                // Open the connection.
                connection.Open();
                // Test whether the method should return a record count.
                if (returnRecordCount == true)
                {
                  // Run the database query.
                  SqlDataReader dataReader = command.ExecuteReader();
                  // Test if data reader has returned any rows.
                  if (dataReader.HasRows)
                  {
                    // Read a single row.
                    dataReader.Read();
                    // Return the number of records.
                    return ((int)dataReader["NumRecords"]);
                  }
                }
                else
                {
                  // Run the database query.
                  command.ExecuteNonQuery();
                }
              }
            }
            // Return a zero record count.
            return 0;
          }
          catch (Exception ex)
          {
            // Raise an exception if an error occurs.
            throw new ProviderException(ex.Message);
          }
        }
      }
      
  6. Enregistrez et compilez le projet.

Remarque

Si vous n'avez pas utilisé les étapes facultatives pour inscrire les assemblys dans le GAC, vous devez copier manuellement les assemblys sur votre ordinateur IIS et ajouter les assemblys au GAC à l'aide de l'outil Gacutil.exe. Pour plus d'informations, consultez Gacutil.exe (Global Assembly Cache Tool).

Étape 3 : Ajouter le fournisseur de démonstration au protocole FTP

Dans cette étape, vous allez ajouter le fournisseur de démonstration à votre service FTP et au site web par défaut.

  1. Déterminez les informations d'assembly pour le fournisseur d'extensibilité :

    • Dans l'Explorateur Windows, ouvrez votre chemin d'accès C:\Windows\assembly, où le disque local C: est votre lecteur de système d'exploitation.
    • Recherchez l’assembly FtpAddressRestrictionAuthentication.
    • Faites un clic droit sur l'assembly, puis cliquez sur Propriétés.
    • Copiez la valeur Culture ; par exemple, Neutre.
    • Copiez le numéro de version, par exemple 1.0.0.0.
    • Copiez la valeur du jeton de clé publique, par exemple 426f62526f636b73.
    • Cliquez sur Annuler.
  2. À l’aide des informations des étapes précédentes, ajoutez le fournisseur d’extensibilité à la liste globale des fournisseurs FTP et configurez les options du fournisseur :

    • Pour le moment, il n’existe aucune interface utilisateur qui vous permet d’ajouter des propriétés pour un module d’authentification personnalisé. Vous devez donc utiliser la ligne de commande suivante :

      cd %SystemRoot%\System32\Inetsrv
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"[name='FtpAddressRestrictionAuthentication',type='FtpAddressRestrictionAuthentication,FtpAddressRestrictionAuthentication,version=1.0.0.0,Culture=neutral,PublicKeyToken=426f62526f636b73']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication'].[key='connectionString',value='Server=localhost;Database=FtpAuthentication;User ID=FtpLogin;Password=P@ssw0rd']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication'].[key='logonAttempts',value='5']" /commit:apphost
      
      appcmd.exe set config -section:system.ftpServer/providerDefinitions /+"activation.[name='FtpAddressRestrictionAuthentication'].[key='floodSeconds',value='30']" /commit:apphost
      

    Remarque

    La chaîne de connexion que vous spécifiez dans l’attribut connectionString doit être une connexion valide pour votre base de données.

  3. Ajoutez le fournisseur personnalisé à un site :

    • Pour le moment, aucune interface utilisateur ne vous permet d'ajouter des fonctionnalités personnalisées à un site. Vous devrez donc utiliser la ligne de commande suivante :

      AppCmd.exe set config -section:system.applicationHost/sites /"[name='Default Web Site'].ftpServer.security.authentication.basicAuthentication.enabled:False" /commit:apphost
      
      AppCmd.exe set config -section:system.applicationHost/sites /+"[name='Default Web Site'].ftpServer.security.authentication.customAuthentication.providers.[name='FtpAddressRestrictionAuthentication',enabled='True']" /commit:apphost
      
      AppCmd set site "Default Web Site" /+ftpServer.customFeatures.providers.[name='FtpAddressRestrictionAuthentication',enabled='true'] /commit:apphost
      

    Remarque

    Cette syntaxe désactive l’authentification FTP de base, et il est important de la désactiver lors de l’utilisation de ce fournisseur d’authentification. Sinon, lorsque l’adresse IP d’un attaquant a été bloquée par ce fournisseur d’authentification, un attaquant peut toujours attaquer les comptes qui utilisent l’authentification de base.

  4. Ajoutez une règle d’autorisation pour le fournisseur d’authentification :

    • Double-cliquez sur Règles d’autorisation FTP dans la fenêtre principale.

    • Cliquez sur Ajouter une règle d'autorisation... dans le volet Actions.

    • Sélectionnez Utilisateurs spécifiés pour l’option d’accès.

    • Entrez un nom d’utilisateur.

      Remarque

      Le nom d’utilisateur doit être entré dans la base de données en dehors de cette liste d’étapes.

    • Sélectionnez Lecture et/ou Écriture pour l’option Autorisations.

    • Cliquez sur OK.

Étape 4 : Utilisation du fournisseur avec FTP 7.5

Lorsque les clients FTP se connectent à votre site FTP, le service FTP tente d’authentifier les utilisateurs auprès de votre fournisseur d’authentification personnalisé à l’aide de comptes stockés dans la base de données. Si un client FTP ne parvient pas à s’authentifier, le fournisseur effectue le suivi de l’adresse IP et de la date/heure de l’échec dans la base de données. Lorsqu’un client FTP ne parvient pas à se connecter à partir d’une adresse IP spécifique pour le nombre d’échecs spécifiés dans le paramètre logonAttempts et dans le délai spécifié dans le paramètre floodSeconds, le fournisseur bloque la connexion à l’adresse IP du service FTP.

Remarque

Cet exemple de fournisseur implémente la logique d’authentification pour le service FTP, mais ne fournit pas de module d’administrateur pour gérer les données dans la base de données. Par exemple, vous ne pouvez pas gérer la liste des comptes d’utilisateur FTP, des adresses IP interdites ou des échecs d’authentification à l’aide de ce fournisseur. Pour gérer les données à l’aide du Gestionnaire IIS, vous pouvez utiliser le Gestionnaire de base de données IIS. Pour plus d'informations, voir la rubrique suivante :

https://www.iis.net/extensions/DatabaseManager

Informations supplémentaires

Vous pouvez utiliser le script SQL suivant pour Microsoft SQL Server afin de créer la base de données et les tables nécessaires. Pour utiliser ce script, vous devez mettre à jour le nom de la base de données et l’emplacement des fichiers de base de données. Dans SQL Server, vous exécutez le script dans une nouvelle fenêtre de requête, puis vous créez une connexion de base de données que vous utiliserez avec votre chaîne de connexion.

Remarque

Vous pouvez modifier le script SQL pour stocker la base de données à un emplacement autre que c:\databases.

/****** Create the FtpAuthentication Database ******/

USE [master]
GO
CREATE DATABASE [FtpAuthentication] ON  PRIMARY 
( NAME = N'FtpAuthentication', FILENAME = N'c:\databases\FtpAuthentication.mdf' , SIZE = 2048KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )
 LOG ON 
( NAME = N'FtpAuthentication_log', FILENAME = N'c:\databases\FtpAuthentication_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)
 COLLATE SQL_Latin1_General_CP1_CI_AS
GO
EXEC dbo.sp_dbcmptlevel @dbname=N'FtpAuthentication', @new_cmptlevel=90
GO
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))
begin
EXEC [FtpAuthentication].[dbo].[sp_fulltext_database] @action = 'enable'
end
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_NULL_DEFAULT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_NULLS OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_PADDING OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ANSI_WARNINGS OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ARITHABORT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_CLOSE OFF 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_CREATE_STATISTICS ON 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_SHRINK OFF 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_UPDATE_STATISTICS ON 
GO
ALTER DATABASE [FtpAuthentication] SET CURSOR_CLOSE_ON_COMMIT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET CURSOR_DEFAULT  GLOBAL 
GO
ALTER DATABASE [FtpAuthentication] SET CONCAT_NULL_YIELDS_NULL OFF 
GO
ALTER DATABASE [FtpAuthentication] SET NUMERIC_ROUNDABORT OFF 
GO
ALTER DATABASE [FtpAuthentication] SET QUOTED_IDENTIFIER OFF 
GO
ALTER DATABASE [FtpAuthentication] SET RECURSIVE_TRIGGERS OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ENABLE_BROKER 
GO
ALTER DATABASE [FtpAuthentication] SET AUTO_UPDATE_STATISTICS_ASYNC OFF 
GO
ALTER DATABASE [FtpAuthentication] SET DATE_CORRELATION_OPTIMIZATION OFF 
GO
ALTER DATABASE [FtpAuthentication] SET TRUSTWORTHY OFF 
GO
ALTER DATABASE [FtpAuthentication] SET ALLOW_SNAPSHOT_ISOLATION OFF 
GO
ALTER DATABASE [FtpAuthentication] SET PARAMETERIZATION SIMPLE 
GO
ALTER DATABASE [FtpAuthentication] SET READ_WRITE 
GO
ALTER DATABASE [FtpAuthentication] SET RECOVERY SIMPLE 
GO
ALTER DATABASE [FtpAuthentication] SET MULTI_USER 
GO
ALTER DATABASE [FtpAuthentication] SET PAGE_VERIFY CHECKSUM  
GO
ALTER DATABASE [FtpAuthentication] SET DB_CHAINING OFF 

/****** Create the Database Tables ******/

USE [FtpAuthentication]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[BannedAddresses]') AND type in (N'U'))
BEGIN
CREATE TABLE [BannedAddresses](
    [IPAddress] [nvarchar](50) NOT NULL
) ON [PRIMARY]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Failures]') AND type in (N'U'))
BEGIN
CREATE TABLE [Failures](
    [IPAddress] [nvarchar](50) NOT NULL,
    [FailureDateTime] [datetime] NOT NULL
) ON [PRIMARY]
END
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[Users]') AND type in (N'U'))
BEGIN
CREATE TABLE [Users](
    [UID] [nvarchar](50) NOT NULL,
    [PWD] [nvarchar](50) NOT NULL,
    [Locked] [bit] NOT NULL
) ON [PRIMARY]
END

Résumé

Dans cette procédure détaillée, vous avez appris à :

  • Créer un projet dans Visual Studio 2008 pour un fournisseur FTP personnalisé.
  • Implémenter les interfaces d’extensibilité pour un fournisseur FTP personnalisé.
  • Ajouter un fournisseur personnalisé FTP à votre service FTP.