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