1 /*
2  * FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
3  * Copyright (C) 2008, Michael Giagnocavo <mgg@packetrino.com>
4  *
5  * Version: MPL 1.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application - mod_managed
18  *
19  * The Initial Developer of the Original Code is
20  * Michael Giagnocavo <mgg@packetrino.com>
21  * Portions created by the Initial Developer are Copyright (C)
22  * the Initial Developer. All Rights Reserved.
23  *
24  * Contributor(s):
25  *
26  * Michael Giagnocavo <mgg@giagnocavo.net>
27  * David Brazier <David.Brazier@360crm.co.uk>
28  * Jeff Lenk <jlenk@frontiernet.net>
29  * Artur Kraev <ravenox@gmail.com>
30  *
31  * mod_mono.cpp -- FreeSWITCH mod_mono main class
32  *
33  * Most of mod_mono is implmented in the mod_mono_managed Loader class.
34  * The native code just handles getting the Mono runtime up and down
35  * and passing pointers into managed code.
36  */
37 
38 #include <switch.h>
39 #include "freeswitch_managed.h"
40 
41 #ifdef _MANAGED
42 #include <mscoree.h>
43 using namespace System;
44 using namespace System::Runtime::InteropServices;
45 #define MOD_MANAGED_VERSION "Microsoft CLR Version"
46 #else
47 #define MOD_MANAGED_VERSION "Mono Version"
48 #endif
49 
50 SWITCH_BEGIN_EXTERN_C
51 
52 SWITCH_MODULE_LOAD_FUNCTION(mod_managed_load);
53 SWITCH_MODULE_DEFINITION_EX(mod_managed, mod_managed_load, NULL, NULL, SMODF_GLOBAL_SYMBOLS);
54 
55 SWITCH_STANDARD_API(managedrun_api_function);	/* ExecuteBackground */
56 SWITCH_STANDARD_API(managed_api_function);	/* Execute */
57 SWITCH_STANDARD_APP(managed_app_function);	/* Run */
58 SWITCH_STANDARD_API(managedreload_api_function);	/* Reload */
59 SWITCH_STANDARD_API(managedlist_api_function); /* List modules */
60 
61 #define MOD_MANAGED_ASM_NAME "FreeSWITCH.Managed"
62 #define MOD_MANAGED_ASM_V1 1
63 #define MOD_MANAGED_ASM_V2 0
64 #define MOD_MANAGED_ASM_V3 2
65 #define MOD_MANAGED_ASM_V4 0
66 #define MOD_MANAGED_DLL MOD_MANAGED_ASM_NAME ".dll"
67 #define MOD_MANAGED_IMAGE_NAME "FreeSWITCH"
68 #define MOD_MANAGED_CLASS_NAME "Loader"
69 
70 mod_managed_globals managed_globals = { 0 };
71 
72 // Global delegates to call managed functions
73 typedef int (*runFunction)(const char *data, void *sessionPtr);
74 typedef int (*executeFunction)(const char *cmd, void *stream, void *Event);
75 typedef int (*executeBackgroundFunction)(const char* cmd);
76 typedef int (*reloadFunction)(const char* cmd);
77 typedef int (*listFunction)(const char *cmd, void *stream, void *Event);
78 static runFunction runDelegate;
79 static executeFunction executeDelegate;
80 static executeBackgroundFunction executeBackgroundDelegate;
81 static reloadFunction reloadDelegate;
82 static listFunction listDelegate;
83 
InitManagedDelegates(runFunction run,executeFunction execute,executeBackgroundFunction executeBackground,reloadFunction reload,listFunction list)84 SWITCH_MOD_DECLARE_NONSTD(void) InitManagedDelegates(runFunction run, executeFunction execute, executeBackgroundFunction executeBackground, reloadFunction reload, listFunction list)
85 {
86 	runDelegate = run;
87 	executeDelegate = execute;
88 	executeBackgroundDelegate = executeBackground;
89 	reloadDelegate = reload;
90 	listDelegate = list;
91 }
92 
93 
94 // Sets up delegates (and anything else needed) on the ManagedSession object
95 // Called from ManagedSession.Initialize Managed -> this is Unmanaged code so all pointers are marshalled and prevented from GC
96 // Exported method.
InitManagedSession(ManagedSession * session,inputFunction dtmfDelegate,hangupFunction hangupDelegate)97 SWITCH_MOD_DECLARE_NONSTD(void) InitManagedSession(ManagedSession *session, inputFunction dtmfDelegate, hangupFunction hangupDelegate)
98 {
99 	switch_assert(session);
100 	if (!session) {
101 		return;
102 	}
103 	session->setDTMFCallback(NULL, (char *)"");
104 	session->setHangupHook(NULL);
105 	session->dtmfDelegate = dtmfDelegate;
106 	session->hangupDelegate = hangupDelegate;
107 }
108 
109 #ifndef _MANAGED
110 
111 #ifdef WIN32
112 #include <shlobj.h>
113 #endif
114 
setMonoDirs()115 switch_status_t setMonoDirs()
116 {
117 #ifdef WIN32
118 	// Win32 Mono installs can't figure out their own path
119 	// Guys in #mono say we should just deploy all the libs we need
120 	// We'll first check for Program Files\Mono to allow people to use the symlink dir for a specific version.
121 	// Then we'll check HKEY_LOCAL_MACHINE\SOFTWARE\Novell\Mono\2.0\FrameworkAssemblyDirectory and MonoConfigDir
122 	// After that, we'll scan program files for a Mono-* dir.
123 	char progFilesPath[MAX_PATH];
124 	char libPath[MAX_PATH];
125 	char etcPath[MAX_PATH];
126 	char findPath[MAX_PATH];
127 	bool found = false;
128 
129 	SHGetFolderPath(NULL, CSIDL_PROGRAM_FILES, NULL, SHGFP_TYPE_CURRENT, progFilesPath);
130 
131 	{ // Check PF\Mono directly
132 		DWORD attr;
133 		switch_snprintf(findPath, MAX_PATH, "%s\\Mono", progFilesPath);
134 		attr = GetFileAttributes(findPath);
135 		found = (attr != INVALID_FILE_ATTRIBUTES && ((attr & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY));
136 		if (found) {
137 			switch_snprintf(libPath, MAX_PATH, "%s\\lib", findPath);
138 			switch_snprintf(etcPath, MAX_PATH, "%s\\etc", findPath);
139 		}
140 	}
141 
142 	if (!found) {   // Check registry
143 		DWORD size = MAX_PATH;
144 		if (ERROR_SUCCESS == RegGetValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\Novell\\Mono\\2.0", "FrameworkAssemblyDirectory", RRF_RT_REG_SZ, NULL, &libPath, &size)) {
145 			size = MAX_PATH;
146 			if (ERROR_SUCCESS == RegGetValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\Novell\\Mono\\2.0", "MonoConfigDir", RRF_RT_REG_SZ, NULL, &etcPath, &size)) {
147 				found = true;
148 			}
149 		}
150 	}
151 
152 	if (!found) { // Scan program files for Mono-2something
153 		HANDLE hFind;
154 		WIN32_FIND_DATA findData;
155 		switch_snprintf(findPath, MAX_PATH, "%s\\Mono-2*", progFilesPath);
156 		hFind = FindFirstFile(findPath, &findData);
157 		if (hFind == INVALID_HANDLE_VALUE) {
158 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Error looking for Mono in Program Files.\n");
159 			return SWITCH_STATUS_FALSE;
160 		}
161 
162 		while ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY) {
163 			if (FindNextFile(hFind, &findData) == 0) {
164 				switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not find Mono directory in Program Files.\n");
165 				FindClose(hFind);
166 				return SWITCH_STATUS_FALSE;
167 			}
168 		}
169 		switch_snprintf(libPath, MAX_PATH, "%s\\%s\\lib", progFilesPath, findData.cFileName);
170 		switch_snprintf(etcPath, MAX_PATH, "%s\\%s\\etc", progFilesPath, findData.cFileName);
171 		FindClose(hFind);
172 	}
173 
174 	/* Got it */
175 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Using Mono paths '%s' and '%s'.\n", libPath, etcPath);
176 	mono_set_dirs(libPath, etcPath);
177 	return SWITCH_STATUS_SUCCESS;
178 
179 #else
180 	// On other platforms, it should just work if it hasn't been relocated
181 	mono_set_dirs(NULL, NULL);
182 	return SWITCH_STATUS_SUCCESS;
183 #endif
184 }
185 
loadRuntime()186 switch_status_t loadRuntime()
187 {
188 	/* Find and load mod_mono_managed.exe */
189 	char filename[256];
190 
191 	if (setMonoDirs() != SWITCH_STATUS_SUCCESS) {
192 		return SWITCH_STATUS_FALSE;
193 	}
194 
195 #ifndef WIN32
196 	// So linux can find the .so
197 	char xmlConfig[300];
198 	switch_snprintf(xmlConfig, 300, "<configuration><dllmap dll=\"mod_managed\" target=\"%s%smod_managed.so\"/></configuration>", SWITCH_GLOBAL_dirs.mod_dir, SWITCH_PATH_SEPARATOR);
199 	mono_config_parse(NULL);
200 	mono_config_parse_memory(xmlConfig);
201 #endif
202 
203 	switch_snprintf(filename, 256, "%s%s%s", SWITCH_GLOBAL_dirs.mod_dir, SWITCH_PATH_SEPARATOR, MOD_MANAGED_DLL);
204 	managed_globals.domain = mono_jit_init(filename);
205 
206 	/* Already got a Mono domain? */
207 	if ((managed_globals.domain = mono_get_root_domain())) {
208 		mono_thread_attach(managed_globals.domain);
209 		managed_globals.embedded = SWITCH_TRUE;
210 	} else {
211 		if (!(managed_globals.domain = mono_jit_init(filename))) {
212 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mono_jit_init failed.\n");
213 			return SWITCH_STATUS_FALSE;
214 		}
215 	}
216 
217 	/* Already loaded? */
218 	MonoAssemblyName *name = mono_assembly_name_new (MOD_MANAGED_ASM_NAME);
219 	//Note also that it can't be allocated on the stack anymore and you'll need to create and destroy it with the following API:
220 	//mono_assembly_name_free (name);
221 
222 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Calling mono_assembly_loaded.\n");
223 
224 	if (!(managed_globals.mod_mono_asm = mono_assembly_loaded(name))) {
225 		/* Open the assembly */
226 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Calling mono_domain_assembly_open.\n");
227 		managed_globals.mod_mono_asm = mono_domain_assembly_open(managed_globals.domain, filename);
228 		if (!managed_globals.mod_mono_asm) {
229 			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "mono_domain_assembly_open failed.\n");
230 			return SWITCH_STATUS_FALSE;
231 		}
232 	}
233 
234 	return SWITCH_STATUS_SUCCESS;
235 }
236 
getMethod(const char * name,MonoClass * klass)237 MonoMethod * getMethod(const char *name, MonoClass * klass)
238 {
239 	MonoMethodDesc * desc;
240 	MonoMethod * method;
241 
242 	desc = mono_method_desc_new(name, TRUE);
243 	method = mono_method_desc_search_in_class(desc, klass);
244 
245 	if (!method) {
246 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not find %s method.\n", name);
247 		return NULL;
248 	}
249 
250 	return method;
251 }
252 
findLoader()253 switch_status_t findLoader()
254 {
255 	/* Find loader class and methods */
256 	MonoClass * loaderClass;
257 	MonoImage * img = mono_assembly_get_image(managed_globals.mod_mono_asm);
258 
259 	if (!(loaderClass = mono_class_from_name(img, MOD_MANAGED_IMAGE_NAME, MOD_MANAGED_CLASS_NAME))) {
260 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not find " MOD_MANAGED_IMAGE_NAME "." MOD_MANAGED_CLASS_NAME " class.\n");
261 		return SWITCH_STATUS_FALSE;
262 	}
263 
264 	if (!(managed_globals.loadMethod = getMethod(MOD_MANAGED_IMAGE_NAME "." MOD_MANAGED_CLASS_NAME ":Load()", loaderClass))) {
265 		return SWITCH_STATUS_FALSE;
266 	}
267 
268 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Found all loader functions.\n");
269 	return SWITCH_STATUS_SUCCESS;
270 }
271 #endif
272 
273 /**********************************************************
274 	CLR Code Starts Here
275 **********************************************************/
276 
277 #ifdef _MANAGED
278 
loadRuntime()279 switch_status_t loadRuntime()
280 {
281 	/* Find and load mod_dotnet_managed.dll */
282 	char filename[256];
283 	switch_snprintf(filename, 256, "%s%s%s", SWITCH_GLOBAL_dirs.mod_dir, SWITCH_PATH_SEPARATOR, MOD_MANAGED_DLL);
284 
285 	wchar_t modpath[256];
286 	mbstowcs(modpath, filename, 255);
287 	try {
288 		FreeSwitchManaged::mod_dotnet_managed = Assembly::LoadFrom(gcnew String(modpath));
289 	} catch (Exception^ ex) {
290 		IntPtr msg = Marshal::StringToHGlobalAnsi(ex->ToString());
291 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Assembly::LoadFrom failed: %s\n", static_cast<const char*>(msg.ToPointer()));
292 		Marshal::FreeHGlobal(msg);
293 		return SWITCH_STATUS_FALSE;
294 	}
295 	return SWITCH_STATUS_SUCCESS;
296 }
297 
findLoader()298 switch_status_t findLoader()
299 {
300 	try {
301 		FreeSwitchManaged::loadMethod = FreeSwitchManaged::mod_dotnet_managed->GetType(MOD_MANAGED_IMAGE_NAME "." MOD_MANAGED_CLASS_NAME)->GetMethod("Load");
302 	} catch(Exception^ ex) {
303 		IntPtr msg = Marshal::StringToHGlobalAnsi(ex->ToString());
304 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Could not load " MOD_MANAGED_IMAGE_NAME "." MOD_MANAGED_CLASS_NAME " class: %s\n", static_cast<const char*>(msg.ToPointer()));
305 		Marshal::FreeHGlobal(msg);
306 		return SWITCH_STATUS_FALSE;
307 	}
308 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Found all " MOD_MANAGED_IMAGE_NAME "." MOD_MANAGED_CLASS_NAME " functions.\n");
309 	return SWITCH_STATUS_SUCCESS;
310 }
311 #endif
312 
SWITCH_MODULE_LOAD_FUNCTION(mod_managed_load)313 SWITCH_MODULE_LOAD_FUNCTION(mod_managed_load)
314 {
315 	int success;
316 	/* connect my internal structure to the blank pointer passed to me */
317 	*module_interface = switch_loadable_module_create_module_interface(pool, modname);
318 	switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_INFO, "Loading mod_managed (Common Language Infrastructure), " MOD_MANAGED_VERSION "\n");
319 
320 	managed_globals.pool = pool;
321 
322 	if (loadRuntime() != SWITCH_STATUS_SUCCESS) {
323 		return SWITCH_STATUS_FALSE;
324 	}
325 
326 	if (findLoader() != SWITCH_STATUS_SUCCESS) {
327 		return SWITCH_STATUS_FALSE;
328 	}
329 #ifdef _MANAGED
330 	try {
331 		Object ^objResult = FreeSwitchManaged::loadMethod->Invoke(nullptr, nullptr);
332 		success = *reinterpret_cast<bool^>(objResult);
333 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Load completed successfully.\n");
334 	}
335 	catch(Exception^ ex) {
336 		IntPtr msg = Marshal::StringToHGlobalAnsi(ex->ToString());
337 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Load did not return true. %s\n", static_cast<const char*>(msg.ToPointer()));
338 		Marshal::FreeHGlobal(msg);
339 		return SWITCH_STATUS_FALSE;
340 	}
341 #else
342 	/* Not sure if this is necesary on the loading thread */
343 	mono_thread_attach(managed_globals.domain);
344 
345 	/* Run loader */
346 	MonoObject * exception = NULL;
347 	MonoObject * objResult = mono_runtime_invoke(managed_globals.loadMethod, NULL, NULL, &exception);
348 	if (exception) {
349 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Load threw an exception.\n");
350 		mono_print_unhandled_exception(exception);
351 		return SWITCH_STATUS_FALSE;
352 	}
353 	success = *(int *) mono_object_unbox(objResult);
354 #endif
355 	if (success) {
356 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Load completed successfully.\n");
357 	} else {
358 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Load did not return true.\n");
359 		return SWITCH_STATUS_FALSE;
360 	}
361 
362 	/* We're good to register */
363 	switch_api_interface_t *api_interface;
364 	switch_application_interface_t *app_interface;
365 
366 	SWITCH_ADD_API(api_interface, "managedrun", "Run a module (ExecuteBackground)", managedrun_api_function, "<module> [<args>]");
367 	SWITCH_ADD_API(api_interface, "managed", "Run a module as an API function (Execute)", managed_api_function, "<module> [<args>]");
368 	SWITCH_ADD_APP(app_interface, "managed", "Run CLI App", "Run an App on a channel", managed_app_function, "<modulename> [<args>]", SAF_SUPPORT_NOMEDIA);
369 	SWITCH_ADD_API(api_interface, "managedreload", "Force [re]load of a file", managedreload_api_function, "<filename>");
370 	SWITCH_ADD_API(api_interface, "managedlist", "Log the list of available APIs and Apps", managedlist_api_function, "");
371 	return SWITCH_STATUS_NOUNLOAD;
372 }
373 
374 #ifdef _MANAGED
375 #pragma unmanaged
376 #endif
SWITCH_STANDARD_API(managedrun_api_function)377 SWITCH_STANDARD_API(managedrun_api_function)
378 {
379 	if (zstr(cmd)) {
380 		stream->write_function(stream, "-ERR no args specified!\n");
381 		return SWITCH_STATUS_SUCCESS;
382 	}
383 #ifndef _MANAGED
384 	mono_thread_attach(managed_globals.domain);
385 #endif
386 	if (executeBackgroundDelegate(cmd)) {
387 		stream->write_function(stream, "+OK\n");
388 	} else {
389 		stream->write_function(stream, "-ERR ExecuteBackground returned false (unknown module or exception?).\n");
390 	}
391 #ifndef _MANAGED
392 	mono_thread_detach(mono_thread_current());
393 #endif
394 	return SWITCH_STATUS_SUCCESS;
395 }
396 
SWITCH_STANDARD_API(managed_api_function)397 SWITCH_STANDARD_API(managed_api_function)
398 {
399 	if (zstr(cmd)) {
400 		stream->write_function(stream, "-ERR no args specified!\n");
401 		return SWITCH_STATUS_SUCCESS;
402 	}
403 #ifndef _MANAGED
404 	mono_thread_attach(managed_globals.domain);
405 #endif
406 	if (!(executeDelegate(cmd, stream, stream->param_event))) {
407 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Execute failed for %s (unknown module or exception).\n", cmd);
408 	}
409 #ifndef _MANAGED
410 	mono_thread_detach(mono_thread_current());
411 #endif
412 	return SWITCH_STATUS_SUCCESS;
413 }
414 
SWITCH_STANDARD_APP(managed_app_function)415 SWITCH_STANDARD_APP(managed_app_function)
416 {
417 	if (zstr(data)) {
418 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "No args specified!\n");
419 		return;
420 	}
421 #ifndef _MANAGED
422 	mono_thread_attach(managed_globals.domain);
423 #endif
424 	if (!(runDelegate(data, session))) {
425 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Application run failed for %s (unknown module or exception).\n", data);
426 	}
427 #ifndef _MANAGED
428 	mono_thread_detach(mono_thread_current());
429 #endif
430 }
431 
SWITCH_STANDARD_API(managedreload_api_function)432 SWITCH_STANDARD_API(managedreload_api_function)
433 {
434 	if (zstr(cmd)) {
435 		stream->write_function(stream, "-ERR no args specified!\n");
436 		return SWITCH_STATUS_SUCCESS;
437 	}
438 #ifndef _MANAGED
439 	mono_thread_attach(managed_globals.domain);
440 #endif
441 	if (!(reloadDelegate(cmd))) {
442 		switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_ERROR, "Execute failed for %s (unknown module or exception).\n", cmd);
443 	}
444 #ifndef _MANAGED
445 	mono_thread_detach(mono_thread_current());
446 #endif
447 	return SWITCH_STATUS_SUCCESS;
448 }
449 
SWITCH_STANDARD_API(managedlist_api_function)450 SWITCH_STANDARD_API(managedlist_api_function)
451 {
452 #ifndef _MANAGED
453 	mono_thread_attach(managed_globals.domain);
454 #endif
455 	listDelegate(cmd, stream, stream->param_event);
456 #ifndef _MANAGED
457 	mono_thread_detach(mono_thread_current());
458 #endif
459 	return SWITCH_STATUS_SUCCESS;
460 }
461 
462 SWITCH_END_EXTERN_C
463