2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \brief PBX channel monitoring
23 * \author Mark Spencer <markster@digium.com>
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34 #include <sys/types.h>
38 #include "asterisk/lock.h"
39 #include "asterisk/channel.h"
40 #include "asterisk/logger.h"
41 #include "asterisk/file.h"
42 #include "asterisk/pbx.h"
43 #include "asterisk/module.h"
44 #include "asterisk/manager.h"
45 #include "asterisk/cli.h"
46 #include "asterisk/monitor.h"
47 #include "asterisk/app.h"
48 #include "asterisk/utils.h"
49 #include "asterisk/config.h"
50 #include "asterisk/options.h"
52 AST_MUTEX_DEFINE_STATIC(monitorlock);
54 #define LOCK_IF_NEEDED(lock, needed) do { \
56 ast_channel_lock(lock); \
59 #define UNLOCK_IF_NEEDED(lock, needed) do { \
61 ast_channel_unlock(lock); \
64 static unsigned long seq = 0;
66 static char *monitor_synopsis = "Monitor a channel";
68 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
69 "Used to start monitoring a channel. The channel's input and output\n"
70 "voice packets are logged to files until the channel hangs up or\n"
71 "monitoring is stopped by the StopMonitor application.\n"
72 " file_format optional, if not set, defaults to \"wav\"\n"
73 " fname_base if set, changes the filename used to the one specified.\n"
75 " m - when the recording ends mix the two leg files into one and\n"
76 " delete the two leg files. If the variable MONITOR_EXEC is set, the\n"
77 " application referenced in it will be executed instead of\n"
78 " soxmix and the raw leg files will NOT be deleted automatically.\n"
79 " soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
80 " and a target mixed file name which is the same as the leg file names\n"
81 " only without the in/out designator.\n"
82 " If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
83 " additional arguements to MONITOR_EXEC\n"
84 " Both MONITOR_EXEC and the Mix flag can be set from the\n"
85 " administrator interface\n"
87 " b - Don't begin recording unless a call is bridged to another channel\n"
89 " i - Skip recording of input stream (disables m option)\n"
91 " o - Skip recording of output stream (disables m option)\n"
92 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
93 "monitored, otherwise 0.\n"
96 static char *stopmonitor_synopsis = "Stop monitoring a channel";
98 static char *stopmonitor_descrip = "StopMonitor\n"
99 "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
101 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
103 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
104 "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
105 "The argument is the new filename base to use for monitoring this channel.\n";
107 static char *pausemonitor_synopsis = "Pause monitoring of a channel";
109 static char *pausemonitor_descrip = "PauseMonitor\n"
110 "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n";
112 static char *unpausemonitor_synopsis = "Unpause monitoring of a channel";
114 static char *unpausemonitor_descrip = "UnpauseMonitor\n"
115 "Unpauses monitoring of a channel on which monitoring had\n"
116 "previously been paused with PauseMonitor.\n";
119 * \brief Change state of monitored channel
121 * \param state monitor state
122 * \retval 0 on success.
123 * \retval -1 on failure.
125 static int ast_monitor_set_state(struct ast_channel *chan, int state)
127 LOCK_IF_NEEDED(chan, 1);
128 if (!chan->monitor) {
129 UNLOCK_IF_NEEDED(chan, 1);
132 chan->monitor->state = state;
133 UNLOCK_IF_NEEDED(chan, 1);
137 /*! \brief Start monitoring a channel
138 * \param chan ast_channel struct to record
139 * \param format_spec file format to use for recording
140 * \param fname_base filename base to record to
141 * \param need_lock whether to lock the channel mutex
142 * \param stream_action whether to record the input and/or output streams. X_REC_IN | X_REC_OUT is most often used
143 * Creates the file to record, if no format is specified it assumes WAV
144 * It also sets channel variable __MONITORED=yes
145 * \retval 0 on success
146 * \retval -1 on failure
148 int ast_monitor_start( struct ast_channel *chan, const char *format_spec,
149 const char *fname_base, int need_lock, int stream_action)
153 LOCK_IF_NEEDED(chan, need_lock);
155 if (!(chan->monitor)) {
156 struct ast_channel_monitor *monitor;
157 char *channel_name, *p;
159 /* Create monitoring directory if needed */
160 ast_mkdir(ast_config_AST_MONITOR_DIR, 0777);
162 if (!(monitor = ast_calloc(1, sizeof(*monitor)))) {
163 UNLOCK_IF_NEEDED(chan, need_lock);
167 /* Determine file names */
168 if (!ast_strlen_zero(fname_base)) {
169 int directory = strchr(fname_base, '/') ? 1 : 0;
170 /* try creating the directory just in case it doesn't exist */
172 char *name = ast_strdupa(fname_base);
173 ast_mkdir(dirname(name), 0777);
175 snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
176 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
177 snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
178 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
179 ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
181 ast_mutex_lock(&monitorlock);
182 snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
183 ast_config_AST_MONITOR_DIR, seq);
184 snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
185 ast_config_AST_MONITOR_DIR, seq);
187 ast_mutex_unlock(&monitorlock);
189 channel_name = ast_strdupa(chan->name);
190 while ((p = strchr(channel_name, '/'))) {
193 snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
194 ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name);
195 monitor->filename_changed = 1;
198 monitor->stop = ast_monitor_stop;
200 /* Determine file format */
201 if (!ast_strlen_zero(format_spec)) {
202 monitor->format = ast_strdup(format_spec);
204 monitor->format = ast_strdup("wav");
208 if (stream_action & X_REC_IN) {
209 if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0)
210 ast_filedelete(monitor->read_filename, NULL);
211 if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
212 monitor->format, NULL,
213 O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) {
214 ast_log(LOG_WARNING, "Could not create file %s\n",
215 monitor->read_filename);
217 UNLOCK_IF_NEEDED(chan, need_lock);
221 monitor->read_stream = NULL;
223 if (stream_action & X_REC_OUT) {
224 if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
225 ast_filedelete(monitor->write_filename, NULL);
227 if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
228 monitor->format, NULL,
229 O_CREAT|O_TRUNC|O_WRONLY, 0, AST_FILE_MODE))) {
230 ast_log(LOG_WARNING, "Could not create file %s\n",
231 monitor->write_filename);
232 ast_closestream(monitor->read_stream);
234 UNLOCK_IF_NEEDED(chan, need_lock);
238 monitor->write_stream = NULL;
240 chan->monitor = monitor;
241 ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
242 /* so we know this call has been monitored in case we need to bill for it or something */
243 pbx_builtin_setvar_helper(chan, "__MONITORED","true");
245 ast_debug(1,"Cannot start monitoring %s, already monitored\n", chan->name);
249 UNLOCK_IF_NEEDED(chan, need_lock);
255 * \brief Get audio format.
256 * \param format recording format.
257 * The file format extensions that Asterisk uses are not all the same as that
258 * which soxmix expects. This function ensures that the format used as the
259 * extension on the filename is something soxmix will understand.
261 static const char *get_soxmix_format(const char *format)
263 const char *res = format;
265 if (!strcasecmp(format,"ulaw"))
267 if (!strcasecmp(format,"alaw"))
274 * \brief Stop monitoring channel
277 * Stop the recording, close any open streams, mix in/out channels if required
280 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
284 LOCK_IF_NEEDED(chan, need_lock);
287 char filename[ FILENAME_MAX ];
289 if (chan->monitor->read_stream) {
290 ast_closestream(chan->monitor->read_stream);
292 if (chan->monitor->write_stream) {
293 ast_closestream(chan->monitor->write_stream);
296 if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
297 if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
298 snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
299 if (ast_fileexists(filename, NULL, NULL) > 0) {
300 ast_filedelete(filename, NULL);
302 ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
304 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
307 if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
308 snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
309 if (ast_fileexists(filename, NULL, NULL) > 0) {
310 ast_filedelete(filename, NULL);
312 ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
314 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
318 if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
321 const char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
322 char *name = chan->monitor->filename_base;
323 int directory = strchr(name, '/') ? 1 : 0;
324 char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
325 const char *execute, *execute_args;
327 /* Set the execute application */
328 execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
329 if (ast_strlen_zero(execute)) {
330 execute = "nice -n 19 soxmix";
331 format = get_soxmix_format(format);
334 execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
335 if (ast_strlen_zero(execute_args)) {
339 snprintf(tmp, sizeof(tmp), "%s \"%s/%s-in.%s\" \"%s/%s-out.%s\" \"%s/%s.%s\" %s &", execute, dir, name, format, dir, name, format, dir, name, format,execute_args);
341 snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
342 ast_copy_string(tmp, tmp2, sizeof(tmp));
344 ast_debug(1,"monitor executing %s\n",tmp);
345 if (ast_safe_system(tmp) == -1)
346 ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
349 ast_free(chan->monitor->format);
350 ast_free(chan->monitor);
351 chan->monitor = NULL;
354 UNLOCK_IF_NEEDED(chan, need_lock);
360 /*! \brief Pause monitoring of channel */
361 int ast_monitor_pause(struct ast_channel *chan)
363 return ast_monitor_set_state(chan, AST_MONITOR_PAUSED);
366 /*! \brief Unpause monitoring of channel */
367 int ast_monitor_unpause(struct ast_channel *chan)
369 return ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
372 /*! \brief Wrapper for ast_monitor_pause */
373 static int pause_monitor_exec(struct ast_channel *chan, void *data)
375 return ast_monitor_pause(chan);
378 /*! \brief Wrapper for ast_monitor_unpause */
379 static int unpause_monitor_exec(struct ast_channel *chan, void *data)
381 return ast_monitor_unpause(chan);
385 * \brief Change monitored filename of channel
387 * \param fname_base new filename
389 * \retval 0 on success.
390 * \retval -1 on failure.
392 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
394 if (ast_strlen_zero(fname_base)) {
395 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null\n", chan->name);
399 LOCK_IF_NEEDED(chan, need_lock);
402 int directory = strchr(fname_base, '/') ? 1 : 0;
403 /* try creating the directory just in case it doesn't exist */
405 char *name = ast_strdupa(fname_base);
406 ast_mkdir(dirname(name), 0777);
409 snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
410 chan->monitor->filename_changed = 1;
412 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
415 UNLOCK_IF_NEEDED(chan, need_lock);
422 * \brief Start monitor
424 * \param data arguements passed fname|options
425 * \retval 0 on success.
426 * \retval -1 on failure.
428 static int start_monitor_exec(struct ast_channel *chan, void *data)
432 char *fname_base = NULL;
433 char *options = NULL;
435 char *urlprefix = NULL;
437 int stream_action = X_REC_IN | X_REC_OUT;
439 int waitforbridge = 0;
442 /* Parse arguments. */
443 if (!ast_strlen_zero((char*)data)) {
444 arg = ast_strdupa((char*)data);
446 fname_base = strchr(arg, '|');
450 if ((options = strchr(fname_base, '|'))) {
453 if (strchr(options, 'm'))
454 stream_action |= X_JOIN;
455 if (strchr(options, 'b'))
457 if (strchr(options, 'i'))
458 stream_action &= ~X_REC_IN;
459 if (strchr(options, 'o'))
460 stream_action &= ~X_REC_OUT;
463 arg = strchr(format,':');
470 snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
471 ((strcmp(format,"gsm")) ? "wav" : "gsm"));
472 if (!chan->cdr && !(chan->cdr = ast_cdr_alloc()))
474 ast_cdr_setuserfield(chan, tmp);
477 /* We must remove the "b" option if listed. In principle none of
478 the following could give NULL results, but we check just to
479 be pedantic. Reconstructing with checks for 'm' option does not
480 work if we end up adding more options than 'm' in the future. */
481 delay = ast_strdupa(data);
482 options = strrchr(delay, '|');
484 arg = strchr(options, 'b');
487 pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
493 res = ast_monitor_start(chan, format, fname_base, 1, stream_action);
495 res = ast_monitor_change_fname(chan, fname_base, 1);
497 if (stream_action & X_JOIN) {
498 if ((stream_action & X_REC_IN) && (stream_action & X_REC_OUT))
501 ast_log(LOG_WARNING, "Won't mix streams unless both input and output streams are recorded\n");
503 ast_monitor_setjoinfiles(chan, joinfiles);
508 /*! \brief Wrapper function \see ast_monitor_stop */
509 static int stop_monitor_exec(struct ast_channel *chan, void *data)
511 return ast_monitor_stop(chan, 1);
514 /*! \brief Wrapper function \see ast_monitor_change_fname */
515 static int change_monitor_exec(struct ast_channel *chan, void *data)
517 return ast_monitor_change_fname(chan, (const char*)data, 1);
520 static char start_monitor_action_help[] =
521 "Description: The 'Monitor' action may be used to record the audio on a\n"
522 " specified channel. The following parameters may be used to control\n"
524 " Channel - Required. Used to specify the channel to record.\n"
525 " File - Optional. Is the name of the file created in the\n"
526 " monitor spool directory. Defaults to the same name\n"
527 " as the channel (with slashes replaced with dashes).\n"
528 " Format - Optional. Is the audio recording format. Defaults\n"
530 " Mix - Optional. Boolean parameter as to whether to mix\n"
531 " the input and output channels together after the\n"
532 " recording is finished.\n";
534 /*! \brief Start monitoring a channel by manager connection */
535 static int start_monitor_action(struct mansession *s, const struct message *m)
537 struct ast_channel *c = NULL;
538 const char *name = astman_get_header(m, "Channel");
539 const char *fname = astman_get_header(m, "File");
540 const char *format = astman_get_header(m, "Format");
541 const char *mix = astman_get_header(m, "Mix");
544 if (ast_strlen_zero(name)) {
545 astman_send_error(s, m, "No channel specified");
548 c = ast_get_channel_by_name_locked(name);
550 astman_send_error(s, m, "No such channel");
554 if (ast_strlen_zero(fname)) {
555 /* No filename base specified, default to channel name as per CLI */
556 if (!(fname = ast_strdup(c->name))) {
557 astman_send_error(s, m, "Could not start monitoring channel");
558 ast_channel_unlock(c);
561 /* Channels have the format technology/channel_name - have to replace that / */
562 if ((d = strchr(fname, '/')))
566 if (ast_monitor_start(c, format, fname, 1, X_REC_IN | X_REC_OUT)) {
567 if (ast_monitor_change_fname(c, fname, 1)) {
568 astman_send_error(s, m, "Could not start monitoring channel");
569 ast_channel_unlock(c);
575 ast_monitor_setjoinfiles(c, 1);
578 ast_channel_unlock(c);
579 astman_send_ack(s, m, "Started monitoring channel");
583 static char stop_monitor_action_help[] =
584 "Description: The 'StopMonitor' action may be used to end a previously\n"
585 " started 'Monitor' action. The only parameter is 'Channel', the name\n"
586 " of the channel monitored.\n";
588 /*! \brief Stop monitoring a channel by manager connection */
589 static int stop_monitor_action(struct mansession *s, const struct message *m)
591 struct ast_channel *c = NULL;
592 const char *name = astman_get_header(m, "Channel");
594 if (ast_strlen_zero(name)) {
595 astman_send_error(s, m, "No channel specified");
598 c = ast_get_channel_by_name_locked(name);
600 astman_send_error(s, m, "No such channel");
603 res = ast_monitor_stop(c, 1);
604 ast_channel_unlock(c);
606 astman_send_error(s, m, "Could not stop monitoring channel");
609 astman_send_ack(s, m, "Stopped monitoring channel");
613 static char change_monitor_action_help[] =
614 "Description: The 'ChangeMonitor' action may be used to change the file\n"
615 " started by a previous 'Monitor' action. The following parameters may\n"
616 " be used to control this:\n"
617 " Channel - Required. Used to specify the channel to record.\n"
618 " File - Required. Is the new name of the file created in the\n"
619 " monitor spool directory.\n";
621 /*! \brief Change filename of a monitored channel by manager connection */
622 static int change_monitor_action(struct mansession *s, const struct message *m)
624 struct ast_channel *c = NULL;
625 const char *name = astman_get_header(m, "Channel");
626 const char *fname = astman_get_header(m, "File");
627 if (ast_strlen_zero(name)) {
628 astman_send_error(s, m, "No channel specified");
631 if (ast_strlen_zero(fname)) {
632 astman_send_error(s, m, "No filename specified");
635 c = ast_get_channel_by_name_locked(name);
637 astman_send_error(s, m, "No such channel");
640 if (ast_monitor_change_fname(c, fname, 1)) {
641 astman_send_error(s, m, "Could not change monitored filename of channel");
642 ast_channel_unlock(c);
645 ast_channel_unlock(c);
646 astman_send_ack(s, m, "Changed monitor filename");
650 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
653 chan->monitor->joinfiles = turnon;
656 enum MONITOR_PAUSING_ACTION
658 MONITOR_ACTION_PAUSE,
659 MONITOR_ACTION_UNPAUSE
662 static int do_pause_or_unpause(struct mansession *s, const struct message *m, int action)
664 struct ast_channel *c = NULL;
665 const char *name = astman_get_header(m, "Channel");
667 if (ast_strlen_zero(name)) {
668 astman_send_error(s, m, "No channel specified");
672 c = ast_get_channel_by_name_locked(name);
674 astman_send_error(s, m, "No such channel");
678 if (action == MONITOR_ACTION_PAUSE)
679 ast_monitor_pause(c);
681 ast_monitor_unpause(c);
683 ast_channel_unlock(c);
684 astman_send_ack(s, m, (action == MONITOR_ACTION_PAUSE ? "Paused monitoring of the channel" : "Unpaused monitoring of the channel"));
688 static char pause_monitor_action_help[] =
689 "Description: The 'PauseMonitor' action may be used to temporarily stop the\n"
690 " recording of a channel. The following parameters may\n"
691 " be used to control this:\n"
692 " Channel - Required. Used to specify the channel to record.\n";
694 static int pause_monitor_action(struct mansession *s, const struct message *m)
696 return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE);
699 static char unpause_monitor_action_help[] =
700 "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n"
701 " of a channel after calling PauseMonitor. The following parameters may\n"
702 " be used to control this:\n"
703 " Channel - Required. Used to specify the channel to record.\n";
705 static int unpause_monitor_action(struct mansession *s, const struct message *m)
707 return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE);
711 static int load_module(void)
713 ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
714 ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
715 ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
716 ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip);
717 ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip);
718 ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
719 ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
720 ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
721 ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help);
722 ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help);
727 static int unload_module(void)
729 ast_unregister_application("Monitor");
730 ast_unregister_application("StopMonitor");
731 ast_unregister_application("ChangeMonitor");
732 ast_unregister_application("PauseMonitor");
733 ast_unregister_application("UnpauseMonitor");
734 ast_manager_unregister("Monitor");
735 ast_manager_unregister("StopMonitor");
736 ast_manager_unregister("ChangeMonitor");
737 ast_manager_unregister("PauseMonitor");
738 ast_manager_unregister("UnpauseMonitor");
743 /* usecount semantics need to be defined */
744 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource",
746 .unload = unload_module,