XIRCBrigeModule is transient, will merge it with IRCBridgeModule:
extends/refactors IRCBridgeModule to support channel-per-region (if
desired).
0.6.0-stable
Dr Scofield 2008-10-30 15:31:44 +00:00
parent 3a6037b421
commit b222d11b12
8 changed files with 2196 additions and 12 deletions

View File

@ -0,0 +1,570 @@
/*
* 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 OpenSim 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.Reflection;
using System.Text.RegularExpressions;
using log4net;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Region.Environment.Interfaces;
using OpenSim.Region.Environment.Scenes;
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
{
// An instance of this class exists for each unique combination of
// IRC chat interface characteristics, as determined by the supplied
// configuration file.
internal class ChannelState
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static Regex arg = new Regex(@"\[[^\[\]]*\]");
private static int _idk_ = 0;
// These are the IRC Connector configurable parameters with hard-wired
// default values (retained for compatability).
internal string Server = null;
internal string IrcChannel = null;
internal string BaseNickname = "OSimBot";
internal uint Port = 6667;
internal string User = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
internal bool ClientReporting = true;
internal bool RelayPrivateChannels = false;
internal int RelayChannel = 1;
// Connector agnostic parameters. These values are NOT shared with the
// connector and do not differentiate at an IRC level
internal string PrivateMessageFormat = "PRIVMSG {0} :<{2}> {1} {3}";
internal string NoticeMessageFormat = "PRIVMSG {0} :<{2}> {3}";
internal int RelayChannelOut = -1;
internal bool RandomizeNickname = true;
internal string AccessPassword = "badkitty";
internal bool CommandsEnabled = false;
internal int CommandChannel = -1;
internal int ConnectDelay = 10;
internal int PingDelay = 15;
internal string DefaultZone = "Sim";
// IRC connector reference
internal XIRCConnector irc = null;
internal int idn = _idk_++;
// List of regions dependent upon this connection
internal List<RegionState> clientregions = new List<RegionState>();
// Needed by OpenChannel
internal ChannelState()
{
}
// This constructor is used by the Update* methods. A copy of the
// existing channel state is created, and distinguishing characteristics
// are copied across.
internal ChannelState(ChannelState model)
{
Server = model.Server;
IrcChannel = model.IrcChannel;
Port = model.Port;
BaseNickname = model.BaseNickname;
RandomizeNickname = model.RandomizeNickname;
User = model.User;
CommandsEnabled = model.CommandsEnabled;
CommandChannel = model.CommandChannel;
RelayPrivateChannels = model.RelayPrivateChannels;
RelayChannelOut = model.RelayChannelOut;
RelayChannel = model.RelayChannel;
PrivateMessageFormat = model.PrivateMessageFormat;
NoticeMessageFormat = model.NoticeMessageFormat;
ClientReporting = model.ClientReporting;
AccessPassword = model.AccessPassword;
DefaultZone = model.DefaultZone;
ConnectDelay = model.ConnectDelay;
PingDelay = model.PingDelay;
}
// Read the configuration file, performing variable substitution and any
// necessary aliasing. See accompanying documentation for how this works.
// If you don't need variables, then this works exactly as before.
// If either channel or server are not specified, the request fails.
internal static void OpenChannel(RegionState rs, IConfig config)
{
// Create a new instance of a channel. This may not actually
// get used if an equivalent channel already exists.
ChannelState cs = new ChannelState();
// Read in the configuration file and filter everything for variable
// subsititution.
m_log.DebugFormat("[IRC-Channel-{0}] Initial request by Region {1} to connect to IRC", cs.idn, rs.Region);
cs.Server = Substitute(rs, config.GetString("server", null));
m_log.DebugFormat("[IRC-Channel-{0}] Server : <{1}>", cs.idn, cs.Server);
cs.IrcChannel = Substitute(rs, config.GetString("channel", null));
m_log.DebugFormat("[IRC-Channel-{0}] IrcChannel : <{1}>", cs.idn, cs.IrcChannel);
cs.Port = Convert.ToUInt32(Substitute(rs, config.GetString("port", Convert.ToString(cs.Port))));
m_log.DebugFormat("[IRC-Channel-{0}] Port : <{1}>", cs.idn, cs.Port);
cs.BaseNickname = Substitute(rs, config.GetString("nick", cs.BaseNickname));
m_log.DebugFormat("[IRC-Channel-{0}] BaseNickname : <{1}>", cs.idn, cs.BaseNickname);
cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("randomize_nick", Convert.ToString(cs.RandomizeNickname))));
m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("nicknum", Convert.ToString(cs.RandomizeNickname))));
m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
cs.User = Substitute(rs, config.GetString("username", cs.User));
m_log.DebugFormat("[IRC-Channel-{0}] User : <{1}>", cs.idn, cs.User);
cs.CommandsEnabled = Convert.ToBoolean(Substitute(rs, config.GetString("commands_enabled", Convert.ToString(cs.CommandsEnabled))));
m_log.DebugFormat("[IRC-Channel-{0}] CommandsEnabled : <{1}>", cs.idn, cs.CommandsEnabled);
cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("commandchannel", Convert.ToString(cs.CommandChannel))));
m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("command_channel", Convert.ToString(cs.CommandChannel))));
m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("relay_private_channels", Convert.ToString(cs.RelayPrivateChannels))));
m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("useworldcomm", Convert.ToString(cs.RelayPrivateChannels))));
m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
cs.RelayChannelOut = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_out", Convert.ToString(cs.RelayChannelOut))));
m_log.DebugFormat("[IRC-Channel-{0}] RelayChannelOut : <{1}>", cs.idn, cs.RelayChannelOut);
cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_in", Convert.ToString(cs.RelayChannel))));
m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("inchannel", Convert.ToString(cs.RelayChannel))));
m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
cs.PrivateMessageFormat = Substitute(rs, config.GetString("msgformat", cs.PrivateMessageFormat));
m_log.DebugFormat("[IRC-Channel-{0}] PrivateMessageFormat : <{1}>", cs.idn, cs.PrivateMessageFormat);
cs.NoticeMessageFormat = Substitute(rs, config.GetString("noticeformat", cs.NoticeMessageFormat));
m_log.DebugFormat("[IRC-Channel-{0}] NoticeMessageFormat : <{1}>", cs.idn, cs.NoticeMessageFormat);
cs.ClientReporting = Convert.ToInt32(Substitute(rs, config.GetString("verbosity", cs.ClientReporting?"1":"0"))) > 0;
m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
cs.ClientReporting = Convert.ToBoolean(Substitute(rs, config.GetString("report_clients", Convert.ToString(cs.ClientReporting))));
m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
cs.AccessPassword = Substitute(rs, config.GetString("access_password", cs.AccessPassword));
m_log.DebugFormat("[IRC-Channel-{0}] AccessPassword : <{1}>", cs.idn, cs.AccessPassword);
cs.DefaultZone = Substitute(rs, config.GetString("fallback_region", cs.DefaultZone));
m_log.DebugFormat("[IRC-Channel-{0}] DefaultZone : <{1}>", cs.idn, cs.DefaultZone);
cs.ConnectDelay = Convert.ToInt32(Substitute(rs, config.GetString("connect_delay", Convert.ToString(cs.ConnectDelay))));
m_log.DebugFormat("[IRC-Channel-{0}] ConnectDelay : <{1}>", cs.idn, cs.ConnectDelay);
cs.PingDelay = Convert.ToInt32(Substitute(rs, config.GetString("ping_delay", Convert.ToString(cs.PingDelay))));
m_log.DebugFormat("[IRC-Channel-{0}] PingDelay : <{1}>", cs.idn, cs.PingDelay);
// Fail if fundamental information is still missing
if (cs.Server == null || cs.IrcChannel == null || cs.BaseNickname == null || cs.User == null)
throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}", cs.idn, rs.Region));
m_log.InfoFormat("[IRC-Channel-{0}] Configuration for Region {1} is valid", cs.idn, rs.Region);
m_log.InfoFormat("[IRC-Channel-{0}] Server = {1}", cs.idn, cs.Server);
m_log.InfoFormat("[IRC-Channel-{0}] Channel = {1}", cs.idn, cs.IrcChannel);
m_log.InfoFormat("[IRC-Channel-{0}] Port = {1}", cs.idn, cs.Port);
m_log.InfoFormat("[IRC-Channel-{0}] Nickname = {1}", cs.idn, cs.BaseNickname);
m_log.InfoFormat("[IRC-Channel-{0}] User = {1}", cs.idn, cs.User);
// Set the channel state for this region
rs.cs = Integrate(rs, cs);
}
// An initialized channel state instance is passed in. If an identical
// channel state instance already exists, then the existing instance
// is used to replace the supplied value.
// If the instance matches with respect to IRC, then the underlying
// IRCConnector is assigned to the supplied channel state and the
// updated value is returned.
// If there is no match, then the supplied instance is completed by
// creating and assigning an instance of an IRC connector.
private static ChannelState Integrate(RegionState rs, ChannelState p_cs)
{
ChannelState cs = p_cs;
// Check to see if we have an existing server/channel setup that can be used
// In the absence of variable substitution this will always resolve to the
// same ChannelState instance, and the table will only contains a single
// entry, so the performance considerations for the existing behavior are
// zero. Only the IRC connector is shared, the ChannelState still contains
// values that, while independent of the IRC connetion, do still distinguish
// this region's behavior.
foreach (ChannelState xcs in XIRCBridgeModule.m_channels)
{
if (cs.IsAPerfectMatchFor(xcs))
{
m_log.DebugFormat("[IRC-Channel-{0}] Channel state matched", cs.idn);
cs = xcs;
break;
}
if (cs.IsAConnectionMatchFor(xcs))
{
m_log.DebugFormat("[IRC-Channel-{0}] Channel matched", cs.idn);
cs.irc = xcs.irc;
break;
}
}
// No entry was found, so this is going to be a new entry.
if (cs.irc == null)
{
m_log.DebugFormat("[IRC-Channel-{0}] New channel required", cs.idn);
if ((cs.irc = new XIRCConnector(cs)) != null)
{
XIRCBridgeModule.m_channels.Add(cs);
m_log.InfoFormat("[IRC-Channel-{0}] New channel initialized for {1}, nick: {2}, commands {3}, private channels {4}",
cs.idn, rs.Region, cs.DefaultZone,
cs.CommandsEnabled ? "enabled" : "not enabled",
cs.RelayPrivateChannels ? "relayed" : "not relayed");
}
else
{
string txt = String.Format("[IRC-Channel-{0}] Region {1} failed to connect to channel {2} on server {3}:{4}",
cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
m_log.Error(txt);
throw new Exception(txt);
}
}
else
{
m_log.InfoFormat("[IRC-Channel-{0}] Region {1} reusing existing connection to channel {2} on server {3}:{4}",
cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
}
m_log.InfoFormat("[IRC-Channel-{0}] Region {1} connected to channel {2} on server {3}:{4}",
cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
// We're finally ready to commit ourselves
return cs;
}
// These routines allow differentiating changes to
// the underlying channel state. If necessary, a
// new channel state will be created.
internal ChannelState UpdateServer(RegionState rs, string server)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.Server = server;
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
internal ChannelState UpdatePort(RegionState rs, string port)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.Port = Convert.ToUInt32(port);
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
internal ChannelState UpdateChannel(RegionState rs, string channel)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.IrcChannel = channel;
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
internal ChannelState UpdateNickname(RegionState rs, string nickname)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.BaseNickname = nickname;
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
internal ChannelState UpdateClientReporting(RegionState rs, string cr)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.ClientReporting = Convert.ToBoolean(cr);
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
internal ChannelState UpdateRelayIn(RegionState rs, string channel)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.RelayChannel = Convert.ToInt32(channel);
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
internal ChannelState UpdateRelayOut(RegionState rs, string channel)
{
RemoveRegion(rs);
ChannelState cs = new ChannelState(this);
cs.RelayChannelOut = Convert.ToInt32(channel);
cs = Integrate(rs, cs);
cs.AddRegion(rs);
return cs;
}
// Determine whether or not this is a 'new' channel. Only those
// attributes that uniquely distinguish an IRC connection should
// be included here (and only those attributes should really be
// in the ChannelState structure)
private bool IsAConnectionMatchFor(ChannelState cs)
{
return (
Server == cs.Server &&
IrcChannel == cs.IrcChannel &&
Port == cs.Port &&
BaseNickname == cs.BaseNickname &&
User == cs.User
);
}
// This level of obsessive matching allows us to produce
// a minimal overhead int he case of a server which does
// need to differentiate IRC at a region level.
private bool IsAPerfectMatchFor(ChannelState cs)
{
return ( IsAConnectionMatchFor(cs) &&
RelayChannelOut == cs.RelayChannelOut &&
PrivateMessageFormat == cs.PrivateMessageFormat &&
NoticeMessageFormat == cs.NoticeMessageFormat &&
RandomizeNickname == cs.RandomizeNickname &&
AccessPassword == cs.AccessPassword &&
CommandsEnabled == cs.CommandsEnabled &&
CommandChannel == cs.CommandChannel &&
DefaultZone == cs.DefaultZone &&
RelayPrivateChannels == cs.RelayPrivateChannels &&
RelayChannel == cs.RelayChannel &&
ClientReporting == cs.ClientReporting
);
}
// This function implements the variable substitution mechanism
// for the configuration values. Each string read from the
// configuration file is scanned for '[...]' enclosures. Each
// one that is found is replaced by either a runtime variable
// (%xxx) or an existing configuration key. When no further
// substitution is possible, the remaining string is returned
// to the caller. This allows for arbitrarily nested
// enclosures.
private static string Substitute(RegionState rs, string instr)
{
string result = instr;
if (result == null || result.Length == 0)
return result;
// Repeatedly scan the string until all possible
// substitutions have been performed.
while (arg.IsMatch(result))
{
string vvar = arg.Match(result).ToString();
string var = vvar.Substring(1,vvar.Length-2).Trim();
switch (var.ToLower())
{
case "%region" :
result = result.Replace(vvar, rs.Region);
break;
case "%host" :
result = result.Replace(vvar, rs.Host);
break;
case "%master1" :
result = result.Replace(vvar, rs.MA1);
break;
case "%master2" :
result = result.Replace(vvar, rs.MA2);
break;
case "%locx" :
result = result.Replace(vvar, rs.LocX);
break;
case "%locy" :
result = result.Replace(vvar, rs.LocY);
break;
case "%k" :
result = result.Replace(vvar, rs.IDK);
break;
default :
result = result.Replace(vvar, rs.config.GetString(var,var));
break;
}
}
return result;
}
public void Close()
{
m_log.InfoFormat("[IRC-Channel-{0}] Closing channel <{1} to server <{2}:{3}>",
idn, IrcChannel, Server, Port);
m_log.InfoFormat("[IRC-Channel-{0}] There are {1} active clients",
idn, clientregions.Count);
irc.Close();
}
public void Open()
{
m_log.InfoFormat("[IRC-Channel-{0}] Opening channel <{1} to server <{2}:{3}>",
idn, IrcChannel, Server, Port);
irc.Open();
}
// These are called by each region that attaches to this channel. The call affects
// only the relationship of the region with the channel. Not the channel to IRC
// relationship (unless it is closed and we want it open).
public void Open(RegionState rs)
{
AddRegion(rs);
Open();
}
public void Close(RegionState rs)
{
RemoveRegion(rs);
}
// Add a client region to this channel if it is not already known
public void AddRegion(RegionState rs)
{
m_log.InfoFormat("[IRC-Channel-{0}] Adding region {1} to channel <{2} to server <{3}:{4}>",
idn, rs.Region, IrcChannel, Server, Port);
if (!clientregions.Contains(rs))
{
clientregions.Add(rs);
lock (irc) irc.depends++;
}
}
// Remove a client region from the channel. If this is the last
// region, then clean up the channel. The connector will clean itself
// up if it finds itself about to be GC'd.
public void RemoveRegion(RegionState rs)
{
m_log.InfoFormat("[IRC-Channel-{0}] Removing region {1} from channel <{2} to server <{3}:{4}>",
idn, rs.Region, IrcChannel, Server, Port);
if (clientregions.Contains(rs))
{
clientregions.Remove(rs);
lock (irc) irc.depends--;
}
}
// This function is lifted from the IRCConnector because it
// contains information that is not differentiating from an
// IRC point-of-view.
public static void OSChat(XIRCConnector p_irc, OSChatMessage c, bool cmsg)
{
// m_log.DebugFormat("[IRC-OSCHAT] from {0}:{1}", p_irc.Server, p_irc.IrcChannel);
try
{
// Scan through the set of unique channel configuration for those
// that belong to this connector. And then forward the message to
// all regions known to those channels.
// Note that this code is responsible for completing some of the
// settings for the inbound OSChatMessage
foreach (ChannelState cs in XIRCBridgeModule.m_channels)
{
if ( p_irc == cs.irc)
{
// This non-IRC differentiator moved to here
if (cmsg && !cs.ClientReporting)
continue;
// This non-IRC differentiator moved to here
c.Channel = (cs.RelayPrivateChannels ? cs.RelayChannel : 0);
foreach (RegionState region in cs.clientregions)
{
region.OSChat(cs.irc, c);
}
}
}
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-OSCHAT]: BroadcastSim Exception: {0}", ex.Message);
m_log.Debug(ex);
}
}
}
}

View File

@ -242,7 +242,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat
break; break;
} }
} }
catch(Exception ex) catch (Exception ex)
{ {
m_log.DebugFormat("[IRC] error processing in-world command channel input: {0}", ex); m_log.DebugFormat("[IRC] error processing in-world command channel input: {0}", ex);
} }

View File

@ -0,0 +1,435 @@
/*
* 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 OpenSim 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.Reflection;
using log4net;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Region.Environment.Interfaces;
using OpenSim.Region.Environment.Scenes;
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
{
// An instance of this class exists for every active region
internal class RegionState
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly OpenMetaverse.Vector3 CenterOfRegion = new OpenMetaverse.Vector3(128, 128, 20);
private const int DEBUG_CHANNEL = 2147483647;
private static int _idk_ = 0;
// Runtime variables; these values are assigned when the
// IrcState is created and remain constant thereafter.
internal string Region = String.Empty;
internal string Host = String.Empty;
internal string LocX = String.Empty;
internal string LocY = String.Empty;
internal string MA1 = String.Empty;
internal string MA2 = String.Empty;
internal string IDK = String.Empty;
// System values - used only be the IRC classes themselves
internal ChannelState cs = null; // associated IRC configuration
internal Scene scene = null; // associated scene
internal IConfig config = null; // configuration file reference
internal bool enabled = true;
// This list is used to keep track of who is here, and by
// implication, who is not.
internal List<IClientAPI> clients = new List<IClientAPI>();
// Setup runtime variable values
public RegionState(Scene p_scene, IConfig p_config)
{
scene = p_scene;
config = p_config;
Region = scene.RegionInfo.RegionName;
Host = scene.RegionInfo.ExternalHostName;
LocX = Convert.ToString(scene.RegionInfo.RegionLocX);
LocY = Convert.ToString(scene.RegionInfo.RegionLocY);
MA1 = scene.RegionInfo.MasterAvatarFirstName;
MA2 = scene.RegionInfo.MasterAvatarLastName;
IDK = Convert.ToString(_idk_++);
// OpenChannel conditionally establishes a connection to the
// IRC server. The request will either succeed, or it will
// throw an exception.
ChannelState.OpenChannel(this, config);
// Connect channel to world events
scene.EventManager.OnChatFromWorld += OnSimChat;
scene.EventManager.OnChatFromClient += OnSimChat;
scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
m_log.InfoFormat("[IRC-Region {0}] Initialization complete", Region);
}
// Auto cleanup when abandoned
~RegionState()
{
if (cs != null)
cs.RemoveRegion(this);
}
// Called by PostInitialize after all regions have been created
public void Open()
{
cs.Open(this);
enabled = true;
}
// Called by IRCBridgeModule.Close immediately prior to unload
public void Close()
{
enabled = false;
cs.Close(this);
}
// The agent has disconnected, cleanup associated resources
private void OnClientLoggedOut(IClientAPI client)
{
try
{
if (clients.Contains(client))
{
if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
{
m_log.InfoFormat("[IRC-Region {0}]: {1} has left", Region, client.Name);
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", client.Name));
}
client.OnLogout -= OnClientLoggedOut;
client.OnConnectionClosed -= OnClientLoggedOut;
clients.Remove(client);
}
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Region {0}]: ClientLoggedOut exception: {1}", Region, ex.Message);
m_log.Debug(ex);
}
}
// This event indicates that the agent has left the building. We should treat that the same
// as if the agent has logged out (we don't want cross-region noise - or do we?)
private void OnMakeChildAgent(ScenePresence presence)
{
IClientAPI client = presence.ControllingClient;
try
{
if (clients.Contains(client))
{
if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
{
string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
m_log.DebugFormat("[IRC-Region {0}] {1} has left", Region, clientName);
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", clientName));
}
client.OnLogout -= OnClientLoggedOut;
client.OnConnectionClosed -= OnClientLoggedOut;
clients.Remove(client);
}
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Region {0}]: MakeChildAgent exception: {1}", Region, ex.Message);
m_log.Debug(ex);
}
}
// An agent has entered the region (from another region). Add the client to the locally
// known clients list
private void OnMakeRootAgent(ScenePresence presence)
{
IClientAPI client = presence.ControllingClient;
try
{
if (!clients.Contains(client))
{
client.OnLogout += OnClientLoggedOut;
client.OnConnectionClosed += OnClientLoggedOut;
clients.Add(client);
if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
{
string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
m_log.DebugFormat("[IRC-Region {0}] {1} has arrived", Region, clientName);
cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has arrived", clientName));
}
}
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Region {0}]: MakeRootAgent exception: {1}", Region, ex.Message);
m_log.Debug(ex);
}
}
// This handler detects chat events int he virtual world.
public void OnSimChat(Object sender, OSChatMessage msg)
{
// early return if this comes from the IRC forwarder
if (cs.irc.Equals(sender)) return;
// early return if nothing to forward
if (msg.Message.Length == 0) return;
// check for commands coming from avatars or in-world
// object (if commands are enabled)
if (cs.CommandsEnabled && msg.Channel == cs.CommandChannel)
{
m_log.DebugFormat("[IRC-Region {0}] command on channel {1}: {2}", Region, msg.Channel, msg.Message);
string[] messages = msg.Message.Split(' ');
string command = messages[0].ToLower();
try
{
switch (command)
{
// These commands potentially require a change in the
// underlying ChannelState.
case "server":
cs.Close(this);
cs = cs.UpdateServer(this, messages[1]);
cs.Open(this);
break;
case "port":
cs.Close(this);
cs = cs.UpdatePort(this, messages[1]);
cs.Open(this);
break;
case "channel":
cs.Close(this);
cs = cs.UpdateChannel(this, messages[1]);
cs.Open(this);
break;
case "nick":
cs.Close(this);
cs = cs.UpdateNickname(this, messages[1]);
cs.Open(this);
break;
// These may also (but are less likely) to require a
// change in ChannelState.
case "client-reporting":
cs = cs.UpdateClientReporting(this, messages[1]);
break;
case "in-channel":
cs = cs.UpdateRelayIn(this, messages[1]);
break;
case "out-channel":
cs = cs.UpdateRelayOut(this, messages[1]);
break;
// These are all taken to be temporary changes in state
// so the underlying connector remains intact. But note
// that with regions sharing a connector, there could
// be interference.
case "close":
enabled = false;
cs.Close(this);
break;
case "connect":
enabled = true;
cs.Open(this);
break;
case "reconnect":
enabled = true;
cs.Close(this);
cs.Open(this);
break;
// This one is harmless as far as we can judge from here.
// If it is not, then the complaints will eventually make
// that evident.
default:
m_log.DebugFormat("[IRC-Region {0}] Forwarding unrecognized command to IRC : {1}",
Region, msg.Message);
cs.irc.Send(msg.Message);
break;
}
}
catch (Exception ex)
{
m_log.WarnFormat("[IRC-Region {0}] error processing in-world command channel input: {1}",
Region, ex.Message);
m_log.Debug(ex);
}
return;
}
// The command channel remains enabled, even if we have otherwise disabled the IRC
// interface.
if (!enabled)
return;
// drop all messages coming in on a private channel,
// except if we are relaying private channels, in which
// case we drop if the private channel is not the
// configured m_relayChannelOut
if (cs.RelayPrivateChannels)
{
if (msg.Channel != 0 && msg.Channel != DEBUG_CHANNEL && msg.Channel != cs.RelayChannelOut)
{
m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
return;
}
}
else if (msg.Channel != 0 && msg.Channel != DEBUG_CHANNEL)
{
m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
return;
}
ScenePresence avatar = null;
string fromName = msg.From;
if (msg.Sender != null)
{
avatar = scene.GetScenePresence(msg.Sender.AgentId);
if (avatar != null) fromName = avatar.Name;
}
if (!cs.irc.Connected)
{
m_log.WarnFormat("[IRC-Region {0}] IRCConnector not connected: dropping message from {1}", Region, fromName);
return;
}
m_log.DebugFormat("[IRC-Region {0}] heard on channel {1} : {2}", Region, msg.Channel, msg.Message);
if (null != avatar)
{
string txt = msg.Message;
if (txt.StartsWith("/me "))
txt = String.Format("{0} {1}", fromName, msg.Message.Substring(4));
cs.irc.PrivMsg(cs.PrivateMessageFormat, fromName, Region, txt);
}
else
{
//Message came from an object
char[] splits = { ',' };
string[] tokens = msg.Message.Split(splits,3); // This is certainly wrong
if (tokens.Length == 3)
{
if (tokens[0] == cs.AccessPassword) // This is my really simple check
{
m_log.DebugFormat("[IRC-Region {0}] message from object {1}, {2}", Region, tokens[0], tokens[1]);
cs.irc.PrivMsg(cs.PrivateMessageFormat, tokens[1], scene.RegionInfo.RegionName, tokens[2]);
}
else
{
m_log.WarnFormat("[IRC-Region {0}] prim security key mismatch <{1}> not <{2}>", Region, tokens[0], cs.AccessPassword);
}
}
}
}
// This method gives the region an opportunity to interfere with
// message delivery. For now we just enforce the enable/disable
// flag.
internal void OSChat(Object irc, OSChatMessage msg)
{
if (enabled)
{
// m_log.DebugFormat("[IRC-OSCHAT] Region {0} being sent message", region.Region);
msg.Scene = scene;
scene.EventManager.TriggerOnChatBroadcast(irc, msg);
}
}
// This supports any local message traffic that might be needed in
// support of command processing. At present there is none.
internal void LocalChat(string msg)
{
if (enabled)
{
OSChatMessage osm = new OSChatMessage();
osm.From = "IRC Agent";
osm.Message = msg;
osm.Type = ChatTypeEnum.Region;
osm.Position = CenterOfRegion;
osm.Sender = null;
osm.SenderUUID = OpenMetaverse.UUID.Zero; // Hmph! Still?
osm.Channel = 0;
OSChat(this, osm);
}
}
}
}

View File

@ -0,0 +1,280 @@
/*
* 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 OpenSim 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;
using System.Collections.Generic;
using System.Reflection;
using log4net;
using Nini.Config;
using Nwc.XmlRpc;
using OpenSim.Framework;
using OpenSim.Region.Environment.Interfaces;
using OpenSim.Region.Environment.Scenes;
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
{
public class XIRCBridgeModule : IRegionModule
{
private static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
internal static bool configured = false;
internal static bool enabled = false;
internal static IConfig m_config = null;
internal static List<RegionState> m_regions = new List<RegionState>();
internal static List<ChannelState> m_channels = new List<ChannelState>();
internal static string password = String.Empty;
#region IRegionModule Members
public string Name
{
get { return "XIRCBridgeModule"; }
}
public bool IsSharedModule
{
get { return true; }
}
public void Initialise(Scene scene, IConfigSource config)
{
// Do a once-only scan of the configuration file to make
// sure it's basically intact.
if (!configured)
{
configured = true;
try
{
if ((m_config = config.Configs["XIRC"]) == null)
{
m_log.InfoFormat("[XIRC-Bridge] module not configured");
return;
}
if (!m_config.GetBoolean("enabled", false))
{
m_log.InfoFormat("[XIRC-Bridge] module disabled in configuration");
return;
}
}
catch (Exception e)
{
m_log.ErrorFormat("[XIRC-Bridge] configuration failed : {0}", e.Message);
return;
}
enabled = true;
if(config.Configs["RemoteAdmin"] != null)
{
password = config.Configs["RemoteAdmin"].GetString("access_password", password);
scene.CommsManager.HttpServer.AddXmlRPCHandler("xirc_admin", XmlRpcAdminMethod, false);
}
}
// Iff the IRC bridge is enabled, then each new region may be
// connected to IRC. But it should NOT be obligatory (and it
// is not).
if (enabled)
{
try
{
m_log.InfoFormat("[XIRC-Bridge] Connecting region {0}", scene.RegionInfo.RegionName);
m_regions.Add(new RegionState(scene, m_config));
}
catch (Exception e)
{
m_log.WarnFormat("[XIRC-Bridge] Region {0} not connected to IRC : {1}", scene.RegionInfo.RegionName, e.Message);
m_log.Debug(e);
}
}
else
{
m_log.WarnFormat("[XIRC-Bridge] Not enabled. Connect for region {0} ignored", scene.RegionInfo.RegionName);
}
}
// Called after all region modules have been loaded.
// Iff the IRC bridge is enabled, then start all of the
// configured channels. The set of channels is a side
// effect of RegionState creation.
public void PostInitialise()
{
if (!enabled)
return;
foreach (RegionState region in m_regions)
{
m_log.InfoFormat("[XIRC-Bridge] Opening connection for {0}:{1} on IRC server {2}:{3}",
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel);
try
{
region.Open();
}
catch (Exception e)
{
m_log.ErrorFormat("[XIRC-Bridge] Open failed for {0}:{1} on IRC server {2}:{3} : {4}",
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel,
e.Message);
}
}
}
// Called immediately before the region module is unloaded. Close all
// associated channels.
public void Close()
{
if (!enabled)
return;
// Stop each of the region sessions
foreach (RegionState region in m_regions)
{
m_log.InfoFormat("[XIRC-Bridge] Closing connection for {0}:{1} on IRC server {2}:{3}",
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel);
try
{
region.Close();
}
catch (Exception e)
{
m_log.ErrorFormat("[XIRC-Bridge] Close failed for {0}:{1} on IRC server {2}:{3} : {4}",
region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel,
e.Message);
}
}
// Perform final cleanup of the channels (they now have no active clients)
foreach (ChannelState channel in m_channels)
{
m_log.InfoFormat("[XIRC-Bridge] Closing connection for {0} on IRC server {1}:{2}",
channel.BaseNickname, channel.Server, channel.IrcChannel);
try
{
channel.Close();
}
catch (Exception e)
{
m_log.ErrorFormat("[XIRC-Bridge] Close failed for {0} on IRC server {1}:{2} : {3}",
channel.BaseNickname, channel.Server, channel.IrcChannel,
e.Message);
}
}
}
#endregion
public XmlRpcResponse XmlRpcAdminMethod(XmlRpcRequest request)
{
m_log.Info("[XIRC-Bridge]: XML RPC Admin Entry");
XmlRpcResponse response = new XmlRpcResponse();
Hashtable responseData = new Hashtable();
try
{
Hashtable requestData = (Hashtable)request.Params[0];
bool found = false;
string region = String.Empty;
if(password != String.Empty)
{
if(!requestData.ContainsKey("password"))
throw new Exception("Invalid request");
if(requestData["password"] != password)
throw new Exception("Invalid request");
}
if(!requestData.ContainsKey("region"))
throw new Exception("No region name specified");
foreach(RegionState rs in m_regions)
{
if(rs.Region == region)
{
responseData["server"] = rs.cs.Server;
responseData["port"] = rs.cs.Port;
responseData["user"] = rs.cs.User;
responseData["channel"] = rs.cs.IrcChannel;
responseData["enabled"] = rs.cs.irc.Enabled;
responseData["connected"] = rs.cs.irc.Connected;
responseData["nickname"] = rs.cs.irc.Nick;
found = true;
break;
}
}
if(!found) throw new Exception(String.Format("Region <{0}> not found", region));
responseData["success"] = true;
}
catch (Exception e)
{
m_log.InfoFormat("[XIRC-Bridge] XML RPC Admin request failed : {0}", e.Message);
responseData["success"] = "false";
responseData["error"] = e.Message;
}
finally
{
response.Value = responseData;
}
m_log.Debug("[XIRC-Bridge]: XML RPC Admin Exit");
return response;
}
}
}

View File

@ -0,0 +1,842 @@
/*
* 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 OpenSim 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.Timers;
using System.Collections.Generic;
using System.IO;
using System.Net.Sockets;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading;
using OpenMetaverse;
using log4net;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Region.Environment.Interfaces;
using OpenSim.Region.Environment.Scenes;
namespace OpenSim.Region.Environment.Modules.Avatar.Chat
{
public class XIRCConnector
{
#region Global (static) state
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
// Local constants
private static readonly Vector3 CenterOfRegion = new Vector3(128, 128, 20);
private static readonly char[] CS_SPACE = { ' ' };
private const int WD_INTERVAL = 1000; // base watchdog interval
private static int PING_PERIOD = 15; // WD intervals per PING
private static int ICCD_PERIOD = 10; // WD intervals between Connects
private static int _idk_ = 0; // core connector identifier
private static int _pdk_ = 0; // ping interval counter
private static int _icc_ = 0; // IRC connect counter
// List of configured connectors
private static List<XIRCConnector> m_connectors = new List<XIRCConnector>();
// Watchdog state
private static System.Timers.Timer m_watchdog = null;
static XIRCConnector()
{
m_log.DebugFormat("[IRC-Connector]: Static initialization started");
m_watchdog = new System.Timers.Timer(WD_INTERVAL);
m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
m_watchdog.AutoReset = true;
m_watchdog.Start();
m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
}
#endregion
#region Instance state
// Connector identity
internal int idn = _idk_++;
// How many regions depend upon this connection
// This count is updated by the ChannelState object and reflects the sum
// of the region clients associated with the set of associated channel
// state instances. That's why it cannot be managed here.
internal int depends = 0;
// Working threads
private Thread m_listener = null;
private Object msyncConnect = new Object();
internal bool m_randomizeNick = true; // add random suffix
internal string m_baseNick = null; // base name for randomizing
internal string m_nick = null; // effective nickname
public string Nick // Public property
{
get { return m_nick; }
set { m_nick = value; }
}
private bool m_enabled = false; // connector enablement
public bool Enabled
{
get { return m_enabled; }
}
private bool m_connected = false; // connection status
public bool Connected
{
get { return m_connected; }
}
private string m_ircChannel; // associated channel id
public string IrcChannel
{
get { return m_ircChannel; }
set { m_ircChannel = value; }
}
private uint m_port = 6667; // session port
public uint Port
{
get { return m_port; }
set { m_port = value; }
}
private string m_server = null; // IRC server name
public string Server
{
get { return m_server; }
set { m_server = value; }
}
private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
public string User
{
get { return m_user; }
}
// Network interface
private TcpClient m_tcp;
private NetworkStream m_stream = null;
private StreamReader m_reader;
private StreamWriter m_writer;
// Channel characteristic info (if available)
internal string usermod = String.Empty;
internal string chanmod = String.Empty;
internal string version = String.Empty;
internal bool motd = false;
#endregion
#region connector instance management
internal XIRCConnector(ChannelState cs)
{
// Prepare network interface
m_tcp = null;
m_writer = null;
m_reader = null;
// Setup IRC session parameters
m_server = cs.Server;
m_baseNick = cs.BaseNickname;
m_randomizeNick = cs.RandomizeNickname;
m_ircChannel = cs.IrcChannel;
m_port = (uint) cs.Port;
m_user = cs.User;
if (m_watchdog == null)
{
// Non-differentiating
ICCD_PERIOD = cs.ConnectDelay;
PING_PERIOD = cs.PingDelay;
// Smaller values are not reasonable
if (ICCD_PERIOD < 5)
ICCD_PERIOD = 5;
if (PING_PERIOD < 5)
PING_PERIOD = 5;
_icc_ = ICCD_PERIOD; // get started right away!
}
// The last line of defense
if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
throw new Exception("Invalid connector configuration");
// Generate an initial nickname if randomizing is enabled
if (m_randomizeNick)
{
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
}
// Add the newly created connector to the known connectors list
m_connectors.Add(this);
m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
}
~XIRCConnector()
{
m_watchdog.Stop();
Close();
}
// Mark the connector as connectable. Harmless if already enabled.
public void Open()
{
if (!m_enabled)
{
m_connectors.Add(this);
m_enabled = true;
if (!Connected)
{
Connect();
}
}
}
// Only close the connector if the dependency count is zero.
public void Close()
{
m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
lock (msyncConnect)
{
if ((depends == 0) && Enabled)
{
m_enabled = false;
if (Connected)
{
m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
// Cleanup the IRC session
try
{
m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
m_nick, m_ircChannel, m_server));
m_writer.Flush();
}
catch (Exception) {}
m_connected = false;
try { m_writer.Close(); } catch (Exception) {}
try { m_reader.Close(); } catch (Exception) {}
try { m_stream.Close(); } catch (Exception) {}
try { m_tcp.Close(); } catch (Exception) {}
}
m_connectors.Remove(this);
}
}
m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
}
#endregion
#region session management
// Connect to the IRC server. A connector should always be connected, once enabled
public void Connect()
{
if (!m_enabled)
return;
// Delay until next WD cycle if this is too close to the last start attempt
while (_icc_ < ICCD_PERIOD)
return;
m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
lock (msyncConnect)
{
_icc_ = 0;
try
{
if (m_connected) return;
m_connected = true;
m_tcp = new TcpClient(m_server, (int)m_port);
m_stream = m_tcp.GetStream();
m_reader = new StreamReader(m_stream);
m_writer = new StreamWriter(m_stream);
m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
m_listener = new Thread(new ThreadStart(ListenerRun));
m_listener.Name = "IRCConnectorListenerThread";
m_listener.IsBackground = true;
m_listener.Start();
ThreadTracker.Add(m_listener);
// This is the message order recommended by RFC 2812
m_writer.WriteLine(String.Format("NICK {0}", m_nick));
m_writer.Flush();
m_writer.WriteLine(m_user);
m_writer.Flush();
m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
m_writer.Flush();
m_log.InfoFormat("[IRC-Connector-{0}]: {1} has joined {2}", idn, m_nick, m_ircChannel);
m_log.InfoFormat("[IRC-Connector-{0}] Connected", idn);
}
catch (Exception e)
{
m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
idn, m_nick, m_server, m_port, e.Message);
m_connected = false;
}
}
return;
}
// Reconnect is used to force a re-cycle of the IRC connection. Should generally
// be a transparent event
public void Reconnect()
{
m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
// Don't do this if a Connect is in progress...
lock (msyncConnect)
{
if (m_connected)
{
m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
// Mark as disconnected. This will allow the listener thread
// to exit if still in-flight.
// The listener thread is not aborted - it *might* actually be
// the thread that is running the Reconnect! Instead just close
// the socket and it will disappear of its own accord, once this
// processing is completed.
try { m_writer.Close(); } catch (Exception) {}
try { m_reader.Close(); } catch (Exception) {}
try { m_tcp.Close(); } catch (Exception) {}
m_connected = false;
}
}
Connect();
}
#endregion
#region Outbound (to-IRC) message handlers
public void PrivMsg(string pattern, string from, string region, string msg)
{
m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
String.Format(pattern, m_ircChannel, from, region, msg));
// One message to the IRC server
try
{
m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
m_writer.Flush();
m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
}
catch (IOException)
{
m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
Reconnect();
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
m_log.Debug(ex);
}
}
public void Send(string msg)
{
m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
try
{
m_writer.WriteLine(msg);
m_writer.Flush();
m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
}
catch (IOException)
{
m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
Reconnect();
}
catch (Exception ex)
{
m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
m_log.Debug(ex);
}
}
#endregion
public void ListenerRun()
{
string inputLine;
try
{
while (m_enabled && m_connected)
{
if ((inputLine = m_reader.ReadLine()) == null)
throw new Exception("Listener input socket closed");
// m_log.Info("[IRCConnector]: " + inputLine);
if (inputLine.Contains("PRIVMSG"))
{
Dictionary<string, string> data = ExtractMsg(inputLine);
// Any chat ???
if (data != null)
{
OSChatMessage c = new OSChatMessage();
c.Message = data["msg"];
c.Type = ChatTypeEnum.Region;
c.Position = CenterOfRegion;
c.From = data["nick"];
c.Sender = null;
c.SenderUUID = UUID.Zero;
// Is message "\001ACTION foo bar\001"?
// Then change to: "/me foo bar"
if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
ChannelState.OSChat(this, c, false);
}
}
else
{
ProcessIRCCommand(inputLine);
}
}
}
catch (Exception /*e*/)
{
// m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
// m_log.Debug(e);
}
if (m_enabled) Reconnect();
}
private Dictionary<string, string> ExtractMsg(string input)
{
//examines IRC commands and extracts any private messages
// which will then be reboadcast in the Sim
// m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
Dictionary<string, string> result = null;
string regex = @":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)";
Regex RE = new Regex(regex, RegexOptions.Multiline);
MatchCollection matches = RE.Matches(input);
// Get some direct matches $1 $4 is a
if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
{
// m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
// if (matches.Count > 0)
// {
// m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
// }
return null;
}
result = new Dictionary<string, string>();
result.Add("nick", matches[0].Groups[1].Value);
result.Add("user", matches[0].Groups[2].Value);
result.Add("channel", matches[0].Groups[3].Value);
result.Add("msg", matches[0].Groups[4].Value);
return result;
}
public void BroadcastSim(string sender, string format, params string[] args)
{
try
{
OSChatMessage c = new OSChatMessage();
c.From = sender;
c.Message = String.Format(format, args);
c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
c.Position = CenterOfRegion;
c.Sender = null;
c.SenderUUID = UUID.Zero;
ChannelState.OSChat(this, c, true);
}
catch (Exception ex) // IRC gate should not crash Sim
{
m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
}
}
#region IRC Command Handlers
public void ProcessIRCCommand(string command)
{
string[] commArgs;
string c_server = m_server;
string pfx = String.Empty;
string cmd = String.Empty;
string parms = String.Empty;
// ":" indicates that a prefix is present
// There are NEVER more than 17 real
// fields. A parameter that starts with
// ":" indicates that the remainder of the
// line is a single parameter value.
commArgs = command.Split(CS_SPACE,2);
if (commArgs[0].StartsWith(":"))
{
pfx = commArgs[0].Substring(1);
commArgs = commArgs[1].Split(CS_SPACE,2);
}
cmd = commArgs[0];
parms = commArgs[1];
// m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
switch (cmd)
{
// Messages 001-004 are always sent
// following signon.
case "001" : // Welcome ...
case "002" : // Server information
case "003" : // Welcome ...
break;
case "004" : // Server information
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
commArgs = parms.Split(CS_SPACE);
c_server = commArgs[1];
m_server = c_server;
version = commArgs[2];
usermod = commArgs[3];
chanmod = commArgs[4];
break;
case "005" : // Server information
break;
case "042" :
case "250" :
case "251" :
case "252" :
case "254" :
case "255" :
case "265" :
case "266" :
case "332" : // Subject
case "333" : // Subject owner (?)
case "353" : // Name list
case "366" : // End-of-Name list marker
case "372" : // MOTD body
case "375" : // MOTD start
m_log.InfoFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
break;
case "376" : // MOTD end
m_log.InfoFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
motd = true;
break;
case "451" : // Not registered
break;
case "433" : // Nickname in use
// Gen a new name
m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
m_log.ErrorFormat("[IRC-Connector-{0}]: IRC SERVER reports NicknameInUse, trying {1}", idn, m_nick);
// Retry
m_writer.WriteLine(String.Format("NICK {0}", m_nick));
m_writer.Flush();
m_writer.WriteLine(m_user);
m_writer.Flush();
m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
m_writer.Flush();
break;
case "NOTICE" :
m_log.WarnFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
break;
case "ERROR" :
m_log.ErrorFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
if (parms.Contains("reconnect too fast"))
ICCD_PERIOD++;
break;
case "PING" :
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
m_writer.WriteLine(String.Format("PONG {0}", parms));
m_writer.Flush();
break;
case "PONG" :
break;
case "JOIN":
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
eventIrcJoin(pfx, cmd, parms);
break;
case "PART":
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
eventIrcPart(pfx, cmd, parms);
break;
case "MODE":
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
eventIrcMode(pfx, cmd, parms);
break;
case "NICK":
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
eventIrcNickChange(pfx, cmd, parms);
break;
case "KICK":
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
eventIrcKick(pfx, cmd, parms);
break;
case "QUIT":
m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
eventIrcQuit(pfx, cmd, parms);
break;
default :
m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
break;
}
// m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
}
public void eventIrcJoin(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE,2);
string IrcUser = prefix.Split('!')[0];
string IrcChannel = args[0];
if (IrcChannel.StartsWith(":"))
IrcChannel = IrcChannel.Substring(1);
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCJoin {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
}
public void eventIrcPart(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE,2);
string IrcUser = prefix.Split('!')[0];
string IrcChannel = args[0];
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
}
public void eventIrcMode(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE,2);
string UserMode = args[1];
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
if (UserMode.Substring(0, 1) == ":")
{
UserMode = UserMode.Remove(0, 1);
}
}
public void eventIrcNickChange(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE,2);
string UserOldNick = prefix.Split('!')[0];
string UserNewNick = args[0].Remove(0, 1);
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
}
public void eventIrcKick(string prefix, string command, string parms)
{
string[] args = parms.Split(CS_SPACE,3);
string UserKicker = prefix.Split('!')[0];
string IrcChannel = args[0];
string UserKicked = args[1];
string KickMessage = args[2];
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
if (UserKicked == m_nick)
{
BroadcastSim(m_nick, "Hey, that was me!!!");
}
}
public void eventIrcQuit(string prefix, string command, string parms)
{
string IrcUser = prefix.Split('!')[0];
string QuitMessage = parms;
m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
}
#endregion
#region Connector Watch Dog
// A single watch dog monitors extant connectors and makes sure that they
// are re-connected as necessary. If a connector IS connected, then it is
// pinged, but only if a PING period has elapsed.
protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
{
// m_log.InfoFormat("[IRC-Watchdog] Status scan");
_pdk_ = (_pdk_+1)%PING_PERIOD; // cycle the ping trigger
_icc_++; // increment the inter-consecutive-connect-delay counter
foreach (XIRCConnector connector in m_connectors)
{
if (connector.Enabled)
{
if (!connector.Connected)
{
try
{
// m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
connector.Connect();
}
catch (Exception e)
{
m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
}
}
else
{
if (_pdk_ == 0)
{
try
{
connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
connector.m_writer.Flush();
}
catch (Exception /*e*/)
{
// m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
// m_log.Debug(e);
connector.Reconnect();
}
}
}
}
}
// m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
}
#endregion
}
}

View File

@ -428,16 +428,6 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Concierge
} }
} }
// private static void checkIntegerParams(XmlRpcRequest request, string[] param)
// {
// Hashtable requestData = (Hashtable) request.Params[0];
// foreach (string p in param)
// {
// if (!requestData.Contains(p))
// throw new Exception(String.Format("missing integer parameter {0}", p));
// }
// }
public XmlRpcResponse XmlRpcUpdateWelcomeMethod(XmlRpcRequest request) public XmlRpcResponse XmlRpcUpdateWelcomeMethod(XmlRpcRequest request)
{ {
_log.Info("[Concierge]: processing UpdateWelcome request"); _log.Info("[Concierge]: processing UpdateWelcome request");

View File

@ -36,6 +36,7 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Reflection; using System.Reflection;
using System.Xml; using System.Xml;
using System.Net;
using OpenMetaverse; using OpenMetaverse;
using log4net; using log4net;
@ -70,7 +71,8 @@ namespace OpenSim.Region.Environment.Modules.World.Archiver
{ {
TarArchiveReader archive TarArchiveReader archive
= new TarArchiveReader( = new TarArchiveReader(
new GZipStream(new FileStream(m_loadPath, FileMode.Open), CompressionMode.Decompress)); new GZipStream(GetStream(m_loadPath), CompressionMode.Decompress));
//AssetsDearchiver dearchiver = new AssetsDearchiver(m_scene.AssetCache); //AssetsDearchiver dearchiver = new AssetsDearchiver(m_scene.AssetCache);
List<string> serialisedSceneObjects = new List<string>(); List<string> serialisedSceneObjects = new List<string>();
@ -284,5 +286,65 @@ namespace OpenSim.Region.Environment.Modules.World.Archiver
return true; return true;
} }
/// <summary>
/// Resolve path to a working FileStream
/// </summary>
private Stream GetStream(string path)
{
try
{
if (File.Exists(path))
{
return new FileStream(path, FileMode.Open);
}
else
{
Uri uri = new Uri(path); // throw exception if not valid URI
if (uri.Scheme == "file")
{
return new FileStream(uri.AbsolutePath, FileMode.Open);
}
else
{
if (uri.Scheme != "http")
throw new Exception(String.Format("Unsupported URI scheme ({0})", path));
// OK, now we know we have an HTTP URI to work with
return URIFetch(uri);
}
}
}
catch (Exception e)
{
throw new Exception(String.Format("Unable to create file input stream for {0}: {1}", path, e));
}
}
private static Stream URIFetch(Uri uri)
{
HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri);
// request.Credentials = credentials;
request.ContentLength = 0;
WebResponse response = request.GetResponse();
Stream file = response.GetResponseStream();
if (response.ContentType != "application/x-oar")
throw new Exception(String.Format("{0} does not identify an OAR file", uri.ToString()));
if (response.ContentLength == 0)
throw new Exception(String.Format("{0} returned an empty file", uri.ToString()));
return new BufferedStream(file, (int) response.ContentLength);
}
} }
} }

View File

@ -448,6 +448,11 @@ msgformat = "PRIVMSG {0} : {3} - {1} of {2}"
;for <bot>:<message> - from <user> : ;for <bot>:<message> - from <user> :
;msgformat = "PRIVMSG {0} : {3} - from {1}" ;msgformat = "PRIVMSG {0} : {3} - from {1}"
; work-in-progress IRCBridge capabable of supporting multiple IRC channels
; [XIRC]
; enabled = false
; Uncomment the following for experiment in world versioning ; Uncomment the following for experiment in world versioning
; support. This is so experimental at this point that I'm not going ; support. This is so experimental at this point that I'm not going
; to explain it further, you'll need to read the source code ; to explain it further, you'll need to read the source code