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