From add42f5e9b2d58a4978d4aab4440d5cc2d255a12 Mon Sep 17 00:00:00 2001 From: Dr Scofield Date: Mon, 3 Nov 2008 17:17:57 +0000 Subject: completing move to refactored multi-channel capable IRCBridgeModule --- .../Modules/Avatar/Chat/ChannelState.cs | 12 +- .../Modules/Avatar/Chat/IRCBridgeModule.cs | 280 +++++++ .../Modules/Avatar/Chat/IRCConnector.cs | 843 +++++++++++++++++++++ .../Modules/Avatar/Chat/XIRCBridgeModule.cs | 280 ------- .../Modules/Avatar/Chat/XIRCConnector.cs | 843 --------------------- 5 files changed, 1129 insertions(+), 1129 deletions(-) create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs create mode 100644 OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs delete mode 100644 OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs delete mode 100644 OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs index f9089cb..4fc744b 100644 --- a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs @@ -97,7 +97,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat // IRC connector reference - internal XIRCConnector irc = null; + internal IRCConnector irc = null; internal int idn = _idk_++; @@ -256,7 +256,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat // values that, while independent of the IRC connetion, do still distinguish // this region's behavior. - foreach (ChannelState xcs in XIRCBridgeModule.m_channels) + foreach (ChannelState xcs in IRCBridgeModule.m_channels) { if (cs.IsAPerfectMatchFor(xcs)) { @@ -279,9 +279,9 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat m_log.DebugFormat("[IRC-Channel-{0}] New channel required", cs.idn); - if ((cs.irc = new XIRCConnector(cs)) != null) + if ((cs.irc = new IRCConnector(cs)) != null) { - XIRCBridgeModule.m_channels.Add(cs); + IRCBridgeModule.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, @@ -554,7 +554,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat // contains information that is not differentiating from an // IRC point-of-view. - public static void OSChat(XIRCConnector p_irc, OSChatMessage c, bool cmsg) + public static void OSChat(IRCConnector p_irc, OSChatMessage c, bool cmsg) { // m_log.DebugFormat("[IRC-OSCHAT] from {0}:{1}", p_irc.Server, p_irc.IrcChannel); @@ -568,7 +568,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat // Note that this code is responsible for completing some of the // settings for the inbound OSChatMessage - foreach (ChannelState cs in XIRCBridgeModule.m_channels) + foreach (ChannelState cs in IRCBridgeModule.m_channels) { if ( p_irc == cs.irc) { diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs new file mode 100644 index 0000000..e413290 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs @@ -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 IRCBridgeModule : 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 m_regions = new List(); + internal static List m_channels = new List(); + + internal static string password = String.Empty; + + #region IRegionModule Members + + public string Name + { + get { return "IRCBridgeModule"; } + } + + 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["IRC"]) == null) + { + m_log.InfoFormat("[IRC-Bridge] module not configured"); + return; + } + + if (!m_config.GetBoolean("enabled", false)) + { + m_log.InfoFormat("[IRC-Bridge] module disabled in configuration"); + return; + } + } + catch (Exception e) + { + m_log.ErrorFormat("[IRC-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("irc_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("[IRC-Bridge] Connecting region {0}", scene.RegionInfo.RegionName); + m_regions.Add(new RegionState(scene, m_config)); + } + catch (Exception e) + { + m_log.WarnFormat("[IRC-Bridge] Region {0} not connected to IRC : {1}", scene.RegionInfo.RegionName, e.Message); + m_log.Debug(e); + } + } + else + { + m_log.WarnFormat("[IRC-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("[IRC-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("[IRC-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("[IRC-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("[IRC-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("[IRC-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("[IRC-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("[IRC-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 ((string)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("[IRC-Bridge] XML RPC Admin request failed : {0}", e.Message); + + responseData["success"] = "false"; + responseData["error"] = e.Message; + + } + finally + { + response.Value = responseData; + } + + m_log.Debug("[IRC-Bridge]: XML RPC Admin Exit"); + + return response; + + } + + } + +} diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs new file mode 100644 index 0000000..b9422f3 --- /dev/null +++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCConnector.cs @@ -0,0 +1,843 @@ +/* + * 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 IRCConnector + { + + #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 m_connectors = new List(); + + // Watchdog state + + private static System.Timers.Timer m_watchdog = null; + + static IRCConnector() + { + 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 IRCConnector(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); + + } + + ~IRCConnector() + { + 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 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 Regex RE = new Regex(@":(?[\w-]*)!(?\S*) PRIVMSG (?\S+) :(?.*)", + RegexOptions.Multiline); + + private Dictionary 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 result = null; + 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(); + 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 (IRCConnector 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 + + } +} diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs deleted file mode 100644 index 9bd7946..0000000 --- a/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs +++ /dev/null @@ -1,280 +0,0 @@ -/* - * 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 m_regions = new List(); - internal static List m_channels = new List(); - - 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["IRC"]) == 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 ((string)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; - - } - - } - -} diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs deleted file mode 100644 index f6a07b8..0000000 --- a/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs +++ /dev/null @@ -1,843 +0,0 @@ -/* - * 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 m_connectors = new List(); - - // 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 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 Regex RE = new Regex(@":(?[\w-]*)!(?\S*) PRIVMSG (?\S+) :(?.*)", - RegexOptions.Multiline); - - private Dictionary 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 result = null; - 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(); - 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 - - } -} -- cgit v1.1