OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
RegionState.cs
Go to the documentation of this file.
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 
28 using System;
29 using System.Collections.Generic;
30 using System.Reflection;
31 using System.Text.RegularExpressions;
32 using log4net;
33 using Nini.Config;
34 using OpenSim.Framework;
35 using OpenSim.Region.Framework.Interfaces;
36 using OpenSim.Region.Framework.Scenes;
37 
38 namespace 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 }
IClientAPI Sender
The client responsible for sending the message, or null.
string Name
Returns the full name of the agent/avatar represented by this client
Definition: IClientAPI.cs:754
string Message
The message sent by the user
ChatFromViewer Arguments
int Channel
Which channel was this message sent on? Different channels may have different listeners. Public chat is on channel zero.