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