Add outbound URL filter to llHttpRequest() and osSetDynamicTextureURL*() script functions.
This is to address an issue where HTTP script functions could make calls to localhost and other endpoints inside the simulator's LAN. By default, all private addresses are now blocked as per http://en.wikipedia.org/wiki/Reserved_IP_addresses If you require exceptions to this, configure [Network] OutboundDisallowForUserScriptsExcept in OpenSim.ini0.7.6.3
parent
654a947e2b
commit
7995c3037a
|
@ -0,0 +1,256 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) Contributors, http://opensimulator.org/
|
||||||
|
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
* * Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
* * Neither the name of the OpenSimulator Project nor the
|
||||||
|
* names of its contributors may be used to endorse or promote products
|
||||||
|
* derived from this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||||
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||||
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using log4net;
|
||||||
|
using LukeSkywalker.IPNetwork;
|
||||||
|
using Nini.Config;
|
||||||
|
|
||||||
|
namespace OpenSim.Framework.Communications
|
||||||
|
{
|
||||||
|
public class OutboundUrlFilter
|
||||||
|
{
|
||||||
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
|
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
private List<IPNetwork> m_blacklistNetworks;
|
||||||
|
private List<IPEndPoint> m_blacklistEndPoints;
|
||||||
|
|
||||||
|
private List<IPNetwork> m_blacklistExceptionNetworks;
|
||||||
|
private List<IPEndPoint> m_blacklistExceptionEndPoints;
|
||||||
|
|
||||||
|
public OutboundUrlFilter(
|
||||||
|
string name,
|
||||||
|
List<IPNetwork> blacklistNetworks, List<IPEndPoint> blacklistEndPoints,
|
||||||
|
List<IPNetwork> blacklistExceptionNetworks, List<IPEndPoint> blacklistExceptionEndPoints)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
|
||||||
|
m_blacklistNetworks = blacklistNetworks;
|
||||||
|
m_blacklistEndPoints = blacklistEndPoints;
|
||||||
|
m_blacklistExceptionNetworks = blacklistExceptionNetworks;
|
||||||
|
m_blacklistExceptionEndPoints = blacklistExceptionEndPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="OpenSim.Framework.Communications.OutboundUrlFilter"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">Name of the filter for logging purposes.</param>
|
||||||
|
/// <param name="config">Filter configuration</param>
|
||||||
|
public OutboundUrlFilter(string name, IConfigSource config)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
|
||||||
|
string configBlacklist
|
||||||
|
= "0.0.0.0/8|10.0.0.0/8|100.64.0.0/10|127.0.0.0/8|169.254.0.0/16|172.16.0.0/12|192.0.0.0/24|192.0.2.0/24|192.88.99.0/24|192.168.0.0/16|198.18.0.0/15|198.51.100.0/24|203.0.113.0/24|224.0.0.0/4|240.0.0.0/4|255.255.255.255/32";
|
||||||
|
string configBlacklistExceptions = "";
|
||||||
|
|
||||||
|
IConfig networkConfig = config.Configs["Network"];
|
||||||
|
|
||||||
|
if (networkConfig != null)
|
||||||
|
{
|
||||||
|
configBlacklist = networkConfig.GetString("OutboundDisallowForUserScripts", configBlacklist);
|
||||||
|
configBlacklistExceptions
|
||||||
|
= networkConfig.GetString("OutboundDisallowForUserScriptsExcept", configBlacklistExceptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_log.DebugFormat(
|
||||||
|
"[OUTBOUND URL FILTER]: OutboundDisallowForUserScripts for {0} is [{1}]", Name, configBlacklist);
|
||||||
|
m_log.DebugFormat(
|
||||||
|
"[OUTBOUND URL FILTER]: OutboundDisallowForUserScriptsExcept for {0} is [{1}]", Name, configBlacklistExceptions);
|
||||||
|
|
||||||
|
OutboundUrlFilter.ParseConfigList(
|
||||||
|
configBlacklist, Name, out m_blacklistNetworks, out m_blacklistEndPoints);
|
||||||
|
OutboundUrlFilter.ParseConfigList(
|
||||||
|
configBlacklistExceptions, Name, out m_blacklistExceptionNetworks, out m_blacklistExceptionEndPoints);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseConfigList(
|
||||||
|
string fullConfigEntry, string filterName, out List<IPNetwork> networks, out List<IPEndPoint> endPoints)
|
||||||
|
{
|
||||||
|
// Parse blacklist
|
||||||
|
string[] configBlacklistEntries
|
||||||
|
= fullConfigEntry.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
|
configBlacklistEntries = configBlacklistEntries.Select(e => e.Trim()).ToArray();
|
||||||
|
|
||||||
|
networks = new List<IPNetwork>();
|
||||||
|
endPoints = new List<IPEndPoint>();
|
||||||
|
|
||||||
|
foreach (string configEntry in configBlacklistEntries)
|
||||||
|
{
|
||||||
|
if (configEntry.Contains("/"))
|
||||||
|
{
|
||||||
|
IPNetwork network;
|
||||||
|
|
||||||
|
if (!IPNetwork.TryParse(configEntry, out network))
|
||||||
|
{
|
||||||
|
m_log.ErrorFormat(
|
||||||
|
"[OUTBOUND URL FILTER]: Entry [{0}] is invalid network for {1}", configEntry, filterName);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
networks.Add(network);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Uri configEntryUri;
|
||||||
|
|
||||||
|
if (!Uri.TryCreate("http://" + configEntry, UriKind.Absolute, out configEntryUri))
|
||||||
|
{
|
||||||
|
m_log.ErrorFormat(
|
||||||
|
"[OUTBOUND URL FILTER]: EndPoint entry [{0}] is invalid endpoint for {1}",
|
||||||
|
configEntry, filterName);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress[] addresses = Dns.GetHostAddresses(configEntryUri.Host);
|
||||||
|
|
||||||
|
foreach (IPAddress addr in addresses)
|
||||||
|
{
|
||||||
|
if (addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found address [{0}] in config", addr);
|
||||||
|
|
||||||
|
IPEndPoint configEntryEp = new IPEndPoint(addr, configEntryUri.Port);
|
||||||
|
endPoints.Add(configEntryEp);
|
||||||
|
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Added blacklist exception [{0}]", configEntryEp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines if an url is in a list of networks and endpoints.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <param name="url">IP address</param>
|
||||||
|
/// <param name="port"></param>
|
||||||
|
/// <param name="networks">Networks.</param>
|
||||||
|
/// <param name="endPoints">End points.</param>
|
||||||
|
/// <param name="filterName">Filter name.</param>
|
||||||
|
private static bool IsInNetwork(
|
||||||
|
IPAddress addr, int port, List<IPNetwork> networks, List<IPEndPoint> endPoints, string filterName)
|
||||||
|
{
|
||||||
|
foreach (IPNetwork ipn in networks)
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat(
|
||||||
|
// "[OUTBOUND URL FILTER]: Checking [{0}] against network [{1}]", addr, ipn);
|
||||||
|
|
||||||
|
if (IPNetwork.Contains(ipn, addr))
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat(
|
||||||
|
// "[OUTBOUND URL FILTER]: Found [{0}] in network [{1}]", addr, ipn);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found address [{0}]", addr);
|
||||||
|
|
||||||
|
foreach (IPEndPoint ep in endPoints)
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat(
|
||||||
|
// "[OUTBOUND URL FILTER]: Checking [{0}:{1}] against endpoint [{2}]",
|
||||||
|
// addr, port, ep);
|
||||||
|
|
||||||
|
if (addr.Equals(ep.Address) && port == ep.Port)
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat(
|
||||||
|
// "[OUTBOUND URL FILTER]: Found [{0}:{1}] in endpoint [{2}]", addr, port, ep);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Did not find [{0}:{1}] in list", addr, port);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks whether the given url is allowed by the filter.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool CheckAllowed(Uri url)
|
||||||
|
{
|
||||||
|
bool allowed = true;
|
||||||
|
|
||||||
|
// Check that we are permitted to make calls to this endpoint.
|
||||||
|
bool foundIpv4Address = false;
|
||||||
|
|
||||||
|
IPAddress[] addresses = Dns.GetHostAddresses(url.Host);
|
||||||
|
|
||||||
|
foreach (IPAddress addr in addresses)
|
||||||
|
{
|
||||||
|
if (addr.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found address [{0}]", addr);
|
||||||
|
|
||||||
|
foundIpv4Address = true;
|
||||||
|
|
||||||
|
// Check blacklist
|
||||||
|
if (OutboundUrlFilter.IsInNetwork(addr, url.Port, m_blacklistNetworks, m_blacklistEndPoints, Name))
|
||||||
|
{
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found [{0}] in blacklist for {1}", url, Name);
|
||||||
|
|
||||||
|
// Check blacklist exceptions
|
||||||
|
allowed
|
||||||
|
= OutboundUrlFilter.IsInNetwork(
|
||||||
|
addr, url.Port, m_blacklistExceptionNetworks, m_blacklistExceptionEndPoints, Name);
|
||||||
|
|
||||||
|
// if (allowed)
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Found [{0}] in whitelist for {1}", url, Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Found at least one address in a blacklist and not a blacklist exception
|
||||||
|
if (!allowed)
|
||||||
|
return false;
|
||||||
|
// else
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: URL [{0}] not in blacklist for {1}", url, Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not know how to handle IPv6 securely yet.
|
||||||
|
if (!foundIpv4Address)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// m_log.DebugFormat("[OUTBOUND URL FILTER]: Allowing request [{0}]", url);
|
||||||
|
|
||||||
|
return allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ using log4net;
|
||||||
using Nini.Config;
|
using Nini.Config;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenSim.Framework;
|
using OpenSim.Framework;
|
||||||
|
using OpenSim.Framework.Communications;
|
||||||
using OpenSim.Framework.Servers;
|
using OpenSim.Framework.Servers;
|
||||||
using OpenSim.Framework.Servers.HttpServer;
|
using OpenSim.Framework.Servers.HttpServer;
|
||||||
using OpenSim.Region.Framework.Interfaces;
|
using OpenSim.Region.Framework.Interfaces;
|
||||||
|
@ -94,10 +95,13 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "HttpRequestModule")]
|
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "HttpRequestModule")]
|
||||||
public class HttpRequestModule : ISharedRegionModule, IHttpRequestModule
|
public class HttpRequestModule : ISharedRegionModule, IHttpRequestModule
|
||||||
{
|
{
|
||||||
|
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
|
|
||||||
private object HttpListLock = new object();
|
private object HttpListLock = new object();
|
||||||
private int httpTimeout = 30000;
|
private int httpTimeout = 30000;
|
||||||
private string m_name = "HttpScriptRequests";
|
private string m_name = "HttpScriptRequests";
|
||||||
|
|
||||||
|
private OutboundUrlFilter m_outboundUrlFilter;
|
||||||
private string m_proxyurl = "";
|
private string m_proxyurl = "";
|
||||||
private string m_proxyexcepts = "";
|
private string m_proxyexcepts = "";
|
||||||
|
|
||||||
|
@ -156,7 +160,9 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
return UUID.Zero;
|
return UUID.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID StartHttpRequest(uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body)
|
public UUID StartHttpRequest(
|
||||||
|
uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body,
|
||||||
|
out HttpInitialRequestStatus status)
|
||||||
{
|
{
|
||||||
UUID reqID = UUID.Random();
|
UUID reqID = UUID.Random();
|
||||||
HttpRequestClass htc = new HttpRequestClass();
|
HttpRequestClass htc = new HttpRequestClass();
|
||||||
|
@ -233,6 +239,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
htc.RequestModule = this;
|
||||||
htc.LocalID = localID;
|
htc.LocalID = localID;
|
||||||
htc.ItemID = itemID;
|
htc.ItemID = itemID;
|
||||||
htc.Url = url;
|
htc.Url = url;
|
||||||
|
@ -243,14 +250,43 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
htc.proxyurl = m_proxyurl;
|
htc.proxyurl = m_proxyurl;
|
||||||
htc.proxyexcepts = m_proxyexcepts;
|
htc.proxyexcepts = m_proxyexcepts;
|
||||||
|
|
||||||
lock (HttpListLock)
|
// Same number as default HttpWebRequest.MaximumAutomaticRedirections
|
||||||
|
htc.MaxRedirects = 50;
|
||||||
|
|
||||||
|
if (StartHttpRequest(htc))
|
||||||
{
|
{
|
||||||
m_pendingRequests.Add(reqID, htc);
|
status = HttpInitialRequestStatus.OK;
|
||||||
|
return htc.ReqID;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
status = HttpInitialRequestStatus.DISALLOWED_BY_FILTER;
|
||||||
|
return UUID.Zero;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
htc.Process();
|
/// <summary>
|
||||||
|
/// Would a caller to this module be allowed to make a request to the given URL?
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public bool CheckAllowed(Uri url)
|
||||||
|
{
|
||||||
|
return m_outboundUrlFilter.CheckAllowed(url);
|
||||||
|
}
|
||||||
|
|
||||||
return reqID;
|
public bool StartHttpRequest(HttpRequestClass req)
|
||||||
|
{
|
||||||
|
if (!CheckAllowed(new Uri(req.Url)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
lock (HttpListLock)
|
||||||
|
{
|
||||||
|
m_pendingRequests.Add(req.ReqID, req);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Process();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StopHttpRequestsForScript(UUID id)
|
public void StopHttpRequestsForScript(UUID id)
|
||||||
|
@ -326,6 +362,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
m_proxyurl = config.Configs["Startup"].GetString("HttpProxy");
|
m_proxyurl = config.Configs["Startup"].GetString("HttpProxy");
|
||||||
m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions");
|
m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions");
|
||||||
|
|
||||||
|
m_outboundUrlFilter = new OutboundUrlFilter("Script HTTP request module", config);
|
||||||
|
|
||||||
m_pendingRequests = new Dictionary<UUID, HttpRequestClass>();
|
m_pendingRequests = new Dictionary<UUID, HttpRequestClass>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +406,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
public class HttpRequestClass: IServiceRequest
|
public class HttpRequestClass : IServiceRequest
|
||||||
{
|
{
|
||||||
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||||
|
|
||||||
|
@ -380,6 +418,12 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
// public const int HTTP_VERBOSE_THROTTLE = 4;
|
// public const int HTTP_VERBOSE_THROTTLE = 4;
|
||||||
// public const int HTTP_CUSTOM_HEADER = 5;
|
// public const int HTTP_CUSTOM_HEADER = 5;
|
||||||
// public const int HTTP_PRAGMA_NO_CACHE = 6;
|
// public const int HTTP_PRAGMA_NO_CACHE = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Module that made this request.
|
||||||
|
/// </summary>
|
||||||
|
public HttpRequestModule RequestModule { get; set; }
|
||||||
|
|
||||||
private bool _finished;
|
private bool _finished;
|
||||||
public bool Finished
|
public bool Finished
|
||||||
{
|
{
|
||||||
|
@ -412,6 +456,17 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
public DateTime Next;
|
public DateTime Next;
|
||||||
public string proxyurl;
|
public string proxyurl;
|
||||||
public string proxyexcepts;
|
public string proxyexcepts;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Number of HTTP redirects that this request has been through.
|
||||||
|
/// </summary>
|
||||||
|
public int Redirects { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maximum number of HTTP redirects allowed for this request.
|
||||||
|
/// </summary>
|
||||||
|
public int MaxRedirects { get; set; }
|
||||||
|
|
||||||
public string OutboundBody;
|
public string OutboundBody;
|
||||||
private UUID _reqID;
|
private UUID _reqID;
|
||||||
public UUID ReqID
|
public UUID ReqID
|
||||||
|
@ -419,7 +474,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
get { return _reqID; }
|
get { return _reqID; }
|
||||||
set { _reqID = value; }
|
set { _reqID = value; }
|
||||||
}
|
}
|
||||||
public WebRequest Request;
|
public HttpWebRequest Request;
|
||||||
public string ResponseBody;
|
public string ResponseBody;
|
||||||
public List<string> ResponseMetadata;
|
public List<string> ResponseMetadata;
|
||||||
public Dictionary<string, string> ResponseHeaders;
|
public Dictionary<string, string> ResponseHeaders;
|
||||||
|
@ -435,7 +490,8 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Request = WebRequest.Create(Url);
|
Request = (HttpWebRequest)WebRequest.Create(Url);
|
||||||
|
Request.AllowAutoRedirect = false;
|
||||||
Request.Method = HttpMethod;
|
Request.Method = HttpMethod;
|
||||||
Request.ContentType = HttpMIMEType;
|
Request.ContentType = HttpMIMEType;
|
||||||
|
|
||||||
|
@ -566,8 +622,53 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||||
if (response != null)
|
if (response != null)
|
||||||
response.Close();
|
response.Close();
|
||||||
|
|
||||||
|
// We need to resubmit
|
||||||
|
if (
|
||||||
|
(Status == (int)HttpStatusCode.MovedPermanently
|
||||||
|
|| Status == (int)HttpStatusCode.Found
|
||||||
|
|| Status == (int)HttpStatusCode.SeeOther
|
||||||
|
|| Status == (int)HttpStatusCode.TemporaryRedirect))
|
||||||
|
{
|
||||||
|
if (Redirects >= MaxRedirects)
|
||||||
|
{
|
||||||
|
Status = (int)OSHttpStatusCode.ClientErrorJoker;
|
||||||
|
ResponseBody = "Number of redirects exceeded max redirects";
|
||||||
_finished = true;
|
_finished = true;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string location = response.Headers["Location"];
|
||||||
|
|
||||||
|
if (location == null)
|
||||||
|
{
|
||||||
|
Status = (int)OSHttpStatusCode.ClientErrorJoker;
|
||||||
|
ResponseBody = "HTTP redirect code but no location header";
|
||||||
|
_finished = true;
|
||||||
|
}
|
||||||
|
else if (!RequestModule.CheckAllowed(new Uri(location)))
|
||||||
|
{
|
||||||
|
Status = (int)OSHttpStatusCode.ClientErrorJoker;
|
||||||
|
ResponseBody = "URL from HTTP redirect blocked: " + location;
|
||||||
|
_finished = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Status = 0;
|
||||||
|
Url = response.Headers["Location"];
|
||||||
|
Redirects++;
|
||||||
|
ResponseBody = null;
|
||||||
|
|
||||||
|
// m_log.DebugFormat("Redirecting to [{0}]", Url);
|
||||||
|
|
||||||
|
Process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_finished = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TimeoutCallback(object state, bool timedOut)
|
private void TimeoutCallback(object state, bool timedOut)
|
||||||
|
|
|
@ -147,11 +147,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest.Tests
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test what happens when we get a 404 response from a call.
|
/// Test what happens when we get a 404 response from a call.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Test]
|
// [Test]
|
||||||
public void Test404Response()
|
public void Test404Response()
|
||||||
{
|
{
|
||||||
TestHelpers.InMethod();
|
TestHelpers.InMethod();
|
||||||
// TestHelpers.EnableLogging();
|
TestHelpers.EnableLogging();
|
||||||
|
|
||||||
if (!Util.IsPlatformMono)
|
if (!Util.IsPlatformMono)
|
||||||
Assert.Ignore("Ignoring test since can only currently run on Mono");
|
Assert.Ignore("Ignoring test since can only currently run on Mono");
|
||||||
|
|
|
@ -32,6 +32,7 @@ using System.Net;
|
||||||
using Nini.Config;
|
using Nini.Config;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
using OpenMetaverse.Imaging;
|
using OpenMetaverse.Imaging;
|
||||||
|
using OpenSim.Framework.Communications;
|
||||||
using OpenSim.Region.CoreModules.Scripting.DynamicTexture;
|
using OpenSim.Region.CoreModules.Scripting.DynamicTexture;
|
||||||
using OpenSim.Region.Framework.Interfaces;
|
using OpenSim.Region.Framework.Interfaces;
|
||||||
using OpenSim.Region.Framework.Scenes;
|
using OpenSim.Region.Framework.Scenes;
|
||||||
|
@ -50,6 +51,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
private Scene m_scene;
|
private Scene m_scene;
|
||||||
private IDynamicTextureManager m_textureManager;
|
private IDynamicTextureManager m_textureManager;
|
||||||
|
|
||||||
|
private OutboundUrlFilter m_outboundUrlFilter;
|
||||||
private string m_proxyurl = "";
|
private string m_proxyurl = "";
|
||||||
private string m_proxyexcepts = "";
|
private string m_proxyexcepts = "";
|
||||||
|
|
||||||
|
@ -88,8 +90,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
|
|
||||||
public bool AsyncConvertUrl(UUID id, string url, string extraParams)
|
public bool AsyncConvertUrl(UUID id, string url, string extraParams)
|
||||||
{
|
{
|
||||||
MakeHttpRequest(url, id);
|
return MakeHttpRequest(url, id);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AsyncConvertData(UUID id, string bodyData, string extraParams)
|
public bool AsyncConvertData(UUID id, string bodyData, string extraParams)
|
||||||
|
@ -110,6 +111,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
|
|
||||||
public void Initialise(IConfigSource config)
|
public void Initialise(IConfigSource config)
|
||||||
{
|
{
|
||||||
|
m_outboundUrlFilter = new OutboundUrlFilter("Script dynamic texture image module", config);
|
||||||
m_proxyurl = config.Configs["Startup"].GetString("HttpProxy");
|
m_proxyurl = config.Configs["Startup"].GetString("HttpProxy");
|
||||||
m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions");
|
m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions");
|
||||||
}
|
}
|
||||||
|
@ -157,9 +159,13 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void MakeHttpRequest(string url, UUID requestID)
|
private bool MakeHttpRequest(string url, UUID requestID)
|
||||||
{
|
{
|
||||||
WebRequest request = HttpWebRequest.Create(url);
|
if (!m_outboundUrlFilter.CheckAllowed(new Uri(url)))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
|
||||||
|
request.AllowAutoRedirect = false;
|
||||||
|
|
||||||
if (m_proxyurl != null && m_proxyurl.Length > 0)
|
if (m_proxyurl != null && m_proxyurl.Length > 0)
|
||||||
{
|
{
|
||||||
|
@ -174,12 +180,14 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestState state = new RequestState((HttpWebRequest) request, requestID);
|
RequestState state = new RequestState(request, requestID);
|
||||||
// IAsyncResult result = request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state);
|
// IAsyncResult result = request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state);
|
||||||
request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state);
|
request.BeginGetResponse(new AsyncCallback(HttpRequestReturn), state);
|
||||||
|
|
||||||
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
|
TimeSpan t = (DateTime.UtcNow - new DateTime(1970, 1, 1));
|
||||||
state.TimeOfRequest = (int) t.TotalSeconds;
|
state.TimeOfRequest = (int) t.TotalSeconds;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HttpRequestReturn(IAsyncResult result)
|
private void HttpRequestReturn(IAsyncResult result)
|
||||||
|
@ -195,10 +203,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
Stream stream = null;
|
Stream stream = null;
|
||||||
byte[] imageJ2000 = new byte[0];
|
byte[] imageJ2000 = new byte[0];
|
||||||
Size newSize = new Size(0, 0);
|
Size newSize = new Size(0, 0);
|
||||||
|
HttpWebResponse response = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(result);
|
response = (HttpWebResponse)request.EndGetResponse(result);
|
||||||
if (response != null && response.StatusCode == HttpStatusCode.OK)
|
if (response != null && response.StatusCode == HttpStatusCode.OK)
|
||||||
{
|
{
|
||||||
stream = response.GetResponseStream();
|
stream = response.GetResponseStream();
|
||||||
|
@ -262,11 +271,23 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (stream != null)
|
if (stream != null)
|
||||||
{
|
|
||||||
stream.Close();
|
stream.Close();
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (response != null)
|
||||||
|
response.Close();
|
||||||
|
|
||||||
|
if (
|
||||||
|
response.StatusCode == HttpStatusCode.MovedPermanently
|
||||||
|
|| response.StatusCode == HttpStatusCode.Found
|
||||||
|
|| response.StatusCode == HttpStatusCode.SeeOther
|
||||||
|
|| response.StatusCode == HttpStatusCode.TemporaryRedirect)
|
||||||
|
{
|
||||||
|
string redirectedUrl = response.Headers["Location"];
|
||||||
|
|
||||||
|
MakeHttpRequest(redirectedUrl, state.RequestID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}",
|
m_log.DebugFormat("[LOADIMAGEURLMODULE]: Returning {0} bytes of image data for request {1}",
|
||||||
imageJ2000.Length, state.RequestID);
|
imageJ2000.Length, state.RequestID);
|
||||||
|
|
||||||
|
@ -275,6 +296,8 @@ namespace OpenSim.Region.CoreModules.Scripting.LoadImageURL
|
||||||
new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture(
|
new OpenSim.Region.CoreModules.Scripting.DynamicTexture.DynamicTexture(
|
||||||
request.RequestUri, null, imageJ2000, newSize, false));
|
request.RequestUri, null, imageJ2000, newSize, false));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#region Nested type: RequestState
|
#region Nested type: RequestState
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using OpenMetaverse;
|
using OpenMetaverse;
|
||||||
|
|
||||||
|
@ -41,10 +42,44 @@ namespace OpenSim.Region.Framework.Interfaces
|
||||||
HTTP_PRAGMA_NO_CACHE = 6
|
HTTP_PRAGMA_NO_CACHE = 6
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The initial status of the request before it is placed on the wire.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The request may still fail later on, in which case the normal HTTP status is set.
|
||||||
|
/// </remarks>
|
||||||
|
[Flags]
|
||||||
|
public enum HttpInitialRequestStatus
|
||||||
|
{
|
||||||
|
OK = 1,
|
||||||
|
DISALLOWED_BY_FILTER = 2
|
||||||
|
}
|
||||||
|
|
||||||
public interface IHttpRequestModule
|
public interface IHttpRequestModule
|
||||||
{
|
{
|
||||||
UUID MakeHttpRequest(string url, string parameters, string body);
|
UUID MakeHttpRequest(string url, string parameters, string body);
|
||||||
UUID StartHttpRequest(uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body);
|
|
||||||
|
/// <summary>
|
||||||
|
/// Starts the http request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is carried out asynchronously unless it fails initial checks. Results are fetched by the script engine
|
||||||
|
/// HTTP requests module to be distributed back to scripts via a script event.
|
||||||
|
/// </remarks>
|
||||||
|
/// <returns>The ID of the request. If the requested could not be performed then this is UUID.Zero</returns>
|
||||||
|
/// <param name="localID">Local ID of the object containing the script making the request.</param>
|
||||||
|
/// <param name="itemID">Item ID of the script making the request.</param>
|
||||||
|
/// <param name="url">Url to request.</param>
|
||||||
|
/// <param name="parameters">LSL parameters for the request.</param>
|
||||||
|
/// <param name="headers">Extra headers for the request.</param>
|
||||||
|
/// <param name="body">Body of the request.</param>
|
||||||
|
/// <param name="status">
|
||||||
|
/// Initial status of the request. If OK then the request is actually made to the URL. Subsequent status is
|
||||||
|
/// then returned via IServiceRequest when the response is asynchronously fetched.
|
||||||
|
/// </param>
|
||||||
|
UUID StartHttpRequest(
|
||||||
|
uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body,
|
||||||
|
out HttpInitialRequestStatus status);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Stop and remove all http requests for the given script.
|
/// Stop and remove all http requests for the given script.
|
||||||
|
|
|
@ -10762,8 +10762,12 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HttpInitialRequestStatus status;
|
||||||
UUID reqID
|
UUID reqID
|
||||||
= httpScriptMod.StartHttpRequest(m_host.LocalId, m_item.ItemID, url, param, httpHeaders, body);
|
= httpScriptMod.StartHttpRequest(m_host.LocalId, m_item.ItemID, url, param, httpHeaders, body, out status);
|
||||||
|
|
||||||
|
if (status == HttpInitialRequestStatus.DISALLOWED_BY_FILTER)
|
||||||
|
Error("llHttpRequest", string.Format("Request to {0} disallowed by filter", url));
|
||||||
|
|
||||||
if (reqID != UUID.Zero)
|
if (reqID != UUID.Zero)
|
||||||
return reqID.ToString();
|
return reqID.ToString();
|
||||||
|
@ -11212,6 +11216,29 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
|
||||||
return item.ItemID;
|
return item.ItemID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reports the script error in the viewer's Script Warning/Error dialog and shouts it on the debug channel.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="command">The name of the command that generated the error.</param>
|
||||||
|
/// <param name="message">The error message to report to the user.</param>
|
||||||
|
internal void Error(string command, string message)
|
||||||
|
{
|
||||||
|
string text = command + ": " + message;
|
||||||
|
if (text.Length > 1023)
|
||||||
|
{
|
||||||
|
text = text.Substring(0, 1023);
|
||||||
|
}
|
||||||
|
|
||||||
|
World.SimChat(Utils.StringToBytes(text), ChatTypeEnum.DebugChannel, ScriptBaseClass.DEBUG_CHANNEL,
|
||||||
|
m_host.ParentGroup.RootPart.AbsolutePosition, m_host.Name, m_host.UUID, false);
|
||||||
|
|
||||||
|
IWorldComm wComm = m_ScriptEngine.World.RequestModuleInterface<IWorldComm>();
|
||||||
|
if (wComm != null)
|
||||||
|
{
|
||||||
|
wComm.DeliverMessage(ChatTypeEnum.Shout, ScriptBaseClass.DEBUG_CHANNEL, m_host.Name, m_host.UUID, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void ShoutError(string msg)
|
internal void ShoutError(string msg)
|
||||||
{
|
{
|
||||||
llShout(ScriptBaseClass.DEBUG_CHANNEL, msg);
|
llShout(ScriptBaseClass.DEBUG_CHANNEL, msg);
|
||||||
|
|
|
@ -435,6 +435,32 @@
|
||||||
;; the region ports use UDP.
|
;; the region ports use UDP.
|
||||||
; http_listener_port = 9000
|
; http_listener_port = 9000
|
||||||
|
|
||||||
|
; By default, OpenSimulator does not allow scripts to make HTTP calls to addresses on the simulator's LAN.
|
||||||
|
; See the OutboundDisallowForUserScripts parameter in OpenSimDefaults.ini for more information on this filter.
|
||||||
|
; If you need to allow scripts to make some LAN calls use the OutboundDisallowForUserScriptsExcept parameter below.
|
||||||
|
; We recommend that you do not override OutboundDisallowForUserScripts directly unless you are very sure about what you're doing.
|
||||||
|
;
|
||||||
|
; You can whitelist individual endpoints by IP or FQDN, e.g.
|
||||||
|
;
|
||||||
|
; OutboundDisallowForUserScriptsExcept = 192.168.1.3:8003
|
||||||
|
;
|
||||||
|
; You can specify multiple addresses by separating them with a bar. For example,
|
||||||
|
;
|
||||||
|
; OutboundDisallowForUserScriptsExcept = 192.168.1.3:8003|myinternalserver:8000
|
||||||
|
;
|
||||||
|
; If an address if given without a port number then port 80 is assumed
|
||||||
|
;
|
||||||
|
; You can also specify a network range in CIDR notation to whitelist, e.g.
|
||||||
|
;
|
||||||
|
; OutboundDisallowForUserScriptsExcept = 192.168.1.0/24
|
||||||
|
;
|
||||||
|
; to whitelist all ports on addresses 192.168.1.0 to 192.168.1.255
|
||||||
|
; To specify an individual IP address use the /32 netmask
|
||||||
|
;
|
||||||
|
; OutboundDisallowForUserScriptsExcept = 192.168.1.2/32
|
||||||
|
;
|
||||||
|
; See http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation for more information on CIDR notation
|
||||||
|
|
||||||
;# {ExternalHostNameForLSL} {} {Hostname to use for HTTP-IN URLs. This should be reachable from the internet.} {}
|
;# {ExternalHostNameForLSL} {} {Hostname to use for HTTP-IN URLs. This should be reachable from the internet.} {}
|
||||||
;; Hostname to use in llRequestURL/llRequestSecureURL
|
;; Hostname to use in llRequestURL/llRequestSecureURL
|
||||||
;; if not defined - default machine name is being used
|
;; if not defined - default machine name is being used
|
||||||
|
|
|
@ -444,6 +444,26 @@
|
||||||
; (on Windows this mean NETBIOS name - useably only inside local network)
|
; (on Windows this mean NETBIOS name - useably only inside local network)
|
||||||
; ExternalHostNameForLSL=127.0.0.1
|
; ExternalHostNameForLSL=127.0.0.1
|
||||||
|
|
||||||
|
; Disallow the following address ranges for user scripting calls (e.g. llHttpRequest())
|
||||||
|
; This is based on http://en.wikipedia.org/wiki/Reserved_IP_addresses
|
||||||
|
; This stops users making HTTP calls to machines in the simulator's local network.
|
||||||
|
; If you need to allow some LAN calls we recommend you use OutboundDisallowForUserScriptsExcept documented in OpenSim.ini.example
|
||||||
|
; If you override OutboundDisallowForUserScripts directly you need to be very careful.
|
||||||
|
;
|
||||||
|
; Network ranges are specified in CIDR notation (http://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing#CIDR_notation) with multiple entries separated by |
|
||||||
|
; To specify an individual IP address use the /32 netmask (e.g. 192.168.1.3/32)
|
||||||
|
; You can also specify individual <addr>:<port> endpoints (e.g. 192.168.1.3:8003)
|
||||||
|
; If an address if given without a port number then port 80 is assumed.
|
||||||
|
OutboundDisallowForUserScripts = 0.0.0.0/8|10.0.0.0/8|100.64.0.0/10|127.0.0.0/8|169.254.0.0/16|172.16.0.0/12|192.0.0.0/24|192.0.2.0/24|192.88.99.0/24|192.168.0.0/16|198.18.0.0/15|198.51.100.0/24|203.0.113.0/24|224.0.0.0/4|240.0.0.0/4|255.255.255.255/32
|
||||||
|
;
|
||||||
|
; You can also prevent all user script outgoing calls with the following override in OpenSim.ini
|
||||||
|
;
|
||||||
|
; OutboundDisallowForUserScripts = 0.0.0.0/0
|
||||||
|
;
|
||||||
|
; You can also disable the blacklist entirely with an empty entry
|
||||||
|
;
|
||||||
|
; OutboundDisallowForUserScripts = ""
|
||||||
|
|
||||||
; What is reported as the "X-Secondlife-Shard"
|
; What is reported as the "X-Secondlife-Shard"
|
||||||
; Defaults to the user server url if not set
|
; Defaults to the user server url if not set
|
||||||
; The old default is "OpenSim", set here for compatibility
|
; The old default is "OpenSim", set here for compatibility
|
||||||
|
|
|
@ -641,6 +641,7 @@
|
||||||
|
|
||||||
<ReferencePath>../../../bin/</ReferencePath>
|
<ReferencePath>../../../bin/</ReferencePath>
|
||||||
<Reference name="System"/>
|
<Reference name="System"/>
|
||||||
|
<Reference name="System.Core"/>
|
||||||
<Reference name="System.Xml"/>
|
<Reference name="System.Xml"/>
|
||||||
<Reference name="System.Web"/>
|
<Reference name="System.Web"/>
|
||||||
<Reference name="OpenSim.Data"/>
|
<Reference name="OpenSim.Data"/>
|
||||||
|
@ -655,6 +656,7 @@
|
||||||
<Reference name="OpenMetaverse.StructuredData" path="../../../bin/"/>
|
<Reference name="OpenMetaverse.StructuredData" path="../../../bin/"/>
|
||||||
<!-- FIXME: The OpenMetaverse.dll reference can be dropped when the TransferRequestPacket reference is removed from the code -->
|
<!-- FIXME: The OpenMetaverse.dll reference can be dropped when the TransferRequestPacket reference is removed from the code -->
|
||||||
<Reference name="OpenMetaverse" path="../../../bin/"/>
|
<Reference name="OpenMetaverse" path="../../../bin/"/>
|
||||||
|
<Reference name="LukeSkywalker.IPNetwork" path="../../../bin/"/>
|
||||||
<Reference name="Nini" path="../../../bin/"/>
|
<Reference name="Nini" path="../../../bin/"/>
|
||||||
<Reference name="XMLRPC" path="../../../bin/"/>
|
<Reference name="XMLRPC" path="../../../bin/"/>
|
||||||
<Reference name="log4net" path="../../../bin/"/>
|
<Reference name="log4net" path="../../../bin/"/>
|
||||||
|
|
Loading…
Reference in New Issue