diff options
Diffstat (limited to 'OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs')
-rw-r--r-- | OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs | 521 |
1 files changed, 521 insertions, 0 deletions
diff --git a/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs b/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs new file mode 100644 index 0000000..4bafe2f --- /dev/null +++ b/OpenSim/Region/OptionalModules/Scripting/Minimodule/MRMModule.cs | |||
@@ -0,0 +1,521 @@ | |||
1 | /* | ||
2 | * Copyright (c) Contributors, http://opensimulator.org/ | ||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. | ||
4 | * | ||
5 | * Redistribution and use in source and binary forms, with or without | ||
6 | * modification, are permitted provided that the following conditions are met: | ||
7 | * * Redistributions of source code must retain the above copyright | ||
8 | * notice, this list of conditions and the following disclaimer. | ||
9 | * * Redistributions in binary form must reproduce the above copyright | ||
10 | * notice, this list of conditions and the following disclaimer in the | ||
11 | * documentation and/or other materials provided with the distribution. | ||
12 | * * Neither the name of the OpenSimulator Project nor the | ||
13 | * names of its contributors may be used to endorse or promote products | ||
14 | * derived from this software without specific prior written permission. | ||
15 | * | ||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY | ||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY | ||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||
26 | */ | ||
27 | |||
28 | using System; | ||
29 | using System.CodeDom.Compiler; | ||
30 | using System.Collections; | ||
31 | using System.Collections.Generic; | ||
32 | using System.Diagnostics; | ||
33 | using System.IO; | ||
34 | using System.Reflection; | ||
35 | using System.Security; | ||
36 | using System.Security.Permissions; | ||
37 | using System.Security.Policy; | ||
38 | using System.Text; | ||
39 | using log4net; | ||
40 | using Microsoft.CSharp; | ||
41 | using Nini.Config; | ||
42 | using OpenMetaverse; | ||
43 | using OpenSim.Framework; | ||
44 | using OpenSim.Region.Framework.Interfaces; | ||
45 | using OpenSim.Region.Framework.Scenes; | ||
46 | using Mono.Addins; | ||
47 | |||
48 | namespace OpenSim.Region.OptionalModules.Scripting.Minimodule | ||
49 | { | ||
50 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "MRMModule")] | ||
51 | public class MRMModule : INonSharedRegionModule, IMRMModule | ||
52 | { | ||
53 | private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); | ||
54 | private Scene m_scene; | ||
55 | private bool m_Enabled; | ||
56 | private bool m_Hidden; | ||
57 | |||
58 | private readonly Dictionary<UUID,MRMBase> m_scripts = new Dictionary<UUID, MRMBase>(); | ||
59 | |||
60 | private readonly Dictionary<Type,object> m_extensions = new Dictionary<Type, object>(); | ||
61 | |||
62 | private static readonly CSharpCodeProvider CScodeProvider = new CSharpCodeProvider(); | ||
63 | |||
64 | private readonly MicroScheduler m_microthreads = new MicroScheduler(); | ||
65 | |||
66 | |||
67 | private IConfig m_config; | ||
68 | |||
69 | public void RegisterExtension<T>(T instance) | ||
70 | { | ||
71 | m_extensions[typeof (T)] = instance; | ||
72 | } | ||
73 | |||
74 | #region INonSharedRegionModule | ||
75 | |||
76 | public void Initialise(IConfigSource source) | ||
77 | { | ||
78 | if (source.Configs["MRM"] != null) | ||
79 | { | ||
80 | m_config = source.Configs["MRM"]; | ||
81 | |||
82 | if (source.Configs["MRM"].GetBoolean("Enabled", false)) | ||
83 | { | ||
84 | m_log.Info("[MRM]: Enabling MRM Module"); | ||
85 | m_Enabled = true; | ||
86 | m_Hidden = source.Configs["MRM"].GetBoolean("Hidden", false); | ||
87 | } | ||
88 | } | ||
89 | } | ||
90 | |||
91 | public void AddRegion(Scene scene) | ||
92 | { | ||
93 | if (!m_Enabled) | ||
94 | return; | ||
95 | |||
96 | m_scene = scene; | ||
97 | |||
98 | // when hidden, we don't listen for client initiated script events | ||
99 | // only making the MRM engine available for region modules | ||
100 | if (!m_Hidden) | ||
101 | { | ||
102 | scene.EventManager.OnRezScript += EventManager_OnRezScript; | ||
103 | scene.EventManager.OnStopScript += EventManager_OnStopScript; | ||
104 | } | ||
105 | |||
106 | scene.EventManager.OnFrame += EventManager_OnFrame; | ||
107 | |||
108 | scene.RegisterModuleInterface<IMRMModule>(this); | ||
109 | } | ||
110 | |||
111 | public void RegionLoaded(Scene scene) | ||
112 | { | ||
113 | } | ||
114 | |||
115 | public void RemoveRegion(Scene scene) | ||
116 | { | ||
117 | } | ||
118 | |||
119 | public void Close() | ||
120 | { | ||
121 | foreach (KeyValuePair<UUID, MRMBase> pair in m_scripts) | ||
122 | { | ||
123 | pair.Value.Stop(); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | public string Name | ||
128 | { | ||
129 | get { return "MiniRegionModule"; } | ||
130 | } | ||
131 | |||
132 | public Type ReplaceableInterface | ||
133 | { | ||
134 | get { return null; } | ||
135 | } | ||
136 | |||
137 | #endregion | ||
138 | |||
139 | void EventManager_OnStopScript(uint localID, UUID itemID) | ||
140 | { | ||
141 | if (m_scripts.ContainsKey(itemID)) | ||
142 | { | ||
143 | m_scripts[itemID].Stop(); | ||
144 | } | ||
145 | } | ||
146 | |||
147 | void EventManager_OnFrame() | ||
148 | { | ||
149 | m_microthreads.Tick(1000); | ||
150 | } | ||
151 | |||
152 | static string ConvertMRMKeywords(string script) | ||
153 | { | ||
154 | script = script.Replace("microthreaded void", "IEnumerable"); | ||
155 | script = script.Replace("relax;", "yield return null;"); | ||
156 | |||
157 | return script; | ||
158 | } | ||
159 | |||
160 | /// <summary> | ||
161 | /// Create an AppDomain that contains policy restricting code to execute | ||
162 | /// with only the permissions granted by a named permission set | ||
163 | /// </summary> | ||
164 | /// <param name="permissionSetName">name of the permission set to restrict to</param> | ||
165 | /// <param name="appDomainName">'friendly' name of the appdomain to be created</param> | ||
166 | /// <exception cref="ArgumentNullException"> | ||
167 | /// if <paramref name="permissionSetName"/> is null | ||
168 | /// </exception> | ||
169 | /// <exception cref="ArgumentOutOfRangeException"> | ||
170 | /// if <paramref name="permissionSetName"/> is empty | ||
171 | /// </exception> | ||
172 | /// <returns>AppDomain with a restricted security policy</returns> | ||
173 | /// <remarks>Substantial portions of this function from: http://blogs.msdn.com/shawnfa/archive/2004/10/25/247379.aspx | ||
174 | /// Valid permissionSetName values are: | ||
175 | /// * FullTrust | ||
176 | /// * SkipVerification | ||
177 | /// * Execution | ||
178 | /// * Nothing | ||
179 | /// * LocalIntranet | ||
180 | /// * Internet | ||
181 | /// * Everything | ||
182 | /// </remarks> | ||
183 | #pragma warning disable 0618 | ||
184 | public static AppDomain CreateRestrictedDomain(string permissionSetName, string appDomainName) | ||
185 | { | ||
186 | if (permissionSetName == null) | ||
187 | throw new ArgumentNullException("permissionSetName"); | ||
188 | if (permissionSetName.Length == 0) | ||
189 | throw new ArgumentOutOfRangeException("permissionSetName", permissionSetName, | ||
190 | "Cannot have an empty permission set name"); | ||
191 | |||
192 | // Default to all code getting nothing | ||
193 | PolicyStatement emptyPolicy = new PolicyStatement(new PermissionSet(PermissionState.None)); | ||
194 | UnionCodeGroup policyRoot = new UnionCodeGroup(new AllMembershipCondition(), emptyPolicy); | ||
195 | |||
196 | bool foundName = false; | ||
197 | PermissionSet setIntersection = new PermissionSet(PermissionState.Unrestricted); | ||
198 | |||
199 | // iterate over each policy level | ||
200 | IEnumerator levelEnumerator = SecurityManager.PolicyHierarchy(); | ||
201 | while (levelEnumerator.MoveNext()) | ||
202 | { | ||
203 | PolicyLevel level = levelEnumerator.Current as PolicyLevel; | ||
204 | |||
205 | // if this level has defined a named permission set with the | ||
206 | // given name, then intersect it with what we've retrieved | ||
207 | // from all the previous levels | ||
208 | if (level != null) | ||
209 | { | ||
210 | PermissionSet levelSet = level.GetNamedPermissionSet(permissionSetName); | ||
211 | if (levelSet != null) | ||
212 | { | ||
213 | foundName = true; | ||
214 | if (setIntersection != null) | ||
215 | setIntersection = setIntersection.Intersect(levelSet); | ||
216 | } | ||
217 | } | ||
218 | } | ||
219 | |||
220 | // Intersect() can return null for an empty set, so convert that | ||
221 | // to an empty set object. Also return an empty set if we didn't find | ||
222 | // the named permission set we were looking for | ||
223 | if (setIntersection == null || !foundName) | ||
224 | setIntersection = new PermissionSet(PermissionState.None); | ||
225 | else | ||
226 | setIntersection = new NamedPermissionSet(permissionSetName, setIntersection); | ||
227 | |||
228 | // if no named permission sets were found, return an empty set, | ||
229 | // otherwise return the set that was found | ||
230 | PolicyStatement permissions = new PolicyStatement(setIntersection); | ||
231 | policyRoot.AddChild(new UnionCodeGroup(new AllMembershipCondition(), permissions)); | ||
232 | |||
233 | // create an AppDomain policy level for the policy tree | ||
234 | PolicyLevel appDomainLevel = PolicyLevel.CreateAppDomainLevel(); | ||
235 | appDomainLevel.RootCodeGroup = policyRoot; | ||
236 | |||
237 | // create an AppDomain where this policy will be in effect | ||
238 | string domainName = appDomainName; | ||
239 | AppDomain restrictedDomain = AppDomain.CreateDomain(domainName); | ||
240 | restrictedDomain.SetAppDomainPolicy(appDomainLevel); | ||
241 | |||
242 | return restrictedDomain; | ||
243 | } | ||
244 | #pragma warning restore 0618 | ||
245 | |||
246 | |||
247 | void EventManager_OnRezScript(uint localID, UUID itemID, string script, int startParam, bool postOnRez, string engine, int stateSource) | ||
248 | { | ||
249 | if (script.StartsWith("//MRM:C#")) | ||
250 | { | ||
251 | if (m_config.GetBoolean("OwnerOnly", true)) | ||
252 | if (m_scene.GetSceneObjectPart(localID).OwnerID != m_scene.RegionInfo.EstateSettings.EstateOwner | ||
253 | || m_scene.GetSceneObjectPart(localID).CreatorID != m_scene.RegionInfo.EstateSettings.EstateOwner) | ||
254 | return; | ||
255 | |||
256 | script = ConvertMRMKeywords(script); | ||
257 | |||
258 | try | ||
259 | { | ||
260 | AppDomain target; | ||
261 | if (m_config.GetBoolean("Sandboxed", true)) | ||
262 | { | ||
263 | m_log.Info("[MRM] Found C# MRM - Starting in AppDomain with " + | ||
264 | m_config.GetString("SandboxLevel", "Internet") + "-level security."); | ||
265 | |||
266 | string domainName = UUID.Random().ToString(); | ||
267 | target = CreateRestrictedDomain(m_config.GetString("SandboxLevel", "Internet"), | ||
268 | domainName); | ||
269 | } | ||
270 | else | ||
271 | { | ||
272 | m_log.Info("[MRM] Found C# MRM - Starting in current AppDomain"); | ||
273 | m_log.Warn( | ||
274 | "[MRM] Security Risk: AppDomain is run in current context. Use only in trusted environments."); | ||
275 | target = AppDomain.CurrentDomain; | ||
276 | } | ||
277 | |||
278 | m_log.Info("[MRM] Unwrapping into target AppDomain"); | ||
279 | MRMBase mmb = (MRMBase) target.CreateInstanceFromAndUnwrap( | ||
280 | CompileFromDotNetText(script, itemID.ToString()), | ||
281 | "OpenSim.MiniModule"); | ||
282 | |||
283 | m_log.Info("[MRM] Initialising MRM Globals"); | ||
284 | InitializeMRM(mmb, localID, itemID); | ||
285 | |||
286 | m_scripts[itemID] = mmb; | ||
287 | |||
288 | m_log.Info("[MRM] Starting MRM"); | ||
289 | mmb.Start(); | ||
290 | } | ||
291 | catch (UnauthorizedAccessException e) | ||
292 | { | ||
293 | m_log.Error("[MRM] UAE " + e.Message); | ||
294 | m_log.Error("[MRM] " + e.StackTrace); | ||
295 | |||
296 | if (e.InnerException != null) | ||
297 | m_log.Error("[MRM] " + e.InnerException); | ||
298 | |||
299 | m_scene.ForEachClient(delegate(IClientAPI user) | ||
300 | { | ||
301 | user.SendAlertMessage( | ||
302 | "MRM UnAuthorizedAccess: " + e); | ||
303 | }); | ||
304 | } | ||
305 | catch (Exception e) | ||
306 | { | ||
307 | m_log.Info("[MRM] Error: " + e); | ||
308 | m_scene.ForEachClient(delegate(IClientAPI user) | ||
309 | { | ||
310 | user.SendAlertMessage( | ||
311 | "Compile error while building MRM script, check OpenSim console for more information."); | ||
312 | }); | ||
313 | } | ||
314 | } | ||
315 | } | ||
316 | |||
317 | public void GetGlobalEnvironment(uint localID, out IWorld world, out IHost host) | ||
318 | { | ||
319 | // UUID should be changed to object owner. | ||
320 | UUID owner = m_scene.RegionInfo.EstateSettings.EstateOwner; | ||
321 | SEUser securityUser = new SEUser(owner, "Name Unassigned"); | ||
322 | SecurityCredential creds = new SecurityCredential(securityUser, m_scene); | ||
323 | |||
324 | world = new World(m_scene, creds); | ||
325 | host = new Host(new SOPObject(m_scene, localID, creds), m_scene, new ExtensionHandler(m_extensions), | ||
326 | m_microthreads); | ||
327 | } | ||
328 | |||
329 | public void InitializeMRM(MRMBase mmb, uint localID, UUID itemID) | ||
330 | { | ||
331 | m_log.Info("[MRM] Created MRM Instance"); | ||
332 | |||
333 | IWorld world; | ||
334 | IHost host; | ||
335 | |||
336 | GetGlobalEnvironment(localID, out world, out host); | ||
337 | |||
338 | mmb.InitMiniModule(world, host, itemID); | ||
339 | } | ||
340 | |||
341 | /// <summary> | ||
342 | /// Stolen from ScriptEngine Common | ||
343 | /// </summary> | ||
344 | /// <param name="Script"></param> | ||
345 | /// <param name="uuid">Unique ID for this module</param> | ||
346 | /// <returns></returns> | ||
347 | internal string CompileFromDotNetText(string Script, string uuid) | ||
348 | { | ||
349 | m_log.Info("MRM 1"); | ||
350 | const string ext = ".cs"; | ||
351 | const string FilePrefix = "MiniModule"; | ||
352 | |||
353 | // Output assembly name | ||
354 | string OutFile = Path.Combine("MiniModules", Path.Combine( | ||
355 | m_scene.RegionInfo.RegionID.ToString(), | ||
356 | FilePrefix + "_compiled_" + uuid + "_" + | ||
357 | Util.RandomClass.Next(9000) + ".dll")); | ||
358 | |||
359 | // Create Directories for Assemblies | ||
360 | if (!Directory.Exists("MiniModules")) | ||
361 | Directory.CreateDirectory("MiniModules"); | ||
362 | string tmp = Path.Combine("MiniModules", m_scene.RegionInfo.RegionID.ToString()); | ||
363 | if (!Directory.Exists(tmp)) | ||
364 | Directory.CreateDirectory(tmp); | ||
365 | |||
366 | m_log.Info("MRM 2"); | ||
367 | |||
368 | try | ||
369 | { | ||
370 | File.Delete(OutFile); | ||
371 | } | ||
372 | catch (UnauthorizedAccessException e) | ||
373 | { | ||
374 | throw new Exception("Unable to delete old existing " + | ||
375 | "script-file before writing new. Compile aborted: " + | ||
376 | e); | ||
377 | } | ||
378 | catch (IOException e) | ||
379 | { | ||
380 | throw new Exception("Unable to delete old existing " + | ||
381 | "script-file before writing new. Compile aborted: " + | ||
382 | e); | ||
383 | } | ||
384 | |||
385 | m_log.Info("MRM 3"); | ||
386 | |||
387 | // DEBUG - write source to disk | ||
388 | string srcFileName = FilePrefix + "_source_" + | ||
389 | Path.GetFileNameWithoutExtension(OutFile) + ext; | ||
390 | try | ||
391 | { | ||
392 | File.WriteAllText(Path.Combine(Path.Combine( | ||
393 | "MiniModules", | ||
394 | m_scene.RegionInfo.RegionID.ToString()), | ||
395 | srcFileName), Script); | ||
396 | } | ||
397 | catch (Exception ex) //NOTLEGIT - Should be just FileIOException | ||
398 | { | ||
399 | m_log.Error("[Compiler]: Exception while " + | ||
400 | "trying to write script source to file \"" + | ||
401 | srcFileName + "\": " + ex); | ||
402 | } | ||
403 | |||
404 | m_log.Info("MRM 4"); | ||
405 | |||
406 | // Do actual compile | ||
407 | CompilerParameters parameters = new CompilerParameters(); | ||
408 | |||
409 | parameters.IncludeDebugInformation = true; | ||
410 | |||
411 | string rootPath = Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); | ||
412 | |||
413 | List<string> libraries = new List<string>(); | ||
414 | string[] lines = Script.Split(new string[] {"\n"}, StringSplitOptions.RemoveEmptyEntries); | ||
415 | foreach (string s in lines) | ||
416 | { | ||
417 | if (s.StartsWith("//@DEPENDS:")) | ||
418 | { | ||
419 | libraries.Add(s.Replace("//@DEPENDS:", "")); | ||
420 | } | ||
421 | } | ||
422 | |||
423 | libraries.Add("OpenSim.Region.OptionalModules.dll"); | ||
424 | libraries.Add("OpenMetaverseTypes.dll"); | ||
425 | libraries.Add("log4net.dll"); | ||
426 | |||
427 | foreach (string library in libraries) | ||
428 | { | ||
429 | parameters.ReferencedAssemblies.Add(Path.Combine(rootPath, library)); | ||
430 | } | ||
431 | |||
432 | parameters.GenerateExecutable = false; | ||
433 | parameters.OutputAssembly = OutFile; | ||
434 | parameters.IncludeDebugInformation = true; | ||
435 | parameters.TreatWarningsAsErrors = false; | ||
436 | |||
437 | m_log.Info("MRM 5"); | ||
438 | |||
439 | CompilerResults results = CScodeProvider.CompileAssemblyFromSource( | ||
440 | parameters, Script); | ||
441 | |||
442 | m_log.Info("MRM 6"); | ||
443 | |||
444 | int display = 5; | ||
445 | if (results.Errors.Count > 0) | ||
446 | { | ||
447 | string errtext = String.Empty; | ||
448 | foreach (CompilerError CompErr in results.Errors) | ||
449 | { | ||
450 | // Show 5 errors max | ||
451 | // | ||
452 | if (display <= 0) | ||
453 | break; | ||
454 | display--; | ||
455 | |||
456 | string severity = "Error"; | ||
457 | if (CompErr.IsWarning) | ||
458 | { | ||
459 | severity = "Warning"; | ||
460 | } | ||
461 | |||
462 | string text = CompErr.ErrorText; | ||
463 | |||
464 | // The Second Life viewer's script editor begins | ||
465 | // countingn lines and columns at 0, so we subtract 1. | ||
466 | errtext += String.Format("Line ({0},{1}): {4} {2}: {3}\n", | ||
467 | CompErr.Line - 1, CompErr.Column - 1, | ||
468 | CompErr.ErrorNumber, text, severity); | ||
469 | } | ||
470 | |||
471 | if (!File.Exists(OutFile)) | ||
472 | { | ||
473 | throw new Exception(errtext); | ||
474 | } | ||
475 | } | ||
476 | |||
477 | m_log.Info("MRM 7"); | ||
478 | |||
479 | if (!File.Exists(OutFile)) | ||
480 | { | ||
481 | string errtext = String.Empty; | ||
482 | errtext += "No compile error. But not able to locate compiled file."; | ||
483 | throw new Exception(errtext); | ||
484 | } | ||
485 | |||
486 | FileInfo fi = new FileInfo(OutFile); | ||
487 | |||
488 | Byte[] data = new Byte[fi.Length]; | ||
489 | |||
490 | try | ||
491 | { | ||
492 | FileStream fs = File.Open(OutFile, FileMode.Open, FileAccess.Read); | ||
493 | fs.Read(data, 0, data.Length); | ||
494 | fs.Close(); | ||
495 | } | ||
496 | catch (IOException) | ||
497 | { | ||
498 | string errtext = String.Empty; | ||
499 | errtext += "No compile error. But not able to open file."; | ||
500 | throw new Exception(errtext); | ||
501 | } | ||
502 | |||
503 | m_log.Info("MRM 8"); | ||
504 | |||
505 | // Convert to base64 | ||
506 | // | ||
507 | string filetext = Convert.ToBase64String(data); | ||
508 | Byte[] buf = Encoding.ASCII.GetBytes(filetext); | ||
509 | |||
510 | m_log.Info("MRM 9"); | ||
511 | |||
512 | FileStream sfs = File.Create(OutFile + ".cil.b64"); | ||
513 | sfs.Write(buf, 0, buf.Length); | ||
514 | sfs.Close(); | ||
515 | |||
516 | m_log.Info("MRM 10"); | ||
517 | |||
518 | return OutFile; | ||
519 | } | ||
520 | } | ||
521 | } | ||