因为公司内部需要访问外部公网上的一个对帐系统,而这个对帐系统使用8443作为SSL的隧道端口,当从内部访问时便会出现错误。之前做过一次添加,因为升级到了ISA2006,重新修改了访问规则等配置,竟然忘记扩展端口了,最近频繁有员工反映这个问题,于是决定载入到 Blog 上做个永久性工作笔记。

      先创建一个.js文件,当然这个文件是从网上获取到现成的,我直接将代码粘贴至此,方便我拷贝。

/*
  This program is Copyright (c) 2004 Microsoft Corporation. 
 
   All rights reserved.
 
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.
 
   IN NO EVENT SHALL MICROSOFT AND/OR ITS RESPECTIVE SUPPLIERS BE
   LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
   DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
   WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
   ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
   OF THIS CODE OR INFORMATION.

   This program will allow the user to create, remove and display
   Tunnel Port Ranges on either ISA 2000 or ISA 2004.

   Revisions:
   - 08/19/2004 - First working version
*/

var g_oObjects = new Objects;
var g_oValues = new Values;
var g_oMessages = new Messages;

main();

/**********************************************************************
 * main()
 * This function:
 * 1. Attempts to create the ISA Admin COM object
 *  Determines if the environment is ISA 2K or ISA 2K4
 *  Sets g_oObjects.tpRanges to a proper TPR object
 *      Defers to ParseArgs() to perform the proper tasking
 * 2. calls into
 *      GetISA()
 *      ParseArgs()
 *  3. called by
 *  - user -
 *
 * if successful:
 * 1. g_oObjects.ISA and g_oObjects.ThisArray are valid ISA objects
 *  2. returns the result of GetISA() or ParseArgs() as appropriate
 *
 * if unsuccessful:
 * 1. called functions indicate the failure and cause
 *  2. returns the result of GetISA() or ParseArgs() as appropriate
 *********************************************************************/
function main()
{
 //WScript.Echo( 'working in main().' );
 var iRtn;

 iRtn = GetISA();
 if ( iRtn == g_oValues.OK )
 {
   g_oObjects.tpRanges =
    g_oObjects.thisArray.ArrayPolicy.WebProxy.TunnelPortRanges;
     iRtn = ParseArgs( );
 }

 return iRtn;
}


/**********************************************************************
 * GetISA( )
 * This function:
 * 1. Creates the default ISA COM object
 *  Determines if the environment is ISA 2K or ISA 2K4
 *  Sets g_oObjects.ISA to the proper ISA object
 * 2. calls into
 *  GetISA2KSE()
 *  GetISA2K4SE()
 *  ShowErrors()
 *  3. called by
 *  main()
 *
 * if successful:
 * 1. g_oObjects.ISA and g_oObjects.ThisArray are valid objects
 *  2. returns g_oValues.OK
 *
 * if unsuccessful:
 * 1. ShowErrors indicate the failure and cause
 * 2. returns values provided by called functions
 *********************************************************************/
function GetISA( )
{
 //WScript.Echo( 'working in GetISA().' );
 var szSE = 'FPC.Root';
 var iRtn;
 
 try
 {
  g_oObjects.ISA = new ActiveXObject( szSE );
  iRtn = GetISA2KSE( );
  if( iRtn == g_oValues.notISA2K )
  { 
   iRtn = GetISA2K4SE( );
  }
  if( iRtn )
  {
   WScript.Echo( g_oMessages.L_NoISA_txt )
   ShowUsage( WScript.Arguments );
  }
 }
 catch( err )
 {
  ShowErrors( err, g_oMessages.L_NoISA_txt );
  ShowUsage( WScript.Arguments );
  iRtn = g_oValues.noISA;
 }

 return iRtn;
}

/**********************************************************************
 * GetISA2KSE( )
 * This function:
 * 1. Distinguishes between ISA2KSE, ISA2KEE and ISA Admin-only
 * 2. calls into
 *  GetISA2K4EE()
 *  ShowErrors()
 *  3. called by
 *  GetISA()
 *
 * if successful:
 * 1. g_oObjects.ISA and g_oObjects.ThisArray are valid ISA objects
 *  2. returns g_oValues.OK
 *
 * if unsuccessful:
 * 1. ShowErrors indicate the failure and cause
 * 2. returns values provided by called functions
 *********************************************************************/
function GetISA2KSE( )
{
 //WScript.Echo( 'working in GetISA2KSE().' );
 var fpcTypeArray = 2;
 var iRtn = g_oValues.OK;
 
 try
 {
  if( g_oObjects.ISA.Type == fpcTypeArray )
  {
   iRtn = GetISA2KEE( );
  }

  if( iRtn == g_oValues.OK )
  {
   g_oObjects.thisArray = g_oObjects.ISA.Arrays.GetContainingArray();
  }
 }
 catch( err )
 {
  switch( ToHex( err.number ) )
  {
   case g_oValues.lErrNotSupported:
    iRtn = g_oValues.notISA2K;
    err.clear;
    break;
   default:
    iRtn = err.number;
    ShowErrors( err, g_oMessages.L_notISA2K_txt );
  }
 }

 return iRtn;
 
}

/**********************************************************************
 * GetISA2KEE( )
 * This function:
 * 1. Sets g_oObjects.ISA to the correct EE context
 * 2. calls into
 *  GetISA2K4EE()
 *  ShowErrors()
 *  3. called by
 *  GetISASE()
 *
 * if successful:
 * 1. g_oObjects.ISA is a valid EE object
 *  2. returns g_oValues.OK
 *
 * if unsuccessful:
 * 1. ShowErrors indicate the failure and cause
 * 2. returns values provided by called functions
 *********************************************************************/
function GetISA2KEE( )
{
 //WScript.Echo( 'working in GetISA2KEE().' );
 var szEE = 'FPCDS.Root';
 var iRtn;
 
 try
 {
  g_oObjects.ISA = new ActiveXObject( szEE );
  iRtn = g_oValues.OK;
 }
 catch( err )
 {
  iRtn = err.number;
  ShowErrors( err, g_oMessages.L_ErrUnknown_txt );
 }
 return iRtn;
}

/**********************************************************************
 * GetISA2K4SE( )
 * This function:
 * 1. Sets g_oObjects.ISA to the an ISA 2004SE context
 * 2. calls into
 *  ShowErrors()
 *  3. called by
 *  GetISASE()
 *
 * if successful:
 * 1. g_oObjects.ISA is a valid ISA 2004 SE object
 *  2. returns g_oValues.OK
 *
 * if unsuccessful:
 * 1. ShowErrors indicate the failure and cause
 * 2. returns values according to determined state
 *********************************************************************/
function GetISA2K4SE( )
{
 //WScript.Echo( 'working in GetISA2K4SE().' );
 var iRtn;
 
 try
 {
  g_oObjects.thisArray = g_oObjects.ISA.GetContainingArray();
  g_oObjects.thisArray.Refresh();
  iRtn = g_oValues.OK;
 }
 catch( err )
 {
  switch( ToHex( err.number ) )
  {
   case g_oValues.lErrNotFound:
    err.clear;
    iRtn = g_oValues.AdminOnly;
    break;
   default:
    iRtn = err.number
    ShowErrors( err, g_oMessages.L_ErrUnknown_txt );
  }   
 }
 return iRtn;
}


/**********************************************************************
 * ParseArgs( )
 * This function:
 * 1. Evaluates the cmd-line arguments
 *     Calls appropriate function
 * 2. calls into
 *      AddRange()
 *      DelRange()
 *      ShowRanges()
 *  ShowUsage()
 *  3. called by
 *  main()
 *
 * if successful:
 * 1. user-defined action is executed
 *  2. returns value from called function
 *
 * if unsuccessful:
 * 1. returns values according to determined state
 *********************************************************************/
function ParseArgs( )
{
 //WScript.Echo( 'working in ParseArgs( ).' );
 var Args = WScript.Arguments;
 var iRtn = g_oValues.badCommand;
 var szName = '';
 var szJob = '';
 var inx;
 
 if ( Args.length == 0 )
 {
  return ShowRanges( '' );
 }

 szJob = Args( 0 ).toLowerCase();
 if( Args.length == 1 && ( szJob == '/add' || szJob == '/del' ) )
 {
  szJob = '';
 }

 if( Args.length >= 2 )
 {
  szName = Args( 1 );
 }
  
 switch( szJob )
 {
  case '/add':
   if( Args.length >= 3 && CheckPorts( Args ) == g_oValues.OK )
   {
    if( AddRange( Args ) == g_oValues.OK )
    {
     ShowRanges( szName );
    }
    else
    {
     ShowRanges( '' );
    }
   }
   else
   {
    ShowUsage( Args );
   }
   break;
  case '/del':
   if( Args.length >= 2 )
   {
    iRtn = DelRange( szName );
    ShowRanges( '' );
   }
   else
   {
    ShowUsage( Args );
   }
   break;
  case '/show':
   iRtn = ShowRanges( szName );
   break;
  default:
   ShowUsage( Args );
 }
 return iRtn;
}


/**********************************************************************
 * AddRange( )
 * This function:
 * 1. Adds a defined tunnel port range to ISA Web Proxy
 * 2. calls into
 *  ShowError()
 *  3. called by
 *  ParseArgs()
 *
 * if successful:
 * 1. user-defined tunnel port range is added
 *  2. returns g_oValues.OK
 *
 * if unsuccessful:
 * 1. returns values according to failure state
 *********************************************************************/
function AddRange( oArgs )
{
 //WScript.Echo( 'working in AddRange().' );
 var iRtn = g_oValues.OK;
 var name = '';
 var port1 = 0;
 var port2 = 0;

 name = oArgs( 1 );
 port1 = oArgs( 2 );

 if( oArgs.length == 3 )
 {
  port2 = port1;
 }
 else
 {
  port2 = oArgs( 3 );
 }

 if( port1 > port2 )
 {
  port1 = oArgs( 3 );
  port2 = oArgs( 2 );
 }

 if( DoesItExist( name, port1, port2 ) )
 {
  return g_oValues.PortsExist ;
 }

 try
 {
  g_oObjects.tpRanges.AddRange( name, port1, port2 );
  g_oObjects.tpRanges.Save();
  WScript.Echo( g_oMessages.L_RestartSvcs_txt );
  iRtn = g_oValues.OK;
 }
 catch( err )
 {
  iRtn = err.number;
  ShowErrors( err, g_oMessages.L_GenError_txt +
     ' adding \'' + name + '\'.\r\n' );
 }
 return iRtn;
}


/**********************************************************************
 * DelRange( )
 * This function:
 * 1. Removes a defined tunnel port range from ISA Web Proxy
 * 2. calls into
 *  ShowError()
 *  3. called by
 *  ParseArgs()
 *
 * if successful:
 * 1. user-defined tunnel port range is removed
 *  2. returns g_oValues.OK
 *
 * if unsuccessful:
 * 1. returns values according to failure state
 *********************************************************************/
function DelRange( szName )
{
 //WScript.Echo( 'working in DelRange().' );
 var iRtn = g_oValues.OK;

 try
 {
  g_oObjects.tpRanges.Remove( szName );
  g_oObjects.tpRanges.Save();
  WScript.Echo( g_oMessages.L_RestartSvcs_txt );
  iRtn = g_oValues.OK;
 }
 catch( err )
 {
  if( ToHex( err.number ) == g_oValues.lErrNotFound )
  {
   WScript.Echo( '\'' + szName + g_oMessages.L_TprNotFound_txt );
  }
  else
  {
   iRtn = err.number
   ShowErrors( err, g_oMessages.L_GenError_txt +
      ' deleting \'' + szName + '\'.\r\n' );
  }
 }
 return iRtn;
}


/**********************************************************************
 * ShowRanges( )
 * This function:
 * 1. displays eiither a selected range or the entire tunnel port ranges
 *     listing depending on user-provided options
 * 2. calls into
 *      ShowRange()
 *      ShowUsage()
 *  3. called by
 *  ParseArgs()
 *
 * if successful:
 * 1. user-defined tunnel port range or entire tunnel port definition
 *     is displayed
 *
 * if unsuccessful:
 * 1. returns values according to failure state
 *********************************************************************/
function ShowRanges( szName )
{
 //WScript.Echo( 'working in ShowRanges(' + szName + ').' );
 var cTprList = null;
 var TPR  = null;
 var iRtn = g_oValues.OK;

 if( szName == '' )
 {
     WScript.Echo ( g_oMessages.L_ShowList_txt );
  cTprList = new Enumerator( g_oObjects.tpRanges );
  for ( ; !cTprList.atEnd(); cTprList.moveNext() )
  {
         TPR = cTprList.item();
   ShowRange( TPR );
     }
  return iRtn;
 }
  
 try
 {
  TPR = g_oObjects.tpRanges( szName );
     WScript.Echo ( g_oMessages.L_ShowItem_txt + szName + ':\r\n' );
  ShowRange( TPR );
 }
 catch( err )
 {
  if( ToHex( err.number ) == g_oValues.lErrNotFound )
  {
   WScript.Echo( '\'' + szName + g_oMessages.L_TprNotFound_txt );
   iRtn = ShowRanges( '' );
  }
  else
  {
   iRtn = err.number
   ShowErrors( err, g_oMessages.L_GenError_txt +
      ' showing \'' + szName + '\'.\r\n' );
  }
 }
 return iRtn;
}


/**********************************************************************
 * ShowRange( )
 * This function:
 * 1. displays the data from a selected tunnel pot range
 * 2. calls into
 *     - none -
 *  3. called by
 *  ShowRanges()
 *
 * if successful:
 * 1. selected tunnel port range definition is displayed
 *
 * if unsuccessful:
 * 1. dunno
 *********************************************************************/
function ShowRange( oTPR )
{
 //WScript.Echo( 'working in ShowRange(' + oTPR.Name + ' ).' );

    if ( oTPR.TunnelLowPort == oTPR.TunnelHighPort )
 {
        WScript.Echo( '\t' + oTPR.Name + ' (single port): ' + oTPR.TunnelLowPort );
 }
    else
 {
        WScript.Echo( '\t' + oTPR.Name + ' (port range) : ' + oTPR.TunnelLowPort +
            ' --> ' + oTPR.TunnelHighPort );
 }
}

/**********************************************************************
 * CheckPorts( oArgs )
 * This function:
 * 1. Compares the user-specified port values to predefined limits
 * 2. calls into
 *     - none -
 *  3. called by
 *  ParseArgs()
 *
 * Returns g_oValues.OK if ports are within limits, g_oValues.badCommand
     otherwise
 *********************************************************************/
function CheckPorts( oArgs )
{
 var iRtn = g_oValues.OK;
 
 if( parseInt( oArgs( 2 ) ) < 1 ||
  parseInt( oArgs( 2 ) ) > 65535 )
  {
   WScript.Echo( '\'' + oArgs( 2 ) + '\'' + g_oMessages.L_NaN_txt );
   iRtn = g_oValues.badCommand;
  }
 if( oArgs.length >= 4 )
 {
  if( parseInt( oArgs( 3 ) ) < 1 ||
   parseInt( oArgs( 3 ) ) > 65535 )
   {
    WScript.Echo( '\'' + oArgs( 3 ) + '\'' + g_oMessages.L_NaN_txt );
    iRtn = g_oValues.badCommand;
   }
 }
 return iRtn;
}

/**********************************************************************
 * DoesItExist( szName, iPort1, iPort2 )
 * This function:
 * 1. compares the new port range data to existing definitions
 * 2. calls into
 *  - nothing -
 *  3. called by
 *  AddRange()
 *
 *  4 Returns true if definition exists in any form, false otherwise
 *
 * errors are not evaluated
 *********************************************************************/
function DoesItExist( szName, iPort1, iPort2 )
{
 //WScript.Echo( 'working in DoesItExist(' + szName + ', ' + iPort1 + ', ' + iPort2 + ').' );
 var TPR;
 var cTprList = new Enumerator( g_oObjects.tpRanges );

 for ( ; !cTprList.atEnd(); cTprList.moveNext() )
 {
        TPR = cTprList.item();
  if( TPR.Name.toLowerCase() == szName.toLowerCase() ||
   ( TPR.TunnelLowPort == iPort1 &&
    TPR.TunnelHighPort == iPort2 )
    )
  {
   WScript.Echo( g_oMessages.L_TprExists_txt + TPR.Name + '\'' );
   return true;
  }
    }
 return false;
}

/**********************************************************************
 * ToHex( lValue)
 * This function:
 * 1. Converts a number to its hexadecimal equivalent and accounts for
 *  negative numbers (hResults)
 * 2. calls into
 *  - nothing -
 *  3. called by
 *  - nearly all functions -
 *
 * errors are not evaluated
 *********************************************************************/
function ToHex( lValue)
{
 var lNewVal;
 var szHexVal;

 if( lValue >= 0 && lValue < 10 )
 {
  szHexVal = lValue.toString();
 }
 else
 {
  lNewVal = ( lValue < 0 )? lValue + 0x100000000: lValue;
  szHexVal = lNewVal.toString( 16 ).toUpperCase();
 }
 return szHexVal;
}

/**********************************************************************
 * ShowErrors( oErr, szMessage )
 * This function:
 * 1. Displays szMessage and any error data if not running in MPSReports
 *  
 * 2. calls into
 *  LogMessage
 *  3. called by
 *  - nearly all functions -
 *
 * errors are not evaluated
 *********************************************************************/
function ShowErrors( oErr, szMessage )
{
 var WshShell = new ActiveXObject( 'WScript.Shell' );
 var Exclamation = 48;
 var YesNo = 4;
 var Yes = 6;
 var No = 7;
 var RtnVal;
 
 if( oErr != null )
 {
   szMessage += g_oMessages.L_ErrNum_txt + ToHex( oErr.number ) +
     g_oMessages.L_ErrDesc_txt + oErr.description +
     g_oMessages.L_CopyMsg_txt;
 }

 RtnVal = WshShell.Popup( szMessage, 0, g_oMessages.L_TitleMsg_txt,
       Exclamation + YesNo );
 if( RtnVal == No )
 {
  WScript.quit();
 }
 oErr.clear;
}

/**********************************************************************
 * ShowErrors( oErr, szMessage )
 * This function:
 * 1. Displays szMessage and any error data if not running in MPSReports
 *  
 * 2. calls into
 *  LogMessage
 *  3. called by
 *  - nearly all functions -
 *
 * errors are not evaluated
 *********************************************************************/
function ShowUsage( oArgs )
{
 var szJob = WScript.ScriptName;
 var inx;
 
 for( inx = 0; inx < oArgs.length; inx++ )
 {
  szJob += ( ' ' + oArgs( inx ) );
 }
 if( szJob.indexOf( '?' ) == -1 )
 {
  WScript.Echo( g_oMessages.L_BadCommand_txt + '\'' + szJob + '\'\r\n' );
 }
 WScript.Echo( g_oMessages.L_Usage_txt );
}


/**********************************************************************
 * Objects()
 * This function holds our global objects
 *  
 *********************************************************************/
function Objects()
{
 this.ISA = null;   //core ISA COM object
 this.thisArray = null;  //current operating array
 this.tpRanges = null;  //selected tunnel port range collection
}

/**********************************************************************
 * Values()
 * This function holds our global values
 *  
 *********************************************************************/
function Values()
{
 this.OK = 0;
 this.ng_oObjects = 999;  //failed to get a useful ISA COM object
 this.AdminOnly = 666;  //ISA Admin only
 this.notISA2K = 333;  //not an ISA 2000 COM
 this.badCommand = 123;
 this.PortsExist = 321;
 this.lErrNotFound = '80070002';  //E_NOT_FOUND
 this.lErrNotSupported = '800A01B6'; //method/property not supported
 this.lErrExists = '800700B7';  //item already exists
}

/**********************************************************************
 * Messages()
 * This function holds our global messages
 *  
 *********************************************************************/
function Messages()
{
 this.divider = '###############################################################################\r\n';
 this.L_Version_txt = '1.0';
 this.L_TitleMsg_txt = WScript.ScriptName + ' version ' + this.L_Version_txt;
 this.L_CopyMsg_txt = '\r\n\r\nHit <Ctrl>-C to copy this message to the clipboard.';
 this.L_noISA_txt = '\r\nISA COM objects are not properly registered on this machine.'
 this.L_notISA2K_txt = '\r\nThis is machine not an ISA 2000 Server.';
 this.L_notISA2K4_txt = '\r\nThis machine is not an ISA 2004 Server.';
 this.L_NoISA_txt = '\r\nThis is not an ISA Server.';
 this.L_ErrUnknown_txt = '\r\nUnknown error occured...';
 this.L_BadCommand_txt = '\r\n\t\tIncorrect usage: ';
 this.L_NaN_txt = ' is not a valid numerical value.';
 this.L_GenError_txt = 'Error encountered while ';
 this.L_TprNotFound_txt = '\' was not found in the list; make sure you typed it correctly.\r\n';
 this.L_TprExists_txt = 'That definiton already exists as \'';
 this.L_ShowList_txt = '\r\nThis is your current Tunnel Port Range list:\r\n';
 this.L_ShowItem_txt = '\r\nThis is the definition for ';
 this.L_ErrNum_txt = '\r\n\r\nError Number : ';
 this.L_ErrDesc_txt = '\r\nDescription  : ';
 this.L_ErrSource_txt = '\r\nSource       : ';
 this.L_RestartSvcs_txt = '\r\nThe Web Proxy service should be restarted to pick up the change.';
 this.L_Usage_txt = this.divider +
     '# \t\t\t' + this.L_TitleMsg_txt + '\r\n' +
     this.divider +
     '#\r\n' +
     '# You must execute this tool on an ISA Server as:\r\n' +
     '# cscript ' + WScript.ScriptName + ' [/opt1] [opt2] [port1] [port2], where:\r\n' +
     '# ..opt1 (text) \'/add\', \'/del\' or \'/show\'\r\n' +
      '# ..opt2 (text) The name of the tunnel port range.  This is mandatory for \r\n' +
      '#         \'/add\' and \'/del\', optional for \'/show\'.\r\n' +
     '# ..port1 (digit >0 and <65536 )\r\n' +
     '#         The first value of the port range.  This value is mandatory for \r\n' +
     '#         \'/add\' and is ignored for \'/del\' and \'/show\'.\r\n' +
     '# ..port2 (digit >0 and <65536 )\r\n' +
     '#         The second value of the port range. This value is optional for \r\n' +
     '#         \'/add\' and is ignored for \'/del\' and \'/show\'.\r\n' +
     '#\r\n' +
     '#        The order of the port1 and port2 values is not important and if they\r\n' +
     '#        are equal, a static port definition will be created.\r\n' +
     '#\r\n' +
     '# Specifying no options displays all currently configured Tunnel Port entries.\r\n' +
     '#\r\n' +
     this.divider +
     '#\r\n' +
     '# Examples:\r\n' +
     '#\r\n' +
     '# cscript ' + WScript.ScriptName + ' /add port123 123\r\n' +
     '#         ..adds a single port called \'port123\' with a static value of \'123\'\r\n' +
     '# cscript ' + WScript.ScriptName + ' /add port123-124 123 124\r\n' +
     '#         ..adds a port range called \'port123-124\' with a range of \'123\'-\'124\'\r\n' +
     '# cscript ' + WScript.ScriptName + ' /del port123\r\n' +
     '#         ..removes a port range called \'port123\'\r\n' +
     '# cscript ' + WScript.ScriptName + ' /show port123\r\n' +
     '#         ..displays a port range called \'port123\'\r\n' +
     '# cscript ' + WScript.ScriptName + ' /show\r\n' +
     '#         ..displays all defined port ranges\r\n' +
     '#\r\n' +
     this.divider;
    
}

      之后运行这个脚本,将会显示当前使用的端口,之后添加我们需要的端口,如添加8443:

cscript name.js /add Ext8443 8443

完毕。

ISA/TMG Server | 评论(2) | 引用(1719) | 阅读(6153)
gOxiA Email Homepage
2010/04/14 13:21
to:johnson,建议监控看看无法访问的具体错误信息提示什么!
Johnson
2010/04/14 11:19
我允许所有的协议,也扩展了SSL端口,为什么还是无法访问?
分页: 1/173 第一页 1 2 3 4 5 6 7 8 9 10 下页 最后页
发表评论
昵称 [注册]
密码 游客无需密码
网址
电邮
打开HTML 打开UBB 打开表情 隐藏 记住我