IIS FTPで一時的にフォルダを公開する

ユーザー認証はしない(anonymous)けど、接続元IPを限定することによって、ある程度のセキュリティは維持でき、一定時間が過ぎると仮想ディレクトリごと削除する。そんな機能が必要になったので、適当に作ってみました。やってみると、意外と難しくない。というか、Microsoft製品のAPIって良くできているなぁと改めて思うほど。

// ftpvdir.cs - A sample code for creating FTP virtual directory
using System;
using System.Collections;
using System.IO;
using System.DirectoryServices;
using System.Security.AccessControl;
using System.Reflection;
using System.Net;
using System.Net.Sockets;

class FtpVDirTest
{
  public static void Main(string[] args)
  {
    if(args.Length < 3)
    {
      Console.WriteLine("ftpvdir <VDIR> <PDIR> <GRANT_PEER_ADDRESS>");
      return;
    }
    
    CreateWriteOnlyFtpDirectory(args[0], args[1], IPAddress.Parse(args[2]));
    Console.WriteLine("Hit any key to close {0}.", args[0]);
    Console.ReadKey();
    DeleteFtpDirectory(args[0]);
  }
  
  static string IIS_FTP_ROOT = "IIS://localhost/msftpsvc/1/Root";
  static string IIS_FTP_SCHEMA = "IIsFtpVirtualDir";
  
  static void CreateWriteOnlyFtpDirectory(string virtualDirName, string physhicalDirPath, IPAddress userIpAddress)
  {
    string ipWithMask;
    if(userIpAddress.AddressFamily == AddressFamily.InterNetwork)
      ipWithMask = userIpAddress.ToString() + ", 255.255.255.255";
    else if(userIpAddress.AddressFamily == AddressFamily.InterNetworkV6)
      ipWithMask = userIpAddress.ToString() + "::1/64";
    else
      throw new ArgumentException("Unsupported Address family: {0}",
        userIpAddress.AddressFamily.ToString());
    
    DirectoryEntry ftpRoot = new DirectoryEntry(IIS_FTP_ROOT);

    ftpRoot.RefreshCache();
    DirectoryEntry deNewVDir = ftpRoot.Children.Add(virtualDirName, IIS_FTP_SCHEMA);

    deNewVDir.Properties["Path"].Insert(0, physhicalDirPath);
    
    // READ=1, WRITE=2, READWRITE=3
    deNewVDir.Properties["AccessFlags"].Insert(0, 2);

    // Grant only accesses from the specified peer
    object ipSecurity = deNewVDir.Properties["IPSecurity"][0];
    SetProperty(ipSecurity, "GrantByDefault", false);
    SetProperty(ipSecurity, "IPGrant", new object[] {ipWithMask});
    deNewVDir.Properties["IPSecurity"][0] = ipSecurity;

    deNewVDir.CommitChanges();
    ftpRoot.CommitChanges();

    deNewVDir.CommitChanges();
    ftpRoot.CommitChanges();
    deNewVDir.Close();
    ftpRoot.Close();
  }
  
  static void DeleteFtpDirectory(string virtualDirName)
  {
    DirectoryEntry ftpRoot = new DirectoryEntry(IIS_FTP_ROOT);

    ftpRoot.RefreshCache();
    DirectoryEntry vDir = ftpRoot.Children.Find(virtualDirName, IIS_FTP_SCHEMA);
    ftpRoot.Children.Remove(vDir);
    ftpRoot.CommitChanges();
    vDir.Close();
    ftpRoot.Close();
  }
  
  static object SetProperty(object obj, string name, object val)
  {
    return obj.GetType().InvokeMember(
      name,
      BindingFlags.DeclaredOnly | 
      BindingFlags.Public | BindingFlags.NonPublic | 
      BindingFlags.Instance | BindingFlags.SetProperty,
      null, obj, new object[] {val});
  }
}

このコードは、

ftpvdir hogehoge c:\foobar 192.168.11.56

とやると、localhost上に、192.168.11.56からしかアクセスできないFTPの仮想フォルダhogehogeを作成します。