aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/OptionalModules/Avatar
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/OptionalModules/Avatar')
-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
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs604
-rwxr-xr-xOpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py130
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs292
-rw-r--r--OpenSim/Region/OptionalModules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs202
8 files changed, 3386 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}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs
new file mode 100644
index 0000000..bb46b11
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeModule.cs
@@ -0,0 +1,604 @@
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.IO;
32using System.Net;
33using System.Net.Sockets;
34using System.Reflection;
35using System.Text;
36using System.Text.RegularExpressions;
37using System.Threading;
38using log4net;
39using Nini.Config;
40using Nwc.XmlRpc;
41using OpenMetaverse;
42using OpenSim.Framework;
43using OpenSim.Framework.Servers;
44using OpenSim.Region.Framework.Interfaces;
45using OpenSim.Region.Framework.Scenes;
46using OpenSim.Region.CoreModules.Avatar.Chat;
47
48namespace OpenSim.Region.OptionalModules.Avatar.Concierge
49{
50 public class ConciergeModule : ChatModule, IRegionModule
51 {
52 private static readonly ILog _log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
53
54 private const int DEBUG_CHANNEL = 2147483647;
55
56 private List<IScene> _scenes = new List<IScene>();
57 private List<IScene> _conciergedScenes = new List<IScene>();
58 private Dictionary<IScene, List<UUID>> _sceneAttendees =
59 new Dictionary<IScene, List<UUID>>();
60 private Dictionary<UUID, string> _attendeeNames =
61 new Dictionary<UUID, string>();
62
63 private bool _replacingChatModule = false;
64
65 private IConfig _config;
66
67 private string _whoami = "conferencier";
68 private Regex _regions = null;
69 private string _welcomes = null;
70 private int _conciergeChannel = 42;
71 private string _announceEntering = "{0} enters {1} (now {2} visitors in this region)";
72 private string _announceLeaving = "{0} leaves {1} (back to {2} visitors in this region)";
73 private string _xmlRpcPassword = String.Empty;
74 private string _brokerURI = String.Empty;
75
76 internal object _syncy = new object();
77
78 #region IRegionModule Members
79 public override void Initialise(Scene scene, IConfigSource config)
80 {
81 try
82 {
83 if ((_config = config.Configs["Concierge"]) == null)
84 {
85 //_log.InfoFormat("[Concierge]: no configuration section [Concierge] in OpenSim.ini: module not configured");
86 return;
87 }
88
89 if (!_config.GetBoolean("enabled", false))
90 {
91 //_log.InfoFormat("[Concierge]: module disabled by OpenSim.ini configuration");
92 return;
93 }
94 }
95 catch (Exception)
96 {
97 _log.Info("[Concierge]: module not configured");
98 return;
99 }
100
101 // check whether ChatModule has been disabled: if yes,
102 // then we'll "stand in"
103 try
104 {
105 if (config.Configs["Chat"] == null)
106 {
107 _replacingChatModule = false;
108 }
109 else
110 {
111 _replacingChatModule = !config.Configs["Chat"].GetBoolean("enabled", true);
112 }
113 }
114 catch (Exception)
115 {
116 _replacingChatModule = false;
117 }
118 _log.InfoFormat("[Concierge] {0} ChatModule", _replacingChatModule ? "replacing" : "not replacing");
119
120
121 // take note of concierge channel and of identity
122 _conciergeChannel = config.Configs["Concierge"].GetInt("concierge_channel", _conciergeChannel);
123 _whoami = _config.GetString("whoami", "conferencier");
124 _welcomes = _config.GetString("welcomes", _welcomes);
125 _announceEntering = _config.GetString("announce_entering", _announceEntering);
126 _announceLeaving = _config.GetString("announce_leaving", _announceLeaving);
127 _xmlRpcPassword = _config.GetString("password", _xmlRpcPassword);
128 _brokerURI = _config.GetString("broker", _brokerURI);
129
130 _log.InfoFormat("[Concierge] reporting as \"{0}\" to our users", _whoami);
131
132 // calculate regions Regex
133 if (_regions == null)
134 {
135 string regions = _config.GetString("regions", String.Empty);
136 if (!String.IsNullOrEmpty(regions))
137 {
138 _regions = new Regex(@regions, RegexOptions.Compiled | RegexOptions.IgnoreCase);
139 }
140 }
141
142 scene.CommsManager.HttpServer.AddXmlRPCHandler("concierge_update_welcome", XmlRpcUpdateWelcomeMethod, false);
143
144 lock (_syncy)
145 {
146 if (!_scenes.Contains(scene))
147 {
148 _scenes.Add(scene);
149
150 if (_regions == null || _regions.IsMatch(scene.RegionInfo.RegionName))
151 _conciergedScenes.Add(scene);
152
153 // subscribe to NewClient events
154 scene.EventManager.OnNewClient += OnNewClient;
155
156 // subscribe to *Chat events
157 scene.EventManager.OnChatFromWorld += OnChatFromWorld;
158 if (!_replacingChatModule)
159 scene.EventManager.OnChatFromClient += OnChatFromClient;
160 scene.EventManager.OnChatBroadcast += OnChatBroadcast;
161
162 // subscribe to agent change events
163 scene.EventManager.OnMakeRootAgent += OnMakeRootAgent;
164 scene.EventManager.OnMakeChildAgent += OnMakeChildAgent;
165 }
166 }
167 _log.InfoFormat("[Concierge]: initialized for {0}", scene.RegionInfo.RegionName);
168 }
169
170 public override void PostInitialise()
171 {
172 }
173
174 public override void Close()
175 {
176 }
177
178 public override string Name
179 {
180 get { return "ConciergeModule"; }
181 }
182
183 public override bool IsSharedModule
184 {
185 get { return true; }
186 }
187
188 #endregion
189
190 #region ISimChat Members
191 public override void OnChatBroadcast(Object sender, OSChatMessage c)
192 {
193 if (_replacingChatModule)
194 {
195 // distribute chat message to each and every avatar in
196 // the region
197 base.OnChatBroadcast(sender, c);
198 }
199
200 // TODO: capture logic
201 return;
202 }
203
204 public override void OnChatFromClient(Object sender, OSChatMessage c)
205 {
206 if (_replacingChatModule)
207 {
208 // replacing ChatModule: need to redistribute
209 // ChatFromClient to interested subscribers
210 c = FixPositionOfChatMessage(c);
211
212 Scene scene = (Scene)c.Scene;
213 scene.EventManager.TriggerOnChatFromClient(sender, c);
214
215 if (_conciergedScenes.Contains(c.Scene))
216 {
217 // when we are replacing ChatModule, we treat
218 // OnChatFromClient like OnChatBroadcast for
219 // concierged regions, effectively extending the
220 // range of chat to cover the whole
221 // region. however, we don't do this for whisper
222 // (got to have some privacy)
223 if (c.Type != ChatTypeEnum.Whisper)
224 {
225 base.OnChatBroadcast(sender, c);
226 return;
227 }
228 }
229
230 // redistribution will be done by base class
231 base.OnChatFromClient(sender, c);
232 }
233
234 // TODO: capture chat
235 return;
236 }
237
238 public override void OnChatFromWorld(Object sender, OSChatMessage c)
239 {
240 if (_replacingChatModule)
241 {
242 if (_conciergedScenes.Contains(c.Scene))
243 {
244 // when we are replacing ChatModule, we treat
245 // OnChatFromClient like OnChatBroadcast for
246 // concierged regions, effectively extending the
247 // range of chat to cover the whole
248 // region. however, we don't do this for whisper
249 // (got to have some privacy)
250 if (c.Type != ChatTypeEnum.Whisper)
251 {
252 base.OnChatBroadcast(sender, c);
253 return;
254 }
255 }
256
257 base.OnChatFromWorld(sender, c);
258 }
259 return;
260 }
261 #endregion
262
263
264 public override void OnNewClient(IClientAPI client)
265 {
266 client.OnLogout += OnClientLoggedOut;
267
268 if (_replacingChatModule)
269 client.OnChatFromClient += OnChatFromClient;
270 }
271
272
273
274 public void OnClientLoggedOut(IClientAPI client)
275 {
276 client.OnLogout -= OnClientLoggedOut;
277 client.OnConnectionClosed -= OnClientLoggedOut;
278
279 if (_conciergedScenes.Contains(client.Scene))
280 {
281 _log.DebugFormat("[Concierge]: {0} logs off from {1}", client.Name, client.Scene.RegionInfo.RegionName);
282 RemoveFromAttendeeList(client.AgentId, client.Name, client.Scene);
283 AnnounceToAgentsRegion(client.Scene, String.Format(_announceLeaving, client.Name, client.Scene.RegionInfo.RegionName,
284 _sceneAttendees[client.Scene].Count));
285 UpdateBroker(client.Scene);
286 }
287 }
288
289
290 public void OnMakeRootAgent(ScenePresence agent)
291 {
292 if (_conciergedScenes.Contains(agent.Scene))
293 {
294 _log.DebugFormat("[Concierge]: {0} enters {1}", agent.Name, agent.Scene.RegionInfo.RegionName);
295 AddToAttendeeList(agent.UUID, agent.Name, agent.Scene);
296 WelcomeAvatar(agent, agent.Scene);
297 AnnounceToAgentsRegion(agent.Scene, String.Format(_announceEntering, agent.Name, agent.Scene.RegionInfo.RegionName,
298 _sceneAttendees[agent.Scene].Count));
299 UpdateBroker(agent.Scene);
300 }
301 }
302
303
304 public void OnMakeChildAgent(ScenePresence agent)
305 {
306 if (_conciergedScenes.Contains(agent.Scene))
307 {
308 _log.DebugFormat("[Concierge]: {0} leaves {1}", agent.Name, agent.Scene.RegionInfo.RegionName);
309 RemoveFromAttendeeList(agent.UUID, agent.Name, agent.Scene);
310 AnnounceToAgentsRegion(agent.Scene, String.Format(_announceLeaving, agent.Name, agent.Scene.RegionInfo.RegionName,
311 _sceneAttendees[agent.Scene].Count));
312 UpdateBroker(agent.Scene);
313 }
314 }
315
316 protected void AddToAttendeeList(UUID agentID, string name, Scene scene)
317 {
318 lock (_sceneAttendees)
319 {
320 if (!_sceneAttendees.ContainsKey(scene))
321 _sceneAttendees[scene] = new List<UUID>();
322
323 List<UUID> attendees = _sceneAttendees[scene];
324 if (!attendees.Contains(agentID))
325 {
326 attendees.Add(agentID);
327 _attendeeNames[agentID] = name;
328 }
329 }
330 }
331
332 protected void RemoveFromAttendeeList(UUID agentID, String name, IScene scene)
333 {
334 lock (_sceneAttendees)
335 {
336 if (!_sceneAttendees.ContainsKey(scene))
337 {
338 _log.WarnFormat("[Concierge]: attendee list missing for region {0}", scene.RegionInfo.RegionName);
339 return;
340 }
341
342 List<UUID> attendees = _sceneAttendees[scene];
343 if (!attendees.Contains(agentID))
344 {
345 _log.WarnFormat("[Concierge]: avatar {0} must have sneaked in to region {1} earlier",
346 name, scene.RegionInfo.RegionName);
347 return;
348 }
349
350 attendees.Remove(agentID);
351 _attendeeNames.Remove(agentID);
352 }
353 }
354
355 protected void UpdateBroker(IScene scene)
356 {
357 if (String.IsNullOrEmpty(_brokerURI))
358 return;
359
360 string uri = String.Format(_brokerURI, scene.RegionInfo.RegionName, scene.RegionInfo.RegionID);
361
362 // get attendee list for the scene
363 List<UUID> attendees;
364 lock (_sceneAttendees)
365 {
366 if (!_sceneAttendees.ContainsKey(scene))
367 {
368 _log.DebugFormat("[Concierge]: attendee list missing for region {0}", scene.RegionInfo.RegionName);
369 return;
370 }
371
372 attendees = _sceneAttendees[scene];
373 }
374
375 // create XML sniplet
376 StringBuilder list = new StringBuilder();
377 if (0 == attendees.Count)
378 {
379 list.Append(String.Format("<avatars count=\"0\" region_name=\"{0}\" region_uuid=\"{1}\" timestamp=\"{2}\" />",
380 scene.RegionInfo.RegionName, scene.RegionInfo.RegionID,
381 DateTime.UtcNow.ToString("s")));
382 }
383 else
384 {
385 list.Append(String.Format("<avatars count=\"{0}\" region_name=\"{1}\" region_uuid=\"{2}\" timestamp=\"{3}\">\n",
386 attendees.Count, scene.RegionInfo.RegionName,
387 scene.RegionInfo.RegionID,
388 DateTime.UtcNow.ToString("s")));
389 foreach (UUID uuid in attendees)
390 {
391 string name = _attendeeNames[uuid];
392 list.Append(String.Format(" <avatar name=\"{0}\" uuid=\"{1}\" />\n", name, uuid));
393 }
394 list.Append("</avatars>");
395 }
396 string payload = list.ToString();
397
398 // post via REST to broker
399 HttpWebRequest updatePost = WebRequest.Create(uri) as HttpWebRequest;
400 updatePost.Method = "POST";
401 updatePost.ContentType = "text/xml";
402 updatePost.ContentLength = payload.Length;
403 updatePost.UserAgent = "OpenSim.Concierge";
404
405 try
406 {
407 StreamWriter payloadStream = new StreamWriter(updatePost.GetRequestStream());
408 payloadStream.Write(payload);
409 payloadStream.Close();
410
411 updatePost.BeginGetResponse(UpdateBrokerDone, updatePost);
412 _log.DebugFormat("[Concierge] async broker POST to {0} started", uri);
413 }
414 catch (WebException we)
415 {
416 _log.ErrorFormat("[Concierge] async broker POST to {0} failed: {1}", uri, we.Status);
417 }
418 }
419
420 private void UpdateBrokerDone(IAsyncResult result)
421 {
422 HttpWebRequest updatePost = null;
423 try
424 {
425 updatePost = result.AsyncState as HttpWebRequest;
426 using (HttpWebResponse response = updatePost.EndGetResponse(result) as HttpWebResponse)
427 {
428 _log.DebugFormat("[Concierge] broker update: status {0}", response.StatusCode);
429 }
430 }
431 catch (WebException we)
432 {
433 string uri = updatePost.RequestUri.OriginalString;
434 _log.ErrorFormat("[Concierge] broker update to {0} failed with status {1}", uri, we.Status);
435 if (null != we.Response)
436 {
437 using (HttpWebResponse resp = we.Response as HttpWebResponse)
438 {
439 _log.ErrorFormat("[Concierge] response from {0} status code: {1}", uri, resp.StatusCode);
440 _log.ErrorFormat("[Concierge] response from {0} status desc: {1}", uri, resp.StatusDescription);
441 _log.ErrorFormat("[Concierge] response from {0} server: {1}", uri, resp.Server);
442
443 if (resp.ContentLength > 0)
444 {
445 StreamReader content = new StreamReader(resp.GetResponseStream());
446 _log.ErrorFormat("[Concierge] response from {0} content: {1}", uri, content.ReadToEnd());
447 content.Close();
448 }
449 }
450 }
451 }
452 }
453
454 protected void WelcomeAvatar(ScenePresence agent, Scene scene)
455 {
456 // welcome mechanics: check whether we have a welcomes
457 // directory set and wether there is a region specific
458 // welcome file there: if yes, send it to the agent
459 if (!String.IsNullOrEmpty(_welcomes))
460 {
461 string[] welcomes = new string[] {
462 Path.Combine(_welcomes, agent.Scene.RegionInfo.RegionName),
463 Path.Combine(_welcomes, "DEFAULT")};
464 foreach (string welcome in welcomes)
465 {
466 if (File.Exists(welcome))
467 {
468 try
469 {
470 string[] welcomeLines = File.ReadAllLines(welcome);
471 foreach (string l in welcomeLines)
472 {
473 AnnounceToAgent(agent, String.Format(l, agent.Name, scene.RegionInfo.RegionName, _whoami));
474 }
475 }
476 catch (IOException ioe)
477 {
478 _log.ErrorFormat("[Concierge]: run into trouble reading welcome file {0} for region {1} for avatar {2}: {3}",
479 welcome, scene.RegionInfo.RegionName, agent.Name, ioe);
480 }
481 catch (FormatException fe)
482 {
483 _log.ErrorFormat("[Concierge]: welcome file {0} is malformed: {1}", welcome, fe);
484 }
485 }
486 return;
487 }
488 _log.DebugFormat("[Concierge]: no welcome message for region {0}", scene.RegionInfo.RegionName);
489 }
490 }
491
492 static private Vector3 PosOfGod = new Vector3(128, 128, 9999);
493
494 // protected void AnnounceToAgentsRegion(Scene scene, string msg)
495 // {
496 // ScenePresence agent = null;
497 // if ((client.Scene is Scene) && (client.Scene as Scene).TryGetAvatar(client.AgentId, out agent))
498 // AnnounceToAgentsRegion(agent, msg);
499 // else
500 // _log.DebugFormat("[Concierge]: could not find an agent for client {0}", client.Name);
501 // }
502
503 protected void AnnounceToAgentsRegion(IScene scene, string msg)
504 {
505 OSChatMessage c = new OSChatMessage();
506 c.Message = msg;
507 c.Type = ChatTypeEnum.Say;
508 c.Channel = 0;
509 c.Position = PosOfGod;
510 c.From = _whoami;
511 c.Sender = null;
512 c.SenderUUID = UUID.Zero;
513 c.Scene = scene;
514
515 if (scene is Scene)
516 (scene as Scene).EventManager.TriggerOnChatBroadcast(this, c);
517 }
518
519 protected void AnnounceToAgent(ScenePresence agent, string msg)
520 {
521 OSChatMessage c = new OSChatMessage();
522 c.Message = msg;
523 c.Type = ChatTypeEnum.Say;
524 c.Channel = 0;
525 c.Position = PosOfGod;
526 c.From = _whoami;
527 c.Sender = null;
528 c.SenderUUID = UUID.Zero;
529 c.Scene = agent.Scene;
530
531 agent.ControllingClient.SendChatMessage(msg, (byte) ChatTypeEnum.Say, PosOfGod, _whoami, UUID.Zero,
532 (byte)ChatSourceType.Object, (byte)ChatAudibleLevel.Fully);
533 }
534
535 private static void checkStringParameters(XmlRpcRequest request, string[] param)
536 {
537 Hashtable requestData = (Hashtable) request.Params[0];
538 foreach (string p in param)
539 {
540 if (!requestData.Contains(p))
541 throw new Exception(String.Format("missing string parameter {0}", p));
542 if (String.IsNullOrEmpty((string)requestData[p]))
543 throw new Exception(String.Format("parameter {0} is empty", p));
544 }
545 }
546
547 public XmlRpcResponse XmlRpcUpdateWelcomeMethod(XmlRpcRequest request)
548 {
549 _log.Info("[Concierge]: processing UpdateWelcome request");
550 XmlRpcResponse response = new XmlRpcResponse();
551 Hashtable responseData = new Hashtable();
552
553 try
554 {
555 Hashtable requestData = (Hashtable)request.Params[0];
556 checkStringParameters(request, new string[] { "password", "region", "welcome" });
557
558 // check password
559 if (!String.IsNullOrEmpty(_xmlRpcPassword) &&
560 (string)requestData["password"] != _xmlRpcPassword) throw new Exception("wrong password");
561
562 if (String.IsNullOrEmpty(_welcomes))
563 throw new Exception("welcome templates are not enabled, ask your OpenSim operator to set the \"welcomes\" option in the [Concierge] section of OpenSim.ini");
564
565 string msg = (string)requestData["welcome"];
566 if (String.IsNullOrEmpty(msg))
567 throw new Exception("empty parameter \"welcome\"");
568
569 string regionName = (string)requestData["region"];
570 IScene scene = _scenes.Find(delegate(IScene s) { return s.RegionInfo.RegionName == regionName; });
571 if (scene == null)
572 throw new Exception(String.Format("unknown region \"{0}\"", regionName));
573
574 if (!_conciergedScenes.Contains(scene))
575 throw new Exception(String.Format("region \"{0}\" is not a concierged region.", regionName));
576
577 string welcome = Path.Combine(_welcomes, regionName);
578 if (File.Exists(welcome))
579 {
580 _log.InfoFormat("[Concierge]: UpdateWelcome: updating existing template \"{0}\"", welcome);
581 string welcomeBackup = String.Format("{0}~", welcome);
582 if (File.Exists(welcomeBackup))
583 File.Delete(welcomeBackup);
584 File.Move(welcome, welcomeBackup);
585 }
586 File.WriteAllText(welcome, msg);
587
588 responseData["success"] = "true";
589 response.Value = responseData;
590 }
591 catch (Exception e)
592 {
593 _log.InfoFormat("[Concierge]: UpdateWelcome failed: {0}", e.Message);
594
595 responseData["success"] = "false";
596 responseData["error"] = e.Message;
597
598 response.Value = responseData;
599 }
600 _log.Debug("[Concierge]: done processing UpdateWelcome request");
601 return response;
602 }
603 }
604}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py
new file mode 100755
index 0000000..1c088fb
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Concierge/ConciergeServer.py
@@ -0,0 +1,130 @@
1#!/usr/bin/env python
2# -*- encoding: utf-8 -*-
3#
4# Copyright (c) Contributors, http://opensimulator.org/
5# See CONTRIBUTORS.TXT for a full list of copyright holders.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions are met:
9# * Redistributions of source code must retain the above copyright
10# notice, this list of conditions and the following disclaimer.
11# * Redistributions in binary form must reproduce the above copyright
12# notice, this list of conditions and the following disclaimer in the
13# documentation and/or other materials provided with the distribution.
14# * Neither the name of the OpenSim Project nor the
15# names of its contributors may be used to endorse or promote products
16# derived from this software without specific prior written permission.
17#
18# THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
19# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21# DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
22# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28#
29
30import logging
31import BaseHTTPServer
32import optparse
33import xml.etree.ElementTree as ET
34import xml.parsers.expat
35
36
37# enable debug level logging
38logging.basicConfig(level = logging.DEBUG,
39 format='%(asctime)s %(levelname)s %(message)s')
40
41options = None
42
43# subclassed HTTPRequestHandler
44class ConciergeHandler(BaseHTTPServer.BaseHTTPRequestHandler):
45 def logRequest(self):
46 logging.info('[ConciergeHandler] %(command)s request: %(host)s:%(port)d --- %(path)s',
47 dict(command = self.command,
48 host = self.client_address[0],
49 port = self.client_address[1],
50 path = self.path))
51
52 def logResponse(self, status):
53 logging.info('[ConciergeHandler] %(command)s returned %(status)d',
54 dict(command = self.command,
55 status = status))
56
57
58 def do_HEAD(self):
59 self.logRequest()
60
61 self.send_response(200)
62 self.send_header('Content-type', 'text/html')
63 self.end_headers()
64
65 self.logResponse(200)
66
67 def dumpXml(self, xml):
68 logging.debug('[ConciergeHandler] %s', xml.tag)
69 for attr in xml.attrib:
70 logging.debug('[ConciergeHandler] %s [%s] %s', xml.tag, attr, xml.attrib[attr])
71 for kid in xml.getchildren():
72 self.dumpXml(kid)
73
74 def do_POST(self):
75 self.logRequest()
76 hdrs = {}
77 for hdr in self.headers.headers:
78 logging.debug('[ConciergeHandler] POST: header: %s', hdr.rstrip())
79
80 length = int(self.headers.getheader('Content-Length'))
81 content = self.rfile.read(length)
82 self.rfile.close()
83
84 logging.debug('[ConciergeHandler] POST: content: %s', content)
85 try:
86 postXml = ET.fromstring(content)
87 self.dumpXml(postXml)
88 except xml.parsers.expat.ExpatError, xmlError:
89 logging.error('[ConciergeHandler] POST illformed:%s', xmlError)
90 self.send_response(500)
91 return
92
93 if not options.fail:
94 self.send_response(200)
95 self.send_header('Content-Type', 'text/html')
96 self.send_header('Content-Length', len('<success/>'))
97 self.end_headers()
98 self.logResponse(200)
99 self.wfile.write('<success/>')
100 self.wfile.close()
101 else:
102 self.send_response(500)
103 self.send_header('Content-Type', 'text/html')
104 self.send_header('Content-Length', len('<error>gotcha!</error>'))
105 self.end_headers()
106 self.wfile.write('<error>gotcha!</error>')
107 self.wfile.close()
108
109 self.logResponse(500)
110
111 def log_request(code, size):
112 pass
113
114if __name__ == '__main__':
115
116 logging.info('[ConciergeServer] Concierge Broker Test Server starting')
117
118 parser = optparse.OptionParser()
119 parser.add_option('-p', '--port', dest = 'port', help = 'port to listen on', metavar = 'PORT')
120 parser.add_option('-f', '--fail', dest = 'fail', action = 'store_true', help = 'always fail POST requests')
121
122 (options, args) = parser.parse_args()
123
124 httpServer = BaseHTTPServer.HTTPServer(('', 8080), ConciergeHandler)
125 try:
126 httpServer.serve_forever()
127 except KeyboardInterrupt:
128 logging.info('[ConciergeServer] terminating')
129
130 httpServer.server_close()
diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs
new file mode 100644
index 0000000..c827214
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Voice/AsterixVoice/AsteriskVoiceModule.cs
@@ -0,0 +1,292 @@
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.Reflection;
31using OpenMetaverse;
32using log4net;
33using Nini.Config;
34using Nwc.XmlRpc;
35using OpenSim.Framework;
36using OpenSim.Framework.Communications.Cache;
37using OpenSim.Framework.Communications.Capabilities;
38using OpenSim.Framework.Servers;
39using OpenSim.Region.Framework.Interfaces;
40using OpenSim.Region.Framework.Scenes;
41using Caps=OpenSim.Framework.Communications.Capabilities.Caps;
42
43namespace OpenSim.Region.OptionalModules.Avatar.Voice.AsterixVoice
44{
45 public class AsteriskVoiceModule : IRegionModule
46 {
47 private static readonly ILog m_log =
48 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
49
50 private static readonly string m_parcelVoiceInfoRequestPath = "0007/";
51 private static readonly string m_provisionVoiceAccountRequestPath = "0008/";
52
53 private string m_asterisk;
54 private string m_asterisk_password;
55 private string m_asterisk_salt;
56 private int m_asterisk_timeout;
57 private string m_confDomain;
58 private IConfig m_config;
59 private Scene m_scene;
60 private string m_sipDomain;
61
62 #region IRegionModule Members
63
64 public void Initialise(Scene scene, IConfigSource config)
65 {
66 m_scene = scene;
67 m_config = config.Configs["AsteriskVoice"];
68
69 if (null == m_config)
70 {
71 m_log.Info("[ASTERISKVOICE] no config found, plugin disabled");
72 return;
73 }
74
75 if (!m_config.GetBoolean("enabled", false))
76 {
77 m_log.Info("[ASTERISKVOICE] plugin disabled by configuration");
78 return;
79 }
80 m_log.Info("[ASTERISKVOICE] plugin enabled");
81
82 try
83 {
84 m_sipDomain = m_config.GetString("sip_domain", String.Empty);
85 m_log.InfoFormat("[ASTERISKVOICE] using SIP domain {0}", m_sipDomain);
86
87 m_confDomain = m_config.GetString("conf_domain", String.Empty);
88 m_log.InfoFormat("[ASTERISKVOICE] using conf domain {0}", m_confDomain);
89
90 m_asterisk = m_config.GetString("asterisk_frontend", String.Empty);
91 m_asterisk_password = m_config.GetString("asterisk_password", String.Empty);
92 m_asterisk_timeout = m_config.GetInt("asterisk_timeout", 3000);
93 m_asterisk_salt = m_config.GetString("asterisk_salt", "Wuffwuff");
94 if (String.IsNullOrEmpty(m_asterisk)) throw new Exception("missing asterisk_frontend config parameter");
95 if (String.IsNullOrEmpty(m_asterisk_password)) throw new Exception("missing asterisk_password config parameter");
96 m_log.InfoFormat("[ASTERISKVOICE] using asterisk front end {0}", m_asterisk);
97
98 scene.EventManager.OnRegisterCaps += OnRegisterCaps;
99 }
100 catch (Exception e)
101 {
102 m_log.ErrorFormat("[ASTERISKVOICE] plugin initialization failed: {0}", e.Message);
103 m_log.DebugFormat("[ASTERISKVOICE] plugin initialization failed: {0}", e.ToString());
104 return;
105 }
106 }
107
108 public void PostInitialise()
109 {
110 }
111
112 public void Close()
113 {
114 }
115
116 public string Name
117 {
118 get { return "AsteriskVoiceModule"; }
119 }
120
121 public bool IsSharedModule
122 {
123 get { return false; }
124 }
125
126 #endregion
127
128 public void OnRegisterCaps(UUID agentID, Caps caps)
129 {
130 m_log.DebugFormat("[ASTERISKVOICE] OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
131 string capsBase = "/CAPS/" + caps.CapsObjectPath;
132 caps.RegisterHandler("ParcelVoiceInfoRequest",
133 new RestStreamHandler("POST", capsBase + m_parcelVoiceInfoRequestPath,
134 delegate(string request, string path, string param,
135 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
136 {
137 return ParcelVoiceInfoRequest(request, path, param,
138 agentID, caps);
139 }));
140 caps.RegisterHandler("ProvisionVoiceAccountRequest",
141 new RestStreamHandler("POST", capsBase + m_provisionVoiceAccountRequestPath,
142 delegate(string request, string path, string param,
143 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
144 {
145 return ProvisionVoiceAccountRequest(request, path, param,
146 agentID, caps);
147 }));
148 }
149
150 /// <summary>
151 /// Callback for a client request for ParcelVoiceInfo
152 /// </summary>
153 /// <param name="request"></param>
154 /// <param name="path"></param>
155 /// <param name="param"></param>
156 /// <param name="agentID"></param>
157 /// <param name="caps"></param>
158 /// <returns></returns>
159 public string ParcelVoiceInfoRequest(string request, string path, string param,
160 UUID agentID, Caps caps)
161 {
162 // we need to do:
163 // - send channel_uri: as "sip:regionID@m_sipDomain"
164 try
165 {
166 m_log.DebugFormat("[ASTERISKVOICE][PARCELVOICE]: request: {0}, path: {1}, param: {2}",
167 request, path, param);
168
169
170 // setup response to client
171 Hashtable creds = new Hashtable();
172 creds["channel_uri"] = String.Format("sip:{0}@{1}",
173 m_scene.RegionInfo.RegionID, m_sipDomain);
174
175 string regionName = m_scene.RegionInfo.RegionName;
176 ScenePresence avatar = m_scene.GetScenePresence(agentID);
177 if (null == m_scene.LandChannel) throw new Exception("land data not yet available");
178 LandData land = m_scene.GetLandData(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y);
179
180 LLSDParcelVoiceInfoResponse parcelVoiceInfo =
181 new LLSDParcelVoiceInfoResponse(regionName, land.LocalID, creds);
182
183 string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
184
185
186 // update region on asterisk-opensim frontend
187 Hashtable requestData = new Hashtable();
188 requestData["admin_password"] = m_asterisk_password;
189 requestData["region"] = m_scene.RegionInfo.RegionID.ToString();
190 if (!String.IsNullOrEmpty(m_confDomain))
191 {
192 requestData["region"] += String.Format("@{0}", m_confDomain);
193 }
194
195 ArrayList SendParams = new ArrayList();
196 SendParams.Add(requestData);
197 XmlRpcRequest updateAccountRequest = new XmlRpcRequest("region_update", SendParams);
198 XmlRpcResponse updateAccountResponse = updateAccountRequest.Send(m_asterisk, m_asterisk_timeout);
199 Hashtable responseData = (Hashtable) updateAccountResponse.Value;
200
201 if (!responseData.ContainsKey("success")) throw new Exception("region_update call failed");
202
203 bool success = Convert.ToBoolean((string) responseData["success"]);
204 if (!success) throw new Exception("region_update failed");
205
206
207 m_log.DebugFormat("[ASTERISKVOICE][PARCELVOICE]: {0}", r);
208 return r;
209 }
210 catch (Exception e)
211 {
212 m_log.ErrorFormat("[ASTERISKVOICE][CAPS][PARCELVOICE]: {0}, retry later", e.Message);
213 m_log.DebugFormat("[ASTERISKVOICE][CAPS][PARCELVOICE]: {0} failed", e.ToString());
214
215 return "<llsd>undef</llsd>";
216 }
217 }
218
219 /// <summary>
220 /// Callback for a client request for Voice Account Details
221 /// </summary>
222 /// <param name="request"></param>
223 /// <param name="path"></param>
224 /// <param name="param"></param>
225 /// <param name="agentID"></param>
226 /// <param name="caps"></param>
227 /// <returns></returns>
228 public string ProvisionVoiceAccountRequest(string request, string path, string param,
229 UUID agentID, Caps caps)
230 {
231 // we need to
232 // - get user data from UserProfileCacheService
233 // - generate nonce for user voice account password
234 // - issue XmlRpc request to asterisk opensim front end:
235 // + user: base 64 encoded user name (otherwise SL
236 // client is unhappy)
237 // + password: nonce
238 // - the XmlRpc call to asteris-opensim was successful:
239 // send account details back to client
240 try
241 {
242 m_log.DebugFormat("[ASTERISKVOICE][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}",
243 request, path, param);
244
245 // get user data & prepare voice account response
246 string voiceUser = "x" + Convert.ToBase64String(agentID.GetBytes());
247 voiceUser = voiceUser.Replace('+', '-').Replace('/', '_');
248
249 CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(agentID);
250 if (null == userInfo) throw new Exception("cannot get user details");
251
252 // we generate a nonce everytime
253 string voicePassword = "$1$" + Util.Md5Hash(DateTime.UtcNow.ToLongTimeString() + m_asterisk_salt);
254 LLSDVoiceAccountResponse voiceAccountResponse =
255 new LLSDVoiceAccountResponse(voiceUser, voicePassword);
256 string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
257 m_log.DebugFormat("[CAPS][PROVISIONVOICE]: {0}", r);
258
259
260 // update user account on asterisk frontend
261 Hashtable requestData = new Hashtable();
262 requestData["admin_password"] = m_asterisk_password;
263 requestData["username"] = voiceUser;
264 if (!String.IsNullOrEmpty(m_sipDomain))
265 {
266 requestData["username"] += String.Format("@{0}", m_sipDomain);
267 }
268 requestData["password"] = voicePassword;
269
270 ArrayList SendParams = new ArrayList();
271 SendParams.Add(requestData);
272 XmlRpcRequest updateAccountRequest = new XmlRpcRequest("account_update", SendParams);
273 XmlRpcResponse updateAccountResponse = updateAccountRequest.Send(m_asterisk, m_asterisk_timeout);
274 Hashtable responseData = (Hashtable) updateAccountResponse.Value;
275
276 if (!responseData.ContainsKey("success")) throw new Exception("account_update call failed");
277
278 bool success = Convert.ToBoolean((string) responseData["success"]);
279 if (!success) throw new Exception("account_update failed");
280
281 return r;
282 }
283 catch (Exception e)
284 {
285 m_log.ErrorFormat("[ASTERISKVOICE][CAPS][PROVISIONVOICE]: {0}, retry later", e.Message);
286 m_log.DebugFormat("[ASTERISKVOICE][CAPS][PROVISIONVOICE]: {0} failed", e.ToString());
287
288 return "<llsd>undef</llsd>";
289 }
290 }
291 }
292}
diff --git a/OpenSim/Region/OptionalModules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs b/OpenSim/Region/OptionalModules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs
new file mode 100644
index 0000000..3e8a433
--- /dev/null
+++ b/OpenSim/Region/OptionalModules/Avatar/Voice/SIPVoice/SIPVoiceModule.cs
@@ -0,0 +1,202 @@
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.Reflection;
31using OpenMetaverse;
32using log4net;
33using Nini.Config;
34using OpenSim.Framework;
35using OpenSim.Framework.Communications.Cache;
36using OpenSim.Framework.Communications.Capabilities;
37using OpenSim.Framework.Servers;
38using OpenSim.Region.Framework.Interfaces;
39using OpenSim.Region.Framework.Scenes;
40using Caps=OpenSim.Framework.Communications.Capabilities.Caps;
41
42namespace OpenSim.Region.OptionalModules.Avatar.Voice.SIPVoice
43{
44 public class SIPVoiceModule : IRegionModule
45 {
46 private static readonly ILog m_log =
47 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
48
49 private static readonly string m_parcelVoiceInfoRequestPath = "0007/";
50 private static readonly string m_provisionVoiceAccountRequestPath = "0008/";
51 private IConfig m_config;
52 private Scene m_scene;
53 private string m_sipDomain;
54
55 #region IRegionModule Members
56
57 public void Initialise(Scene scene, IConfigSource config)
58 {
59 m_scene = scene;
60 m_config = config.Configs["Voice"];
61
62 if (null == m_config || !m_config.GetBoolean("enabled", false))
63 {
64 m_log.Info("[VOICE] plugin disabled");
65 return;
66 }
67 m_log.Info("[VOICE] plugin enabled");
68
69 m_sipDomain = m_config.GetString("sip_domain", String.Empty);
70 if (String.IsNullOrEmpty(m_sipDomain))
71 {
72 m_log.Error("[VOICE] plugin mis-configured: missing sip_domain configuration");
73 m_log.Info("[VOICE] plugin disabled");
74 return;
75 }
76 m_log.InfoFormat("[VOICE] using SIP domain {0}", m_sipDomain);
77
78 scene.EventManager.OnRegisterCaps += OnRegisterCaps;
79 }
80
81 public void PostInitialise()
82 {
83 }
84
85 public void Close()
86 {
87 }
88
89 public string Name
90 {
91 get { return "VoiceModule"; }
92 }
93
94 public bool IsSharedModule
95 {
96 get { return false; }
97 }
98
99 #endregion
100
101 public void OnRegisterCaps(UUID agentID, Caps caps)
102 {
103 m_log.DebugFormat("[VOICE] OnRegisterCaps: agentID {0} caps {1}", agentID, caps);
104 string capsBase = "/CAPS/" + caps.CapsObjectPath;
105 caps.RegisterHandler("ParcelVoiceInfoRequest",
106 new RestStreamHandler("POST", capsBase + m_parcelVoiceInfoRequestPath,
107 delegate(string request, string path, string param,
108 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
109 {
110 return ParcelVoiceInfoRequest(request, path, param,
111 agentID, caps);
112 }));
113 caps.RegisterHandler("ProvisionVoiceAccountRequest",
114 new RestStreamHandler("POST", capsBase + m_provisionVoiceAccountRequestPath,
115 delegate(string request, string path, string param,
116 OSHttpRequest httpRequest, OSHttpResponse httpResponse)
117 {
118 return ProvisionVoiceAccountRequest(request, path, param,
119 agentID, caps);
120 }));
121 }
122
123 /// <summary>
124 /// Callback for a client request for ParcelVoiceInfo
125 /// </summary>
126 /// <param name="request"></param>
127 /// <param name="path"></param>
128 /// <param name="param"></param>
129 /// <param name="agentID"></param>
130 /// <param name="caps"></param>
131 /// <returns></returns>
132 public string ParcelVoiceInfoRequest(string request, string path, string param,
133 UUID agentID, Caps caps)
134 {
135 try
136 {
137 m_log.DebugFormat("[VOICE][PARCELVOICE]: request: {0}, path: {1}, param: {2}", request, path, param);
138
139 // FIXME: get the creds from region file or from config
140 Hashtable creds = new Hashtable();
141
142 creds["channel_uri"] = String.Format("sip:{0}@{1}", agentID, m_sipDomain);
143
144 string regionName = m_scene.RegionInfo.RegionName;
145 ScenePresence avatar = m_scene.GetScenePresence(agentID);
146 if (null == m_scene.LandChannel) throw new Exception("land data not yet available");
147 LandData land = m_scene.GetLandData(avatar.AbsolutePosition.X, avatar.AbsolutePosition.Y);
148
149 LLSDParcelVoiceInfoResponse parcelVoiceInfo =
150 new LLSDParcelVoiceInfoResponse(regionName, land.LocalID, creds);
151
152 string r = LLSDHelpers.SerialiseLLSDReply(parcelVoiceInfo);
153 m_log.DebugFormat("[VOICE][PARCELVOICE]: {0}", r);
154
155 return r;
156 }
157 catch (Exception e)
158 {
159 m_log.ErrorFormat("[CAPS]: {0}, try again later", e.ToString());
160 }
161
162 return null;
163 }
164
165 /// <summary>
166 /// Callback for a client request for Voice Account Details
167 /// </summary>
168 /// <param name="request"></param>
169 /// <param name="path"></param>
170 /// <param name="param"></param>
171 /// <param name="agentID"></param>
172 /// <param name="caps"></param>
173 /// <returns></returns>
174 public string ProvisionVoiceAccountRequest(string request, string path, string param,
175 UUID agentID, Caps caps)
176 {
177 try
178 {
179 m_log.DebugFormat("[VOICE][PROVISIONVOICE]: request: {0}, path: {1}, param: {2}",
180 request, path, param);
181
182 string voiceUser = "x" + Convert.ToBase64String(agentID.GetBytes());
183 voiceUser = voiceUser.Replace('+', '-').Replace('/', '_');
184
185 CachedUserInfo userInfo = m_scene.CommsManager.UserProfileCacheService.GetUserDetails(agentID);
186 if (null == userInfo) throw new Exception("cannot get user details");
187
188 LLSDVoiceAccountResponse voiceAccountResponse =
189 new LLSDVoiceAccountResponse(voiceUser, "$1$" + userInfo.UserProfile.PasswordHash);
190 string r = LLSDHelpers.SerialiseLLSDReply(voiceAccountResponse);
191 m_log.DebugFormat("[CAPS][PROVISIONVOICE]: {0}", r);
192 return r;
193 }
194 catch (Exception e)
195 {
196 m_log.ErrorFormat("[CAPS][PROVISIONVOICE]: {0}, retry later", e.Message);
197 }
198
199 return null;
200 }
201 }
202}