aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/Environment/Modules/Avatar/Chat
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/Environment/Modules/Avatar/Chat')
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs570
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs2
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Chat/RegionState.cs435
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs280
-rw-r--r--OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs842
5 files changed, 2128 insertions, 1 deletions
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs
new file mode 100644
index 0000000..7a3eadf
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/ChannelState.cs
@@ -0,0 +1,570 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Reflection;
31using System.Text.RegularExpressions;
32using log4net;
33using Nini.Config;
34using OpenSim.Framework;
35using OpenSim.Region.Environment.Interfaces;
36using OpenSim.Region.Environment.Scenes;
37
38namespace OpenSim.Region.Environment.Modules.Avatar.Chat
39{
40
41 // An instance of this class exists for each unique combination of
42 // IRC chat interface characteristics, as determined by the supplied
43 // configuration file.
44
45 internal class ChannelState
46 {
47
48 private static readonly ILog m_log =
49 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
50
51 private static Regex arg = new Regex(@"\[[^\[\]]*\]");
52 private static int _idk_ = 0;
53
54 // These are the IRC Connector configurable parameters with hard-wired
55 // default values (retained for compatability).
56
57 internal string Server = null;
58 internal string IrcChannel = null;
59 internal string BaseNickname = "OSimBot";
60 internal uint Port = 6667;
61 internal string User = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
62
63 internal bool ClientReporting = true;
64 internal bool RelayPrivateChannels = false;
65 internal int RelayChannel = 1;
66
67 // Connector agnostic parameters. These values are NOT shared with the
68 // connector and do not differentiate at an IRC level
69
70 internal string PrivateMessageFormat = "PRIVMSG {0} :<{2}> {1} {3}";
71 internal string NoticeMessageFormat = "PRIVMSG {0} :<{2}> {3}";
72 internal int RelayChannelOut = -1;
73 internal bool RandomizeNickname = true;
74 internal string AccessPassword = "badkitty";
75 internal bool CommandsEnabled = false;
76 internal int CommandChannel = -1;
77 internal int ConnectDelay = 10;
78 internal int PingDelay = 15;
79 internal string DefaultZone = "Sim";
80
81 // IRC connector reference
82
83 internal XIRCConnector irc = null;
84
85 internal int idn = _idk_++;
86
87 // List of regions dependent upon this connection
88
89 internal List<RegionState> clientregions = new List<RegionState>();
90
91 // Needed by OpenChannel
92
93 internal ChannelState()
94 {
95 }
96
97 // This constructor is used by the Update* methods. A copy of the
98 // existing channel state is created, and distinguishing characteristics
99 // are copied across.
100
101 internal ChannelState(ChannelState model)
102 {
103 Server = model.Server;
104 IrcChannel = model.IrcChannel;
105 Port = model.Port;
106 BaseNickname = model.BaseNickname;
107 RandomizeNickname = model.RandomizeNickname;
108 User = model.User;
109 CommandsEnabled = model.CommandsEnabled;
110 CommandChannel = model.CommandChannel;
111 RelayPrivateChannels = model.RelayPrivateChannels;
112 RelayChannelOut = model.RelayChannelOut;
113 RelayChannel = model.RelayChannel;
114 PrivateMessageFormat = model.PrivateMessageFormat;
115 NoticeMessageFormat = model.NoticeMessageFormat;
116 ClientReporting = model.ClientReporting;
117 AccessPassword = model.AccessPassword;
118 DefaultZone = model.DefaultZone;
119 ConnectDelay = model.ConnectDelay;
120 PingDelay = model.PingDelay;
121 }
122
123 // Read the configuration file, performing variable substitution and any
124 // necessary aliasing. See accompanying documentation for how this works.
125 // If you don't need variables, then this works exactly as before.
126 // If either channel or server are not specified, the request fails.
127
128 internal static void OpenChannel(RegionState rs, IConfig config)
129 {
130
131 // Create a new instance of a channel. This may not actually
132 // get used if an equivalent channel already exists.
133
134 ChannelState cs = new ChannelState();
135
136 // Read in the configuration file and filter everything for variable
137 // subsititution.
138
139 m_log.DebugFormat("[IRC-Channel-{0}] Initial request by Region {1} to connect to IRC", cs.idn, rs.Region);
140
141 cs.Server = Substitute(rs, config.GetString("server", null));
142 m_log.DebugFormat("[IRC-Channel-{0}] Server : <{1}>", cs.idn, cs.Server);
143 cs.IrcChannel = Substitute(rs, config.GetString("channel", null));
144 m_log.DebugFormat("[IRC-Channel-{0}] IrcChannel : <{1}>", cs.idn, cs.IrcChannel);
145 cs.Port = Convert.ToUInt32(Substitute(rs, config.GetString("port", Convert.ToString(cs.Port))));
146 m_log.DebugFormat("[IRC-Channel-{0}] Port : <{1}>", cs.idn, cs.Port);
147 cs.BaseNickname = Substitute(rs, config.GetString("nick", cs.BaseNickname));
148 m_log.DebugFormat("[IRC-Channel-{0}] BaseNickname : <{1}>", cs.idn, cs.BaseNickname);
149 cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("randomize_nick", Convert.ToString(cs.RandomizeNickname))));
150 m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
151 cs.RandomizeNickname = Convert.ToBoolean(Substitute(rs, config.GetString("nicknum", Convert.ToString(cs.RandomizeNickname))));
152 m_log.DebugFormat("[IRC-Channel-{0}] RandomizeNickname : <{1}>", cs.idn, cs.RandomizeNickname);
153 cs.User = Substitute(rs, config.GetString("username", cs.User));
154 m_log.DebugFormat("[IRC-Channel-{0}] User : <{1}>", cs.idn, cs.User);
155 cs.CommandsEnabled = Convert.ToBoolean(Substitute(rs, config.GetString("commands_enabled", Convert.ToString(cs.CommandsEnabled))));
156 m_log.DebugFormat("[IRC-Channel-{0}] CommandsEnabled : <{1}>", cs.idn, cs.CommandsEnabled);
157 cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("commandchannel", Convert.ToString(cs.CommandChannel))));
158 m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
159 cs.CommandChannel = Convert.ToInt32(Substitute(rs, config.GetString("command_channel", Convert.ToString(cs.CommandChannel))));
160 m_log.DebugFormat("[IRC-Channel-{0}] CommandChannel : <{1}>", cs.idn, cs.CommandChannel);
161 cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("relay_private_channels", Convert.ToString(cs.RelayPrivateChannels))));
162 m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
163 cs.RelayPrivateChannels = Convert.ToBoolean(Substitute(rs, config.GetString("useworldcomm", Convert.ToString(cs.RelayPrivateChannels))));
164 m_log.DebugFormat("[IRC-Channel-{0}] RelayPrivateChannels : <{1}>", cs.idn, cs.RelayPrivateChannels);
165 cs.RelayChannelOut = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_out", Convert.ToString(cs.RelayChannelOut))));
166 m_log.DebugFormat("[IRC-Channel-{0}] RelayChannelOut : <{1}>", cs.idn, cs.RelayChannelOut);
167 cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("relay_private_channel_in", Convert.ToString(cs.RelayChannel))));
168 m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
169 cs.RelayChannel = Convert.ToInt32(Substitute(rs, config.GetString("inchannel", Convert.ToString(cs.RelayChannel))));
170 m_log.DebugFormat("[IRC-Channel-{0}] RelayChannel : <{1}>", cs.idn, cs.RelayChannel);
171 cs.PrivateMessageFormat = Substitute(rs, config.GetString("msgformat", cs.PrivateMessageFormat));
172 m_log.DebugFormat("[IRC-Channel-{0}] PrivateMessageFormat : <{1}>", cs.idn, cs.PrivateMessageFormat);
173 cs.NoticeMessageFormat = Substitute(rs, config.GetString("noticeformat", cs.NoticeMessageFormat));
174 m_log.DebugFormat("[IRC-Channel-{0}] NoticeMessageFormat : <{1}>", cs.idn, cs.NoticeMessageFormat);
175 cs.ClientReporting = Convert.ToInt32(Substitute(rs, config.GetString("verbosity", cs.ClientReporting?"1":"0"))) > 0;
176 m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
177 cs.ClientReporting = Convert.ToBoolean(Substitute(rs, config.GetString("report_clients", Convert.ToString(cs.ClientReporting))));
178 m_log.DebugFormat("[IRC-Channel-{0}] ClientReporting : <{1}>", cs.idn, cs.ClientReporting);
179 cs.AccessPassword = Substitute(rs, config.GetString("access_password", cs.AccessPassword));
180 m_log.DebugFormat("[IRC-Channel-{0}] AccessPassword : <{1}>", cs.idn, cs.AccessPassword);
181 cs.DefaultZone = Substitute(rs, config.GetString("fallback_region", cs.DefaultZone));
182 m_log.DebugFormat("[IRC-Channel-{0}] DefaultZone : <{1}>", cs.idn, cs.DefaultZone);
183 cs.ConnectDelay = Convert.ToInt32(Substitute(rs, config.GetString("connect_delay", Convert.ToString(cs.ConnectDelay))));
184 m_log.DebugFormat("[IRC-Channel-{0}] ConnectDelay : <{1}>", cs.idn, cs.ConnectDelay);
185 cs.PingDelay = Convert.ToInt32(Substitute(rs, config.GetString("ping_delay", Convert.ToString(cs.PingDelay))));
186 m_log.DebugFormat("[IRC-Channel-{0}] PingDelay : <{1}>", cs.idn, cs.PingDelay);
187
188 // Fail if fundamental information is still missing
189
190 if (cs.Server == null || cs.IrcChannel == null || cs.BaseNickname == null || cs.User == null)
191 throw new Exception(String.Format("[IRC-Channel-{0}] Invalid configuration for region {1}", cs.idn, rs.Region));
192
193 m_log.InfoFormat("[IRC-Channel-{0}] Configuration for Region {1} is valid", cs.idn, rs.Region);
194 m_log.InfoFormat("[IRC-Channel-{0}] Server = {1}", cs.idn, cs.Server);
195 m_log.InfoFormat("[IRC-Channel-{0}] Channel = {1}", cs.idn, cs.IrcChannel);
196 m_log.InfoFormat("[IRC-Channel-{0}] Port = {1}", cs.idn, cs.Port);
197 m_log.InfoFormat("[IRC-Channel-{0}] Nickname = {1}", cs.idn, cs.BaseNickname);
198 m_log.InfoFormat("[IRC-Channel-{0}] User = {1}", cs.idn, cs.User);
199
200 // Set the channel state for this region
201
202 rs.cs = Integrate(rs, cs);
203
204 }
205
206 // An initialized channel state instance is passed in. If an identical
207 // channel state instance already exists, then the existing instance
208 // is used to replace the supplied value.
209 // If the instance matches with respect to IRC, then the underlying
210 // IRCConnector is assigned to the supplied channel state and the
211 // updated value is returned.
212 // If there is no match, then the supplied instance is completed by
213 // creating and assigning an instance of an IRC connector.
214
215 private static ChannelState Integrate(RegionState rs, ChannelState p_cs)
216 {
217
218 ChannelState cs = p_cs;
219
220 // Check to see if we have an existing server/channel setup that can be used
221 // In the absence of variable substitution this will always resolve to the
222 // same ChannelState instance, and the table will only contains a single
223 // entry, so the performance considerations for the existing behavior are
224 // zero. Only the IRC connector is shared, the ChannelState still contains
225 // values that, while independent of the IRC connetion, do still distinguish
226 // this region's behavior.
227
228 foreach (ChannelState xcs in XIRCBridgeModule.m_channels)
229 {
230 if (cs.IsAPerfectMatchFor(xcs))
231 {
232 m_log.DebugFormat("[IRC-Channel-{0}] Channel state matched", cs.idn);
233 cs = xcs;
234 break;
235 }
236 if (cs.IsAConnectionMatchFor(xcs))
237 {
238 m_log.DebugFormat("[IRC-Channel-{0}] Channel matched", cs.idn);
239 cs.irc = xcs.irc;
240 break;
241 }
242 }
243
244 // No entry was found, so this is going to be a new entry.
245
246 if (cs.irc == null)
247 {
248
249 m_log.DebugFormat("[IRC-Channel-{0}] New channel required", cs.idn);
250
251 if ((cs.irc = new XIRCConnector(cs)) != null)
252 {
253 XIRCBridgeModule.m_channels.Add(cs);
254
255 m_log.InfoFormat("[IRC-Channel-{0}] New channel initialized for {1}, nick: {2}, commands {3}, private channels {4}",
256 cs.idn, rs.Region, cs.DefaultZone,
257 cs.CommandsEnabled ? "enabled" : "not enabled",
258 cs.RelayPrivateChannels ? "relayed" : "not relayed");
259 }
260 else
261 {
262 string txt = String.Format("[IRC-Channel-{0}] Region {1} failed to connect to channel {2} on server {3}:{4}",
263 cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
264 m_log.Error(txt);
265 throw new Exception(txt);
266 }
267 }
268 else
269 {
270 m_log.InfoFormat("[IRC-Channel-{0}] Region {1} reusing existing connection to channel {2} on server {3}:{4}",
271 cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
272 }
273
274 m_log.InfoFormat("[IRC-Channel-{0}] Region {1} connected to channel {2} on server {3}:{4}",
275 cs.idn, rs.Region, cs.IrcChannel, cs.Server, cs.Port);
276
277 // We're finally ready to commit ourselves
278
279 return cs;
280
281 }
282
283 // These routines allow differentiating changes to
284 // the underlying channel state. If necessary, a
285 // new channel state will be created.
286
287 internal ChannelState UpdateServer(RegionState rs, string server)
288 {
289 RemoveRegion(rs);
290 ChannelState cs = new ChannelState(this);
291 cs.Server = server;
292 cs = Integrate(rs, cs);
293 cs.AddRegion(rs);
294 return cs;
295 }
296
297 internal ChannelState UpdatePort(RegionState rs, string port)
298 {
299 RemoveRegion(rs);
300 ChannelState cs = new ChannelState(this);
301 cs.Port = Convert.ToUInt32(port);
302 cs = Integrate(rs, cs);
303 cs.AddRegion(rs);
304 return cs;
305 }
306
307 internal ChannelState UpdateChannel(RegionState rs, string channel)
308 {
309 RemoveRegion(rs);
310 ChannelState cs = new ChannelState(this);
311 cs.IrcChannel = channel;
312 cs = Integrate(rs, cs);
313 cs.AddRegion(rs);
314 return cs;
315 }
316
317 internal ChannelState UpdateNickname(RegionState rs, string nickname)
318 {
319 RemoveRegion(rs);
320 ChannelState cs = new ChannelState(this);
321 cs.BaseNickname = nickname;
322 cs = Integrate(rs, cs);
323 cs.AddRegion(rs);
324 return cs;
325 }
326
327 internal ChannelState UpdateClientReporting(RegionState rs, string cr)
328 {
329 RemoveRegion(rs);
330 ChannelState cs = new ChannelState(this);
331 cs.ClientReporting = Convert.ToBoolean(cr);
332 cs = Integrate(rs, cs);
333 cs.AddRegion(rs);
334 return cs;
335 }
336
337 internal ChannelState UpdateRelayIn(RegionState rs, string channel)
338 {
339 RemoveRegion(rs);
340 ChannelState cs = new ChannelState(this);
341 cs.RelayChannel = Convert.ToInt32(channel);
342 cs = Integrate(rs, cs);
343 cs.AddRegion(rs);
344 return cs;
345 }
346
347 internal ChannelState UpdateRelayOut(RegionState rs, string channel)
348 {
349 RemoveRegion(rs);
350 ChannelState cs = new ChannelState(this);
351 cs.RelayChannelOut = Convert.ToInt32(channel);
352 cs = Integrate(rs, cs);
353 cs.AddRegion(rs);
354 return cs;
355 }
356
357 // Determine whether or not this is a 'new' channel. Only those
358 // attributes that uniquely distinguish an IRC connection should
359 // be included here (and only those attributes should really be
360 // in the ChannelState structure)
361
362 private bool IsAConnectionMatchFor(ChannelState cs)
363 {
364 return (
365 Server == cs.Server &&
366 IrcChannel == cs.IrcChannel &&
367 Port == cs.Port &&
368 BaseNickname == cs.BaseNickname &&
369 User == cs.User
370 );
371 }
372
373 // This level of obsessive matching allows us to produce
374 // a minimal overhead int he case of a server which does
375 // need to differentiate IRC at a region level.
376
377 private bool IsAPerfectMatchFor(ChannelState cs)
378 {
379 return ( IsAConnectionMatchFor(cs) &&
380
381 RelayChannelOut == cs.RelayChannelOut &&
382 PrivateMessageFormat == cs.PrivateMessageFormat &&
383 NoticeMessageFormat == cs.NoticeMessageFormat &&
384 RandomizeNickname == cs.RandomizeNickname &&
385 AccessPassword == cs.AccessPassword &&
386 CommandsEnabled == cs.CommandsEnabled &&
387 CommandChannel == cs.CommandChannel &&
388 DefaultZone == cs.DefaultZone &&
389 RelayPrivateChannels == cs.RelayPrivateChannels &&
390 RelayChannel == cs.RelayChannel &&
391 ClientReporting == cs.ClientReporting
392 );
393 }
394
395 // This function implements the variable substitution mechanism
396 // for the configuration values. Each string read from the
397 // configuration file is scanned for '[...]' enclosures. Each
398 // one that is found is replaced by either a runtime variable
399 // (%xxx) or an existing configuration key. When no further
400 // substitution is possible, the remaining string is returned
401 // to the caller. This allows for arbitrarily nested
402 // enclosures.
403
404 private static string Substitute(RegionState rs, string instr)
405 {
406
407 string result = instr;
408
409 if (result == null || result.Length == 0)
410 return result;
411
412 // Repeatedly scan the string until all possible
413 // substitutions have been performed.
414
415 while (arg.IsMatch(result))
416 {
417
418 string vvar = arg.Match(result).ToString();
419 string var = vvar.Substring(1,vvar.Length-2).Trim();
420
421 switch (var.ToLower())
422 {
423 case "%region" :
424 result = result.Replace(vvar, rs.Region);
425 break;
426 case "%host" :
427 result = result.Replace(vvar, rs.Host);
428 break;
429 case "%master1" :
430 result = result.Replace(vvar, rs.MA1);
431 break;
432 case "%master2" :
433 result = result.Replace(vvar, rs.MA2);
434 break;
435 case "%locx" :
436 result = result.Replace(vvar, rs.LocX);
437 break;
438 case "%locy" :
439 result = result.Replace(vvar, rs.LocY);
440 break;
441 case "%k" :
442 result = result.Replace(vvar, rs.IDK);
443 break;
444 default :
445 result = result.Replace(vvar, rs.config.GetString(var,var));
446 break;
447 }
448 }
449
450 return result;
451
452 }
453
454 public void Close()
455 {
456
457 m_log.InfoFormat("[IRC-Channel-{0}] Closing channel <{1} to server <{2}:{3}>",
458 idn, IrcChannel, Server, Port);
459
460 m_log.InfoFormat("[IRC-Channel-{0}] There are {1} active clients",
461 idn, clientregions.Count);
462
463 irc.Close();
464
465 }
466
467 public void Open()
468 {
469 m_log.InfoFormat("[IRC-Channel-{0}] Opening channel <{1} to server <{2}:{3}>",
470 idn, IrcChannel, Server, Port);
471
472 irc.Open();
473
474 }
475
476 // These are called by each region that attaches to this channel. The call affects
477 // only the relationship of the region with the channel. Not the channel to IRC
478 // relationship (unless it is closed and we want it open).
479
480 public void Open(RegionState rs)
481 {
482 AddRegion(rs);
483 Open();
484 }
485
486 public void Close(RegionState rs)
487 {
488 RemoveRegion(rs);
489 }
490
491 // Add a client region to this channel if it is not already known
492
493 public void AddRegion(RegionState rs)
494 {
495 m_log.InfoFormat("[IRC-Channel-{0}] Adding region {1} to channel <{2} to server <{3}:{4}>",
496 idn, rs.Region, IrcChannel, Server, Port);
497 if (!clientregions.Contains(rs))
498 {
499 clientregions.Add(rs);
500 lock (irc) irc.depends++;
501 }
502 }
503
504 // Remove a client region from the channel. If this is the last
505 // region, then clean up the channel. The connector will clean itself
506 // up if it finds itself about to be GC'd.
507
508 public void RemoveRegion(RegionState rs)
509 {
510
511 m_log.InfoFormat("[IRC-Channel-{0}] Removing region {1} from channel <{2} to server <{3}:{4}>",
512 idn, rs.Region, IrcChannel, Server, Port);
513
514 if (clientregions.Contains(rs))
515 {
516 clientregions.Remove(rs);
517 lock (irc) irc.depends--;
518 }
519
520 }
521
522 // This function is lifted from the IRCConnector because it
523 // contains information that is not differentiating from an
524 // IRC point-of-view.
525
526 public static void OSChat(XIRCConnector p_irc, OSChatMessage c, bool cmsg)
527 {
528
529 // m_log.DebugFormat("[IRC-OSCHAT] from {0}:{1}", p_irc.Server, p_irc.IrcChannel);
530
531 try
532 {
533
534 // Scan through the set of unique channel configuration for those
535 // that belong to this connector. And then forward the message to
536 // all regions known to those channels.
537 // Note that this code is responsible for completing some of the
538 // settings for the inbound OSChatMessage
539
540 foreach (ChannelState cs in XIRCBridgeModule.m_channels)
541 {
542 if ( p_irc == cs.irc)
543 {
544
545 // This non-IRC differentiator moved to here
546
547 if (cmsg && !cs.ClientReporting)
548 continue;
549
550 // This non-IRC differentiator moved to here
551
552 c.Channel = (cs.RelayPrivateChannels ? cs.RelayChannel : 0);
553
554 foreach (RegionState region in cs.clientregions)
555 {
556 region.OSChat(cs.irc, c);
557 }
558
559 }
560 }
561
562 }
563 catch (Exception ex)
564 {
565 m_log.ErrorFormat("[IRC-OSCHAT]: BroadcastSim Exception: {0}", ex.Message);
566 m_log.Debug(ex);
567 }
568 }
569 }
570}
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs
index ba993a0..f1aa756 100644
--- a/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs
+++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/IRCBridgeModule.cs
@@ -242,7 +242,7 @@ namespace OpenSim.Region.Environment.Modules.Avatar.Chat
242 break; 242 break;
243 } 243 }
244 } 244 }
245 catch(Exception ex) 245 catch (Exception ex)
246 { 246 {
247 m_log.DebugFormat("[IRC] error processing in-world command channel input: {0}", ex); 247 m_log.DebugFormat("[IRC] error processing in-world command channel input: {0}", ex);
248 } 248 }
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/RegionState.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/RegionState.cs
new file mode 100644
index 0000000..cf2154f
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/RegionState.cs
@@ -0,0 +1,435 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.Reflection;
31using log4net;
32using Nini.Config;
33using OpenSim.Framework;
34using OpenSim.Region.Environment.Interfaces;
35using OpenSim.Region.Environment.Scenes;
36
37namespace OpenSim.Region.Environment.Modules.Avatar.Chat
38{
39 // An instance of this class exists for every active region
40
41 internal class RegionState
42 {
43
44 private static readonly ILog m_log =
45 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
46
47 private static readonly OpenMetaverse.Vector3 CenterOfRegion = new OpenMetaverse.Vector3(128, 128, 20);
48 private const int DEBUG_CHANNEL = 2147483647;
49
50 private static int _idk_ = 0;
51
52 // Runtime variables; these values are assigned when the
53 // IrcState is created and remain constant thereafter.
54
55 internal string Region = String.Empty;
56 internal string Host = String.Empty;
57 internal string LocX = String.Empty;
58 internal string LocY = String.Empty;
59 internal string MA1 = String.Empty;
60 internal string MA2 = String.Empty;
61 internal string IDK = String.Empty;
62
63 // System values - used only be the IRC classes themselves
64
65 internal ChannelState cs = null; // associated IRC configuration
66 internal Scene scene = null; // associated scene
67 internal IConfig config = null; // configuration file reference
68 internal bool enabled = true;
69
70 // This list is used to keep track of who is here, and by
71 // implication, who is not.
72
73 internal List<IClientAPI> clients = new List<IClientAPI>();
74
75 // Setup runtime variable values
76
77 public RegionState(Scene p_scene, IConfig p_config)
78 {
79
80 scene = p_scene;
81 config = p_config;
82
83 Region = scene.RegionInfo.RegionName;
84 Host = scene.RegionInfo.ExternalHostName;
85 LocX = Convert.ToString(scene.RegionInfo.RegionLocX);
86 LocY = Convert.ToString(scene.RegionInfo.RegionLocY);
87 MA1 = scene.RegionInfo.MasterAvatarFirstName;
88 MA2 = scene.RegionInfo.MasterAvatarLastName;
89 IDK = Convert.ToString(_idk_++);
90
91 // OpenChannel conditionally establishes a connection to the
92 // IRC server. The request will either succeed, or it will
93 // throw an exception.
94
95 ChannelState.OpenChannel(this, config);
96
97 // Connect channel to world events
98
99 scene.EventManager.OnChatFromWorld += OnSimChat;
100 scene.EventManager.OnChatFromClient += OnSimChat;
101 scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
102 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
103
104 m_log.InfoFormat("[IRC-Region {0}] Initialization complete", Region);
105
106 }
107
108 // Auto cleanup when abandoned
109
110 ~RegionState()
111 {
112 if (cs != null)
113 cs.RemoveRegion(this);
114 }
115
116 // Called by PostInitialize after all regions have been created
117
118 public void Open()
119 {
120 cs.Open(this);
121 enabled = true;
122 }
123
124 // Called by IRCBridgeModule.Close immediately prior to unload
125
126 public void Close()
127 {
128 enabled = false;
129 cs.Close(this);
130 }
131
132 // The agent has disconnected, cleanup associated resources
133
134 private void OnClientLoggedOut(IClientAPI client)
135 {
136 try
137 {
138 if (clients.Contains(client))
139 {
140 if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
141 {
142 m_log.InfoFormat("[IRC-Region {0}]: {1} has left", Region, client.Name);
143 cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", client.Name));
144 }
145 client.OnLogout -= OnClientLoggedOut;
146 client.OnConnectionClosed -= OnClientLoggedOut;
147 clients.Remove(client);
148 }
149 }
150 catch (Exception ex)
151 {
152 m_log.ErrorFormat("[IRC-Region {0}]: ClientLoggedOut exception: {1}", Region, ex.Message);
153 m_log.Debug(ex);
154 }
155 }
156
157 // This event indicates that the agent has left the building. We should treat that the same
158 // as if the agent has logged out (we don't want cross-region noise - or do we?)
159
160 private void OnMakeChildAgent(ScenePresence presence)
161 {
162
163 IClientAPI client = presence.ControllingClient;
164
165 try
166 {
167 if (clients.Contains(client))
168 {
169 if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
170 {
171 string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
172 m_log.DebugFormat("[IRC-Region {0}] {1} has left", Region, clientName);
173 cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has left", clientName));
174 }
175 client.OnLogout -= OnClientLoggedOut;
176 client.OnConnectionClosed -= OnClientLoggedOut;
177 clients.Remove(client);
178 }
179 }
180 catch (Exception ex)
181 {
182 m_log.ErrorFormat("[IRC-Region {0}]: MakeChildAgent exception: {1}", Region, ex.Message);
183 m_log.Debug(ex);
184 }
185
186 }
187
188 // An agent has entered the region (from another region). Add the client to the locally
189 // known clients list
190
191 private void OnMakeRootAgent(ScenePresence presence)
192 {
193
194 IClientAPI client = presence.ControllingClient;
195
196 try
197 {
198 if (!clients.Contains(client))
199 {
200 client.OnLogout += OnClientLoggedOut;
201 client.OnConnectionClosed += OnClientLoggedOut;
202 clients.Add(client);
203 if (enabled && (cs.irc.Enabled) && (cs.irc.Connected) && (cs.ClientReporting))
204 {
205 string clientName = String.Format("{0} {1}", presence.Firstname, presence.Lastname);
206 m_log.DebugFormat("[IRC-Region {0}] {1} has arrived", Region, clientName);
207 cs.irc.PrivMsg(cs.NoticeMessageFormat, cs.irc.Nick, Region, String.Format("{0} has arrived", clientName));
208 }
209 }
210 }
211 catch (Exception ex)
212 {
213 m_log.ErrorFormat("[IRC-Region {0}]: MakeRootAgent exception: {1}", Region, ex.Message);
214 m_log.Debug(ex);
215 }
216
217 }
218
219 // This handler detects chat events int he virtual world.
220
221 public void OnSimChat(Object sender, OSChatMessage msg)
222 {
223
224 // early return if this comes from the IRC forwarder
225
226 if (cs.irc.Equals(sender)) return;
227
228 // early return if nothing to forward
229
230 if (msg.Message.Length == 0) return;
231
232 // check for commands coming from avatars or in-world
233 // object (if commands are enabled)
234
235 if (cs.CommandsEnabled && msg.Channel == cs.CommandChannel)
236 {
237
238 m_log.DebugFormat("[IRC-Region {0}] command on channel {1}: {2}", Region, msg.Channel, msg.Message);
239
240 string[] messages = msg.Message.Split(' ');
241 string command = messages[0].ToLower();
242
243 try
244 {
245 switch (command)
246 {
247
248 // These commands potentially require a change in the
249 // underlying ChannelState.
250
251 case "server":
252 cs.Close(this);
253 cs = cs.UpdateServer(this, messages[1]);
254 cs.Open(this);
255 break;
256 case "port":
257 cs.Close(this);
258 cs = cs.UpdatePort(this, messages[1]);
259 cs.Open(this);
260 break;
261 case "channel":
262 cs.Close(this);
263 cs = cs.UpdateChannel(this, messages[1]);
264 cs.Open(this);
265 break;
266 case "nick":
267 cs.Close(this);
268 cs = cs.UpdateNickname(this, messages[1]);
269 cs.Open(this);
270 break;
271
272 // These may also (but are less likely) to require a
273 // change in ChannelState.
274
275 case "client-reporting":
276 cs = cs.UpdateClientReporting(this, messages[1]);
277 break;
278 case "in-channel":
279 cs = cs.UpdateRelayIn(this, messages[1]);
280 break;
281 case "out-channel":
282 cs = cs.UpdateRelayOut(this, messages[1]);
283 break;
284
285 // These are all taken to be temporary changes in state
286 // so the underlying connector remains intact. But note
287 // that with regions sharing a connector, there could
288 // be interference.
289
290 case "close":
291 enabled = false;
292 cs.Close(this);
293 break;
294
295 case "connect":
296 enabled = true;
297 cs.Open(this);
298 break;
299
300 case "reconnect":
301 enabled = true;
302 cs.Close(this);
303 cs.Open(this);
304 break;
305
306 // This one is harmless as far as we can judge from here.
307 // If it is not, then the complaints will eventually make
308 // that evident.
309
310 default:
311 m_log.DebugFormat("[IRC-Region {0}] Forwarding unrecognized command to IRC : {1}",
312 Region, msg.Message);
313 cs.irc.Send(msg.Message);
314 break;
315 }
316 }
317 catch (Exception ex)
318 {
319 m_log.WarnFormat("[IRC-Region {0}] error processing in-world command channel input: {1}",
320 Region, ex.Message);
321 m_log.Debug(ex);
322 }
323
324 return;
325
326 }
327
328 // The command channel remains enabled, even if we have otherwise disabled the IRC
329 // interface.
330
331 if (!enabled)
332 return;
333
334 // drop all messages coming in on a private channel,
335 // except if we are relaying private channels, in which
336 // case we drop if the private channel is not the
337 // configured m_relayChannelOut
338
339 if (cs.RelayPrivateChannels)
340 {
341 if (msg.Channel != 0 && msg.Channel != DEBUG_CHANNEL && msg.Channel != cs.RelayChannelOut)
342 {
343 m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
344 return;
345 }
346 }
347 else if (msg.Channel != 0 && msg.Channel != DEBUG_CHANNEL)
348 {
349 m_log.DebugFormat("[IRC-Region {0}] dropping message {1} on channel {2}", Region, msg, msg.Channel);
350 return;
351 }
352
353 ScenePresence avatar = null;
354
355 string fromName = msg.From;
356
357 if (msg.Sender != null)
358 {
359 avatar = scene.GetScenePresence(msg.Sender.AgentId);
360 if (avatar != null) fromName = avatar.Name;
361 }
362
363 if (!cs.irc.Connected)
364 {
365 m_log.WarnFormat("[IRC-Region {0}] IRCConnector not connected: dropping message from {1}", Region, fromName);
366 return;
367 }
368
369 m_log.DebugFormat("[IRC-Region {0}] heard on channel {1} : {2}", Region, msg.Channel, msg.Message);
370
371 if (null != avatar)
372 {
373 string txt = msg.Message;
374 if (txt.StartsWith("/me "))
375 txt = String.Format("{0} {1}", fromName, msg.Message.Substring(4));
376
377 cs.irc.PrivMsg(cs.PrivateMessageFormat, fromName, Region, txt);
378 }
379 else
380 {
381 //Message came from an object
382 char[] splits = { ',' };
383 string[] tokens = msg.Message.Split(splits,3); // This is certainly wrong
384
385 if (tokens.Length == 3)
386 {
387 if (tokens[0] == cs.AccessPassword) // This is my really simple check
388 {
389 m_log.DebugFormat("[IRC-Region {0}] message from object {1}, {2}", Region, tokens[0], tokens[1]);
390 cs.irc.PrivMsg(cs.PrivateMessageFormat, tokens[1], scene.RegionInfo.RegionName, tokens[2]);
391 }
392 else
393 {
394 m_log.WarnFormat("[IRC-Region {0}] prim security key mismatch <{1}> not <{2}>", Region, tokens[0], cs.AccessPassword);
395 }
396 }
397 }
398 }
399
400 // This method gives the region an opportunity to interfere with
401 // message delivery. For now we just enforce the enable/disable
402 // flag.
403
404 internal void OSChat(Object irc, OSChatMessage msg)
405 {
406 if (enabled)
407 {
408 // m_log.DebugFormat("[IRC-OSCHAT] Region {0} being sent message", region.Region);
409 msg.Scene = scene;
410 scene.EventManager.TriggerOnChatBroadcast(irc, msg);
411 }
412 }
413
414 // This supports any local message traffic that might be needed in
415 // support of command processing. At present there is none.
416
417 internal void LocalChat(string msg)
418 {
419 if (enabled)
420 {
421 OSChatMessage osm = new OSChatMessage();
422 osm.From = "IRC Agent";
423 osm.Message = msg;
424 osm.Type = ChatTypeEnum.Region;
425 osm.Position = CenterOfRegion;
426 osm.Sender = null;
427 osm.SenderUUID = OpenMetaverse.UUID.Zero; // Hmph! Still?
428 osm.Channel = 0;
429 OSChat(this, osm);
430 }
431 }
432
433 }
434
435}
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs
new file mode 100644
index 0000000..d7abc19
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCBridgeModule.cs
@@ -0,0 +1,280 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections;
30using System.Collections.Generic;
31using System.Reflection;
32using log4net;
33using Nini.Config;
34using Nwc.XmlRpc;
35using OpenSim.Framework;
36using OpenSim.Region.Environment.Interfaces;
37using OpenSim.Region.Environment.Scenes;
38
39namespace OpenSim.Region.Environment.Modules.Avatar.Chat
40{
41
42 public class XIRCBridgeModule : IRegionModule
43 {
44
45 private static readonly ILog m_log =
46 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
47
48 internal static bool configured = false;
49 internal static bool enabled = false;
50 internal static IConfig m_config = null;
51
52 internal static List<RegionState> m_regions = new List<RegionState>();
53 internal static List<ChannelState> m_channels = new List<ChannelState>();
54
55 internal static string password = String.Empty;
56
57 #region IRegionModule Members
58
59 public string Name
60 {
61 get { return "XIRCBridgeModule"; }
62 }
63
64 public bool IsSharedModule
65 {
66 get { return true; }
67 }
68
69 public void Initialise(Scene scene, IConfigSource config)
70 {
71
72 // Do a once-only scan of the configuration file to make
73 // sure it's basically intact.
74
75 if (!configured)
76 {
77
78 configured = true;
79
80 try
81 {
82 if ((m_config = config.Configs["XIRC"]) == null)
83 {
84 m_log.InfoFormat("[XIRC-Bridge] module not configured");
85 return;
86 }
87
88 if (!m_config.GetBoolean("enabled", false))
89 {
90 m_log.InfoFormat("[XIRC-Bridge] module disabled in configuration");
91 return;
92 }
93 }
94 catch (Exception e)
95 {
96 m_log.ErrorFormat("[XIRC-Bridge] configuration failed : {0}", e.Message);
97 return;
98 }
99
100 enabled = true;
101
102 if(config.Configs["RemoteAdmin"] != null)
103 {
104 password = config.Configs["RemoteAdmin"].GetString("access_password", password);
105 scene.CommsManager.HttpServer.AddXmlRPCHandler("xirc_admin", XmlRpcAdminMethod, false);
106 }
107
108 }
109
110 // Iff the IRC bridge is enabled, then each new region may be
111 // connected to IRC. But it should NOT be obligatory (and it
112 // is not).
113
114 if (enabled)
115 {
116 try
117 {
118 m_log.InfoFormat("[XIRC-Bridge] Connecting region {0}", scene.RegionInfo.RegionName);
119 m_regions.Add(new RegionState(scene, m_config));
120 }
121 catch (Exception e)
122 {
123 m_log.WarnFormat("[XIRC-Bridge] Region {0} not connected to IRC : {1}", scene.RegionInfo.RegionName, e.Message);
124 m_log.Debug(e);
125 }
126 }
127 else
128 {
129 m_log.WarnFormat("[XIRC-Bridge] Not enabled. Connect for region {0} ignored", scene.RegionInfo.RegionName);
130 }
131
132 }
133
134 // Called after all region modules have been loaded.
135 // Iff the IRC bridge is enabled, then start all of the
136 // configured channels. The set of channels is a side
137 // effect of RegionState creation.
138
139 public void PostInitialise()
140 {
141
142 if (!enabled)
143 return;
144
145 foreach (RegionState region in m_regions)
146 {
147 m_log.InfoFormat("[XIRC-Bridge] Opening connection for {0}:{1} on IRC server {2}:{3}",
148 region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel);
149 try
150 {
151 region.Open();
152 }
153 catch (Exception e)
154 {
155 m_log.ErrorFormat("[XIRC-Bridge] Open failed for {0}:{1} on IRC server {2}:{3} : {4}",
156 region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel,
157 e.Message);
158 }
159 }
160
161 }
162
163 // Called immediately before the region module is unloaded. Close all
164 // associated channels.
165
166 public void Close()
167 {
168
169 if (!enabled)
170 return;
171
172 // Stop each of the region sessions
173
174 foreach (RegionState region in m_regions)
175 {
176 m_log.InfoFormat("[XIRC-Bridge] Closing connection for {0}:{1} on IRC server {2}:{3}",
177 region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel);
178 try
179 {
180 region.Close();
181 }
182 catch (Exception e)
183 {
184 m_log.ErrorFormat("[XIRC-Bridge] Close failed for {0}:{1} on IRC server {2}:{3} : {4}",
185 region.Region, region.cs.BaseNickname, region.cs.Server, region.cs.IrcChannel,
186 e.Message);
187 }
188 }
189
190 // Perform final cleanup of the channels (they now have no active clients)
191
192 foreach (ChannelState channel in m_channels)
193 {
194 m_log.InfoFormat("[XIRC-Bridge] Closing connection for {0} on IRC server {1}:{2}",
195 channel.BaseNickname, channel.Server, channel.IrcChannel);
196 try
197 {
198 channel.Close();
199 }
200 catch (Exception e)
201 {
202 m_log.ErrorFormat("[XIRC-Bridge] Close failed for {0} on IRC server {1}:{2} : {3}",
203 channel.BaseNickname, channel.Server, channel.IrcChannel,
204 e.Message);
205 }
206 }
207
208 }
209
210 #endregion
211
212 public XmlRpcResponse XmlRpcAdminMethod(XmlRpcRequest request)
213 {
214
215 m_log.Info("[XIRC-Bridge]: XML RPC Admin Entry");
216
217 XmlRpcResponse response = new XmlRpcResponse();
218 Hashtable responseData = new Hashtable();
219
220 try
221 {
222
223 Hashtable requestData = (Hashtable)request.Params[0];
224 bool found = false;
225 string region = String.Empty;
226
227 if(password != String.Empty)
228 {
229 if(!requestData.ContainsKey("password"))
230 throw new Exception("Invalid request");
231 if(requestData["password"] != password)
232 throw new Exception("Invalid request");
233 }
234
235 if(!requestData.ContainsKey("region"))
236 throw new Exception("No region name specified");
237
238 foreach(RegionState rs in m_regions)
239 {
240 if(rs.Region == region)
241 {
242 responseData["server"] = rs.cs.Server;
243 responseData["port"] = rs.cs.Port;
244 responseData["user"] = rs.cs.User;
245 responseData["channel"] = rs.cs.IrcChannel;
246 responseData["enabled"] = rs.cs.irc.Enabled;
247 responseData["connected"] = rs.cs.irc.Connected;
248 responseData["nickname"] = rs.cs.irc.Nick;
249 found = true;
250 break;
251 }
252 }
253
254 if(!found) throw new Exception(String.Format("Region <{0}> not found", region));
255
256 responseData["success"] = true;
257
258 }
259 catch (Exception e)
260 {
261 m_log.InfoFormat("[XIRC-Bridge] XML RPC Admin request failed : {0}", e.Message);
262
263 responseData["success"] = "false";
264 responseData["error"] = e.Message;
265
266 }
267 finally
268 {
269 response.Value = responseData;
270 }
271
272 m_log.Debug("[XIRC-Bridge]: XML RPC Admin Exit");
273
274 return response;
275
276 }
277
278 }
279
280}
diff --git a/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs b/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs
new file mode 100644
index 0000000..b9203a8
--- /dev/null
+++ b/OpenSim/Region/Environment/Modules/Avatar/Chat/XIRCConnector.cs
@@ -0,0 +1,842 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSim Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Timers;
30using System.Collections.Generic;
31using System.IO;
32using System.Net.Sockets;
33using System.Reflection;
34using System.Text.RegularExpressions;
35using System.Threading;
36using OpenMetaverse;
37using log4net;
38using Nini.Config;
39using OpenSim.Framework;
40using OpenSim.Region.Environment.Interfaces;
41using OpenSim.Region.Environment.Scenes;
42
43namespace OpenSim.Region.Environment.Modules.Avatar.Chat
44{
45 public class XIRCConnector
46 {
47
48 #region Global (static) state
49
50 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
51
52 // Local constants
53
54 private static readonly Vector3 CenterOfRegion = new Vector3(128, 128, 20);
55 private static readonly char[] CS_SPACE = { ' ' };
56
57 private const int WD_INTERVAL = 1000; // base watchdog interval
58 private static int PING_PERIOD = 15; // WD intervals per PING
59 private static int ICCD_PERIOD = 10; // WD intervals between Connects
60
61 private static int _idk_ = 0; // core connector identifier
62 private static int _pdk_ = 0; // ping interval counter
63 private static int _icc_ = 0; // IRC connect counter
64
65 // List of configured connectors
66
67 private static List<XIRCConnector> m_connectors = new List<XIRCConnector>();
68
69 // Watchdog state
70
71 private static System.Timers.Timer m_watchdog = null;
72
73 static XIRCConnector()
74 {
75 m_log.DebugFormat("[IRC-Connector]: Static initialization started");
76 m_watchdog = new System.Timers.Timer(WD_INTERVAL);
77 m_watchdog.Elapsed += new ElapsedEventHandler(WatchdogHandler);
78 m_watchdog.AutoReset = true;
79 m_watchdog.Start();
80 m_log.DebugFormat("[IRC-Connector]: Static initialization complete");
81 }
82
83 #endregion
84
85 #region Instance state
86
87 // Connector identity
88
89 internal int idn = _idk_++;
90
91 // How many regions depend upon this connection
92 // This count is updated by the ChannelState object and reflects the sum
93 // of the region clients associated with the set of associated channel
94 // state instances. That's why it cannot be managed here.
95
96 internal int depends = 0;
97
98 // Working threads
99
100 private Thread m_listener = null;
101
102 private Object msyncConnect = new Object();
103
104 internal bool m_randomizeNick = true; // add random suffix
105 internal string m_baseNick = null; // base name for randomizing
106 internal string m_nick = null; // effective nickname
107
108 public string Nick // Public property
109 {
110 get { return m_nick; }
111 set { m_nick = value; }
112 }
113
114 private bool m_enabled = false; // connector enablement
115 public bool Enabled
116 {
117 get { return m_enabled; }
118 }
119
120 private bool m_connected = false; // connection status
121 public bool Connected
122 {
123 get { return m_connected; }
124 }
125
126 private string m_ircChannel; // associated channel id
127 public string IrcChannel
128 {
129 get { return m_ircChannel; }
130 set { m_ircChannel = value; }
131 }
132
133 private uint m_port = 6667; // session port
134 public uint Port
135 {
136 get { return m_port; }
137 set { m_port = value; }
138 }
139
140 private string m_server = null; // IRC server name
141 public string Server
142 {
143 get { return m_server; }
144 set { m_server = value; }
145 }
146
147 private string m_user = "USER OpenSimBot 8 * :I'm an OpenSim to IRC bot";
148 public string User
149 {
150 get { return m_user; }
151 }
152
153 // Network interface
154
155 private TcpClient m_tcp;
156 private NetworkStream m_stream = null;
157 private StreamReader m_reader;
158 private StreamWriter m_writer;
159
160 // Channel characteristic info (if available)
161
162 internal string usermod = String.Empty;
163 internal string chanmod = String.Empty;
164 internal string version = String.Empty;
165 internal bool motd = false;
166
167 #endregion
168
169 #region connector instance management
170
171 internal XIRCConnector(ChannelState cs)
172 {
173
174 // Prepare network interface
175
176 m_tcp = null;
177 m_writer = null;
178 m_reader = null;
179
180 // Setup IRC session parameters
181
182 m_server = cs.Server;
183 m_baseNick = cs.BaseNickname;
184 m_randomizeNick = cs.RandomizeNickname;
185 m_ircChannel = cs.IrcChannel;
186 m_port = (uint) cs.Port;
187 m_user = cs.User;
188
189 if (m_watchdog == null)
190 {
191 // Non-differentiating
192
193 ICCD_PERIOD = cs.ConnectDelay;
194 PING_PERIOD = cs.PingDelay;
195
196 // Smaller values are not reasonable
197
198 if (ICCD_PERIOD < 5)
199 ICCD_PERIOD = 5;
200
201 if (PING_PERIOD < 5)
202 PING_PERIOD = 5;
203
204 _icc_ = ICCD_PERIOD; // get started right away!
205
206 }
207
208 // The last line of defense
209
210 if (m_server == null || m_baseNick == null || m_ircChannel == null || m_user == null)
211 throw new Exception("Invalid connector configuration");
212
213 // Generate an initial nickname if randomizing is enabled
214
215 if (m_randomizeNick)
216 {
217 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
218 }
219
220 // Add the newly created connector to the known connectors list
221
222 m_connectors.Add(this);
223
224 m_log.InfoFormat("[IRC-Connector-{0}]: Initialization complete", idn);
225
226 }
227
228 ~XIRCConnector()
229 {
230 m_watchdog.Stop();
231 Close();
232 }
233
234 // Mark the connector as connectable. Harmless if already enabled.
235
236 public void Open()
237 {
238 if (!m_enabled)
239 {
240
241 m_connectors.Add(this);
242 m_enabled = true;
243
244 if (!Connected)
245 {
246 Connect();
247 }
248
249 }
250 }
251
252 // Only close the connector if the dependency count is zero.
253
254 public void Close()
255 {
256
257 m_log.InfoFormat("[IRC-Connector-{0}] Closing", idn);
258
259 lock (msyncConnect)
260 {
261
262 if ((depends == 0) && Enabled)
263 {
264
265 m_enabled = false;
266
267 if (Connected)
268 {
269 m_log.DebugFormat("[IRC-Connector-{0}] Closing interface", idn);
270
271 // Cleanup the IRC session
272
273 try
274 {
275 m_writer.WriteLine(String.Format("QUIT :{0} to {1} wormhole to {2} closing",
276 m_nick, m_ircChannel, m_server));
277 m_writer.Flush();
278 }
279 catch (Exception) {}
280
281
282 m_connected = false;
283
284 try { m_writer.Close(); } catch (Exception) {}
285 try { m_reader.Close(); } catch (Exception) {}
286 try { m_stream.Close(); } catch (Exception) {}
287 try { m_tcp.Close(); } catch (Exception) {}
288
289 }
290
291 m_connectors.Remove(this);
292
293 }
294 }
295
296 m_log.InfoFormat("[IRC-Connector-{0}] Closed", idn);
297
298 }
299
300 #endregion
301
302 #region session management
303
304 // Connect to the IRC server. A connector should always be connected, once enabled
305
306 public void Connect()
307 {
308
309 if (!m_enabled)
310 return;
311
312 // Delay until next WD cycle if this is too close to the last start attempt
313
314 while (_icc_ < ICCD_PERIOD)
315 return;
316
317 m_log.DebugFormat("[IRC-Connector-{0}]: Connection request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
318
319 lock (msyncConnect)
320 {
321
322 _icc_ = 0;
323
324 try
325 {
326 if (m_connected) return;
327
328 m_connected = true;
329
330 m_tcp = new TcpClient(m_server, (int)m_port);
331 m_stream = m_tcp.GetStream();
332 m_reader = new StreamReader(m_stream);
333 m_writer = new StreamWriter(m_stream);
334
335 m_log.InfoFormat("[IRC-Connector-{0}]: Connected to {1}:{2}", idn, m_server, m_port);
336
337 m_listener = new Thread(new ThreadStart(ListenerRun));
338 m_listener.Name = "IRCConnectorListenerThread";
339 m_listener.IsBackground = true;
340 m_listener.Start();
341 ThreadTracker.Add(m_listener);
342
343 // This is the message order recommended by RFC 2812
344
345 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
346 m_writer.Flush();
347 m_writer.WriteLine(m_user);
348 m_writer.Flush();
349 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
350 m_writer.Flush();
351
352 m_log.InfoFormat("[IRC-Connector-{0}]: {1} has joined {2}", idn, m_nick, m_ircChannel);
353 m_log.InfoFormat("[IRC-Connector-{0}] Connected", idn);
354
355 }
356 catch (Exception e)
357 {
358 m_log.ErrorFormat("[IRC-Connector-{0}] cannot connect {1} to {2}:{3}: {4}",
359 idn, m_nick, m_server, m_port, e.Message);
360 m_connected = false;
361 }
362
363 }
364
365 return;
366
367 }
368
369 // Reconnect is used to force a re-cycle of the IRC connection. Should generally
370 // be a transparent event
371
372 public void Reconnect()
373 {
374
375 m_log.DebugFormat("[IRC-Connector-{0}]: Reconnect request for {1} on {2}:{3}", idn, m_nick, m_server, m_ircChannel);
376
377 // Don't do this if a Connect is in progress...
378
379 lock (msyncConnect)
380 {
381
382 if (m_connected)
383 {
384 m_log.InfoFormat("[IRC-Connector-{0}] Resetting connector", idn);
385
386 // Mark as disconnected. This will allow the listener thread
387 // to exit if still in-flight.
388
389
390 // The listener thread is not aborted - it *might* actually be
391 // the thread that is running the Reconnect! Instead just close
392 // the socket and it will disappear of its own accord, once this
393 // processing is completed.
394
395 try { m_writer.Close(); } catch (Exception) {}
396 try { m_reader.Close(); } catch (Exception) {}
397 try { m_tcp.Close(); } catch (Exception) {}
398
399 m_connected = false;
400
401 }
402
403 }
404
405 Connect();
406
407 }
408
409 #endregion
410
411 #region Outbound (to-IRC) message handlers
412
413 public void PrivMsg(string pattern, string from, string region, string msg)
414 {
415
416 m_log.DebugFormat("[IRC-Connector-{0}] PrivMsg to IRC from {1}: <{2}>", idn, from,
417 String.Format(pattern, m_ircChannel, from, region, msg));
418
419 // One message to the IRC server
420
421 try
422 {
423 m_writer.WriteLine(pattern, m_ircChannel, from, region, msg);
424 m_writer.Flush();
425 m_log.DebugFormat("[IRC-Connector-{0}]: PrivMsg from {1} in {2}: {3}", idn, from, region, msg);
426 }
427 catch (IOException)
428 {
429 m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg I/O Error: disconnected from IRC server", idn);
430 Reconnect();
431 }
432 catch (Exception ex)
433 {
434 m_log.ErrorFormat("[IRC-Connector-{0}]: PrivMsg exception : {1}", idn, ex.Message);
435 m_log.Debug(ex);
436 }
437
438 }
439
440 public void Send(string msg)
441 {
442
443 m_log.DebugFormat("[IRC-Connector-{0}] Send to IRC : <{1}>", idn, msg);
444
445 try
446 {
447 m_writer.WriteLine(msg);
448 m_writer.Flush();
449 m_log.DebugFormat("[IRC-Connector-{0}] Sent command string: {1}", idn, msg);
450 }
451 catch (IOException)
452 {
453 m_log.ErrorFormat("[IRC-Connector-{0}] Disconnected from IRC server.(Send)", idn);
454 Reconnect();
455 }
456 catch (Exception ex)
457 {
458 m_log.ErrorFormat("[IRC-Connector-{0}] Send exception trap: {0}", idn, ex.Message);
459 m_log.Debug(ex);
460 }
461
462 }
463
464 #endregion
465
466 public void ListenerRun()
467 {
468 string inputLine;
469
470 try
471 {
472 while (m_enabled && m_connected)
473 {
474
475 if ((inputLine = m_reader.ReadLine()) == null)
476 throw new Exception("Listener input socket closed");
477
478 // m_log.Info("[IRCConnector]: " + inputLine);
479
480 if (inputLine.Contains("PRIVMSG"))
481 {
482
483 Dictionary<string, string> data = ExtractMsg(inputLine);
484
485 // Any chat ???
486 if (data != null)
487 {
488
489 OSChatMessage c = new OSChatMessage();
490 c.Message = data["msg"];
491 c.Type = ChatTypeEnum.Region;
492 c.Position = CenterOfRegion;
493 c.From = data["nick"];
494 c.Sender = null;
495 c.SenderUUID = UUID.Zero;
496
497 // Is message "\001ACTION foo bar\001"?
498 // Then change to: "/me foo bar"
499
500 if ((1 == c.Message[0]) && c.Message.Substring(1).StartsWith("ACTION"))
501 c.Message = String.Format("/me {0}", c.Message.Substring(8, c.Message.Length - 9));
502
503 ChannelState.OSChat(this, c, false);
504
505 }
506
507 }
508 else
509 {
510 ProcessIRCCommand(inputLine);
511 }
512 }
513 }
514 catch (Exception /*e*/)
515 {
516 // m_log.ErrorFormat("[IRC-Connector-{0}]: ListenerRun exception trap: {1}", idn, e.Message);
517 // m_log.Debug(e);
518 }
519
520 if (m_enabled) Reconnect();
521
522 }
523
524 private Dictionary<string, string> ExtractMsg(string input)
525 {
526 //examines IRC commands and extracts any private messages
527 // which will then be reboadcast in the Sim
528
529 // m_log.InfoFormat("[IRC-Connector-{0}]: ExtractMsg: {1}", idn, input);
530
531 Dictionary<string, string> result = null;
532 string regex = @":(?<nick>[\w-]*)!(?<user>\S*) PRIVMSG (?<channel>\S+) :(?<msg>.*)";
533 Regex RE = new Regex(regex, RegexOptions.Multiline);
534 MatchCollection matches = RE.Matches(input);
535
536 // Get some direct matches $1 $4 is a
537 if ((matches.Count == 0) || (matches.Count != 1) || (matches[0].Groups.Count != 5))
538 {
539 // m_log.Info("[IRCConnector]: Number of matches: " + matches.Count);
540 // if (matches.Count > 0)
541 // {
542 // m_log.Info("[IRCConnector]: Number of groups: " + matches[0].Groups.Count);
543 // }
544 return null;
545 }
546
547 result = new Dictionary<string, string>();
548 result.Add("nick", matches[0].Groups[1].Value);
549 result.Add("user", matches[0].Groups[2].Value);
550 result.Add("channel", matches[0].Groups[3].Value);
551 result.Add("msg", matches[0].Groups[4].Value);
552
553 return result;
554 }
555
556 public void BroadcastSim(string sender, string format, params string[] args)
557 {
558 try
559 {
560 OSChatMessage c = new OSChatMessage();
561 c.From = sender;
562 c.Message = String.Format(format, args);
563 c.Type = ChatTypeEnum.Region; // ChatTypeEnum.Say;
564 c.Position = CenterOfRegion;
565 c.Sender = null;
566 c.SenderUUID = UUID.Zero;
567
568 ChannelState.OSChat(this, c, true);
569
570 }
571 catch (Exception ex) // IRC gate should not crash Sim
572 {
573 m_log.ErrorFormat("[IRC-Connector-{0}]: BroadcastSim Exception Trap: {1}\n{2}", idn, ex.Message, ex.StackTrace);
574 }
575 }
576
577 #region IRC Command Handlers
578
579 public void ProcessIRCCommand(string command)
580 {
581
582 string[] commArgs;
583 string c_server = m_server;
584
585 string pfx = String.Empty;
586 string cmd = String.Empty;
587 string parms = String.Empty;
588
589 // ":" indicates that a prefix is present
590 // There are NEVER more than 17 real
591 // fields. A parameter that starts with
592 // ":" indicates that the remainder of the
593 // line is a single parameter value.
594
595 commArgs = command.Split(CS_SPACE,2);
596
597 if (commArgs[0].StartsWith(":"))
598 {
599 pfx = commArgs[0].Substring(1);
600 commArgs = commArgs[1].Split(CS_SPACE,2);
601 }
602
603 cmd = commArgs[0];
604 parms = commArgs[1];
605
606 // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}>", idn, pfx, cmd);
607
608 switch (cmd)
609 {
610
611 // Messages 001-004 are always sent
612 // following signon.
613
614 case "001" : // Welcome ...
615 case "002" : // Server information
616 case "003" : // Welcome ...
617 break;
618 case "004" : // Server information
619 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
620 commArgs = parms.Split(CS_SPACE);
621 c_server = commArgs[1];
622 m_server = c_server;
623 version = commArgs[2];
624 usermod = commArgs[3];
625 chanmod = commArgs[4];
626 break;
627 case "005" : // Server information
628 break;
629 case "042" :
630 case "250" :
631 case "251" :
632 case "252" :
633 case "254" :
634 case "255" :
635 case "265" :
636 case "266" :
637 case "332" : // Subject
638 case "333" : // Subject owner (?)
639 case "353" : // Name list
640 case "366" : // End-of-Name list marker
641 case "372" : // MOTD body
642 case "375" : // MOTD start
643 m_log.InfoFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
644 break;
645 case "376" : // MOTD end
646 m_log.InfoFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
647 motd = true;
648 break;
649 case "451" : // Not registered
650 break;
651 case "433" : // Nickname in use
652 // Gen a new name
653 m_nick = m_baseNick + Util.RandomClass.Next(1, 99);
654 m_log.ErrorFormat("[IRC-Connector-{0}]: IRC SERVER reports NicknameInUse, trying {1}", idn, m_nick);
655 // Retry
656 m_writer.WriteLine(String.Format("NICK {0}", m_nick));
657 m_writer.Flush();
658 m_writer.WriteLine(m_user);
659 m_writer.Flush();
660 m_writer.WriteLine(String.Format("JOIN {0}", m_ircChannel));
661 m_writer.Flush();
662 break;
663 case "NOTICE" :
664 m_log.WarnFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
665 break;
666 case "ERROR" :
667 m_log.ErrorFormat("[IRC-Connector-{0}] {1}", idn, parms.Split(CS_SPACE,2)[1]);
668 if (parms.Contains("reconnect too fast"))
669 ICCD_PERIOD++;
670 break;
671 case "PING" :
672 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
673 m_writer.WriteLine(String.Format("PONG {0}", parms));
674 m_writer.Flush();
675 break;
676 case "PONG" :
677 break;
678 case "JOIN":
679 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
680 eventIrcJoin(pfx, cmd, parms);
681 break;
682 case "PART":
683 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
684 eventIrcPart(pfx, cmd, parms);
685 break;
686 case "MODE":
687 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
688 eventIrcMode(pfx, cmd, parms);
689 break;
690 case "NICK":
691 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
692 eventIrcNickChange(pfx, cmd, parms);
693 break;
694 case "KICK":
695 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
696 eventIrcKick(pfx, cmd, parms);
697 break;
698 case "QUIT":
699 m_log.DebugFormat("[IRC-Connector-{0}] parms = <{1}>", idn, parms);
700 eventIrcQuit(pfx, cmd, parms);
701 break;
702 default :
703 m_log.DebugFormat("[IRC-Connector-{0}] Command '{1}' ignored, parms = {2}", idn, cmd, parms);
704 break;
705 }
706
707 // m_log.DebugFormat("[IRC-Connector-{0}] prefix = <{1}> cmd = <{2}> complete", idn, pfx, cmd);
708
709 }
710
711 public void eventIrcJoin(string prefix, string command, string parms)
712 {
713 string[] args = parms.Split(CS_SPACE,2);
714 string IrcUser = prefix.Split('!')[0];
715 string IrcChannel = args[0];
716
717 if (IrcChannel.StartsWith(":"))
718 IrcChannel = IrcChannel.Substring(1);
719
720 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCJoin {1}:{2}", idn, m_server, m_ircChannel);
721 BroadcastSim(IrcUser, "/me joins {0}", IrcChannel);
722 }
723
724 public void eventIrcPart(string prefix, string command, string parms)
725 {
726 string[] args = parms.Split(CS_SPACE,2);
727 string IrcUser = prefix.Split('!')[0];
728 string IrcChannel = args[0];
729
730 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCPart {1}:{2}", idn, m_server, m_ircChannel);
731 BroadcastSim(IrcUser, "/me parts {0}", IrcChannel);
732 }
733
734 public void eventIrcMode(string prefix, string command, string parms)
735 {
736 string[] args = parms.Split(CS_SPACE,2);
737 string UserMode = args[1];
738
739 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCMode {1}:{2}", idn, m_server, m_ircChannel);
740 if (UserMode.Substring(0, 1) == ":")
741 {
742 UserMode = UserMode.Remove(0, 1);
743 }
744 }
745
746 public void eventIrcNickChange(string prefix, string command, string parms)
747 {
748 string[] args = parms.Split(CS_SPACE,2);
749 string UserOldNick = prefix.Split('!')[0];
750 string UserNewNick = args[0].Remove(0, 1);
751
752 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCNickChange {1}:{2}", idn, m_server, m_ircChannel);
753 BroadcastSim(UserOldNick, "/me is now known as {0}", UserNewNick);
754 }
755
756 public void eventIrcKick(string prefix, string command, string parms)
757 {
758 string[] args = parms.Split(CS_SPACE,3);
759 string UserKicker = prefix.Split('!')[0];
760 string IrcChannel = args[0];
761 string UserKicked = args[1];
762 string KickMessage = args[2];
763
764 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCKick {1}:{2}", idn, m_server, m_ircChannel);
765 BroadcastSim(UserKicker, "/me kicks kicks {0} off {1} saying \"{2}\"", UserKicked, IrcChannel, KickMessage);
766
767 if (UserKicked == m_nick)
768 {
769 BroadcastSim(m_nick, "Hey, that was me!!!");
770 }
771
772 }
773
774 public void eventIrcQuit(string prefix, string command, string parms)
775 {
776 string IrcUser = prefix.Split('!')[0];
777 string QuitMessage = parms;
778
779 m_log.DebugFormat("[IRC-Connector-{0}] Event: IRCQuit {1}:{2}", idn, m_server, m_ircChannel);
780 BroadcastSim(IrcUser, "/me quits saying \"{0}\"", QuitMessage);
781 }
782
783 #endregion
784
785 #region Connector Watch Dog
786
787 // A single watch dog monitors extant connectors and makes sure that they
788 // are re-connected as necessary. If a connector IS connected, then it is
789 // pinged, but only if a PING period has elapsed.
790
791 protected static void WatchdogHandler(Object source, ElapsedEventArgs args)
792 {
793
794 // m_log.InfoFormat("[IRC-Watchdog] Status scan");
795
796 _pdk_ = (_pdk_+1)%PING_PERIOD; // cycle the ping trigger
797 _icc_++; // increment the inter-consecutive-connect-delay counter
798
799 foreach (XIRCConnector connector in m_connectors)
800 {
801 if (connector.Enabled)
802 {
803 if (!connector.Connected)
804 {
805 try
806 {
807 // m_log.DebugFormat("[IRC-Watchdog] Connecting {1}:{2}", connector.idn, connector.m_server, connector.m_ircChannel);
808 connector.Connect();
809 }
810 catch (Exception e)
811 {
812 m_log.ErrorFormat("[IRC-Watchdog] Exception on connector {0}: {1} ", connector.idn, e.Message);
813 }
814 }
815 else
816 {
817 if (_pdk_ == 0)
818 {
819 try
820 {
821 connector.m_writer.WriteLine(String.Format("PING :{0}", connector.m_server));
822 connector.m_writer.Flush();
823 }
824 catch (Exception /*e*/)
825 {
826 // m_log.ErrorFormat("[IRC-PingRun] Exception on connector {0}: {1} ", connector.idn, e.Message);
827 // m_log.Debug(e);
828 connector.Reconnect();
829 }
830 }
831 }
832 }
833 }
834
835 // m_log.InfoFormat("[IRC-Watchdog] Status scan completed");
836
837 }
838
839 #endregion
840
841 }
842}