remove documentation of 'global' section in modules.conf, since it is no longer neede...
[asterisk-bristuff.git] / res / res_monitor.c
blob86d6021aff8cecd5dce11c849ecc9af958f88471
1 /*
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.
19 /*! \file
21 * \brief PBX channel monitoring
23 * \author Mark Spencer <markster@digium.com>
26 #include "asterisk.h"
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <errno.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <libgen.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"
51 AST_MUTEX_DEFINE_STATIC(monitorlock);
53 #define LOCK_IF_NEEDED(lock, needed) do { \
54 if (needed) \
55 ast_channel_lock(lock); \
56 } while(0)
58 #define UNLOCK_IF_NEEDED(lock, needed) do { \
59 if (needed) \
60 ast_channel_unlock(lock); \
61 } while (0)
63 static unsigned long seq = 0;
65 static char *monitor_synopsis = "Monitor a channel";
67 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
68 "Used to start monitoring a channel. The channel's input and output\n"
69 "voice packets are logged to files until the channel hangs up or\n"
70 "monitoring is stopped by the StopMonitor application.\n"
71 " file_format optional, if not set, defaults to \"wav\"\n"
72 " fname_base if set, changes the filename used to the one specified.\n"
73 " options:\n"
74 " m - when the recording ends mix the two leg files into one and\n"
75 " delete the two leg files. If the variable MONITOR_EXEC is set, the\n"
76 " application referenced in it will be executed instead of\n"
77 " soxmix and the raw leg files will NOT be deleted automatically.\n"
78 " soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
79 " and a target mixed file name which is the same as the leg file names\n"
80 " only without the in/out designator.\n"
81 " If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
82 " additional arguements to MONITOR_EXEC\n"
83 " Both MONITOR_EXEC and the Mix flag can be set from the\n"
84 " administrator interface\n"
85 "\n"
86 " b - Don't begin recording unless a call is bridged to another channel\n"
87 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
88 "monitored, otherwise 0.\n"
91 static char *stopmonitor_synopsis = "Stop monitoring a channel";
93 static char *stopmonitor_descrip = "StopMonitor\n"
94 "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
96 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
98 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
99 "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
100 "The argument is the new filename base to use for monitoring this channel.\n";
102 static char *pausemonitor_synopsis = "Pause monitoring of a channel";
104 static char *pausemonitor_descrip = "PauseMonitor\n"
105 "Pauses monitoring of a channel until it is re-enabled by a call to UnpauseMonitor.\n";
107 static char *unpausemonitor_synopsis = "Unpause monitoring of a channel";
109 static char *unpausemonitor_descrip = "UnpauseMonitor\n"
110 "Unpauses monitoring of a channel on which monitoring had\n"
111 "previously been paused with PauseMonitor.\n";
113 static int ast_monitor_set_state(struct ast_channel *chan, int state)
115 LOCK_IF_NEEDED(chan, 1);
116 if (!chan->monitor) {
117 UNLOCK_IF_NEEDED(chan, 1);
118 return -1;
120 chan->monitor->state = state;
121 UNLOCK_IF_NEEDED(chan, 1);
122 return 0;
125 /* Start monitoring a channel */
126 int ast_monitor_start( struct ast_channel *chan, const char *format_spec,
127 const char *fname_base, int need_lock)
129 int res = 0;
130 char tmp[256];
132 LOCK_IF_NEEDED(chan, need_lock);
134 if (!(chan->monitor)) {
135 struct ast_channel_monitor *monitor;
136 char *channel_name, *p;
138 /* Create monitoring directory if needed */
139 if (mkdir(ast_config_AST_MONITOR_DIR, 0770) < 0) {
140 if (errno != EEXIST) {
141 ast_log(LOG_WARNING, "Unable to create audio monitor directory: %s\n",
142 strerror(errno));
146 if (!(monitor = ast_calloc(1, sizeof(*monitor)))) {
147 UNLOCK_IF_NEEDED(chan, need_lock);
148 return -1;
151 /* Determine file names */
152 if (!ast_strlen_zero(fname_base)) {
153 int directory = strchr(fname_base, '/') ? 1 : 0;
154 /* try creating the directory just in case it doesn't exist */
155 if (directory) {
156 char *name = strdup(fname_base);
157 snprintf(tmp, sizeof(tmp), "mkdir -p \"%s\"",dirname(name));
158 free(name);
159 ast_safe_system(tmp);
161 snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
162 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
163 snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
164 directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
165 ast_copy_string(monitor->filename_base, fname_base, sizeof(monitor->filename_base));
166 } else {
167 ast_mutex_lock(&monitorlock);
168 snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
169 ast_config_AST_MONITOR_DIR, seq);
170 snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
171 ast_config_AST_MONITOR_DIR, seq);
172 seq++;
173 ast_mutex_unlock(&monitorlock);
175 channel_name = ast_strdupa(chan->name);
176 while ((p = strchr(channel_name, '/'))) {
177 *p = '-';
179 snprintf(monitor->filename_base, FILENAME_MAX, "%s/%d-%s",
180 ast_config_AST_MONITOR_DIR, (int)time(NULL), channel_name);
181 monitor->filename_changed = 1;
184 monitor->stop = ast_monitor_stop;
186 /* Determine file format */
187 if (!ast_strlen_zero(format_spec)) {
188 monitor->format = strdup(format_spec);
189 } else {
190 monitor->format = strdup("wav");
193 /* open files */
194 if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
195 ast_filedelete(monitor->read_filename, NULL);
197 if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
198 monitor->format, NULL,
199 O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
200 ast_log(LOG_WARNING, "Could not create file %s\n",
201 monitor->read_filename);
202 free(monitor);
203 UNLOCK_IF_NEEDED(chan, need_lock);
204 return -1;
206 if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
207 ast_filedelete(monitor->write_filename, NULL);
209 if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
210 monitor->format, NULL,
211 O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
212 ast_log(LOG_WARNING, "Could not create file %s\n",
213 monitor->write_filename);
214 ast_closestream(monitor->read_stream);
215 free(monitor);
216 UNLOCK_IF_NEEDED(chan, need_lock);
217 return -1;
219 chan->monitor = monitor;
220 ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
221 /* so we know this call has been monitored in case we need to bill for it or something */
222 pbx_builtin_setvar_helper(chan, "__MONITORED","true");
223 } else {
224 ast_log(LOG_DEBUG,"Cannot start monitoring %s, already monitored\n",
225 chan->name);
226 res = -1;
229 UNLOCK_IF_NEEDED(chan, need_lock);
231 return res;
234 /* Stop monitoring a channel */
235 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
237 int delfiles = 0;
239 LOCK_IF_NEEDED(chan, need_lock);
241 if (chan->monitor) {
242 char filename[ FILENAME_MAX ];
244 if (chan->monitor->read_stream) {
245 ast_closestream(chan->monitor->read_stream);
247 if (chan->monitor->write_stream) {
248 ast_closestream(chan->monitor->write_stream);
251 if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
252 if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
253 snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
254 if (ast_fileexists(filename, NULL, NULL) > 0) {
255 ast_filedelete(filename, NULL);
257 ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
258 } else {
259 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
262 if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
263 snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
264 if (ast_fileexists(filename, NULL, NULL) > 0) {
265 ast_filedelete(filename, NULL);
267 ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
268 } else {
269 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
273 if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
274 char tmp[1024];
275 char tmp2[1024];
276 char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
277 char *name = chan->monitor->filename_base;
278 int directory = strchr(name, '/') ? 1 : 0;
279 char *dir = directory ? "" : ast_config_AST_MONITOR_DIR;
280 const char *execute, *execute_args;
282 /* Set the execute application */
283 execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
284 if (ast_strlen_zero(execute)) {
285 execute = "nice -n 19 soxmix";
286 delfiles = 1;
288 execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
289 if (ast_strlen_zero(execute_args)) {
290 execute_args = "";
293 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);
294 if (delfiles) {
295 snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
296 ast_copy_string(tmp, tmp2, sizeof(tmp));
298 ast_log(LOG_DEBUG,"monitor executing %s\n",tmp);
299 if (ast_safe_system(tmp) == -1)
300 ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
303 free(chan->monitor->format);
304 free(chan->monitor);
305 chan->monitor = NULL;
308 UNLOCK_IF_NEEDED(chan, need_lock);
310 return 0;
314 /* Pause monitoring of a channel */
315 int ast_monitor_pause(struct ast_channel *chan)
317 return ast_monitor_set_state(chan, AST_MONITOR_PAUSED);
320 /* Unpause monitoring of a channel */
321 int ast_monitor_unpause(struct ast_channel *chan)
323 return ast_monitor_set_state(chan, AST_MONITOR_RUNNING);
326 int pause_monitor_exec(struct ast_channel *chan, void *data)
328 return ast_monitor_pause(chan);
331 int unpause_monitor_exec(struct ast_channel *chan, void *data)
333 return ast_monitor_unpause(chan);
336 /* Change monitoring filename of a channel */
337 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
339 char tmp[256];
340 if (ast_strlen_zero(fname_base)) {
341 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null", chan->name);
342 return -1;
345 LOCK_IF_NEEDED(chan, need_lock);
347 if (chan->monitor) {
348 int directory = strchr(fname_base, '/') ? 1 : 0;
349 /* try creating the directory just in case it doesn't exist */
350 if (directory) {
351 char *name = strdup(fname_base);
352 snprintf(tmp, sizeof(tmp), "mkdir -p %s",dirname(name));
353 free(name);
354 ast_safe_system(tmp);
357 snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : ast_config_AST_MONITOR_DIR, fname_base);
358 } else {
359 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started\n", chan->name, fname_base);
362 UNLOCK_IF_NEEDED(chan, need_lock);
364 return 0;
367 static int start_monitor_exec(struct ast_channel *chan, void *data)
369 char *arg = NULL;
370 char *format = NULL;
371 char *fname_base = NULL;
372 char *options = NULL;
373 char *delay = NULL;
374 char *urlprefix = NULL;
375 char tmp[256];
376 int joinfiles = 0;
377 int waitforbridge = 0;
378 int res = 0;
380 /* Parse arguments. */
381 if (!ast_strlen_zero((char*)data)) {
382 arg = ast_strdupa((char*)data);
383 format = arg;
384 fname_base = strchr(arg, '|');
385 if (fname_base) {
386 *fname_base = 0;
387 fname_base++;
388 if ((options = strchr(fname_base, '|'))) {
389 *options = 0;
390 options++;
391 if (strchr(options, 'm'))
392 joinfiles = 1;
393 if (strchr(options, 'b'))
394 waitforbridge = 1;
397 arg = strchr(format,':');
398 if (arg) {
399 *arg++ = 0;
400 urlprefix = arg;
403 if (urlprefix) {
404 snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
405 ((strcmp(format,"gsm")) ? "wav" : "gsm"));
406 if (!chan->cdr && !(chan->cdr = ast_cdr_alloc()))
407 return -1;
408 ast_cdr_setuserfield(chan, tmp);
410 if (waitforbridge) {
411 /* We must remove the "b" option if listed. In principle none of
412 the following could give NULL results, but we check just to
413 be pedantic. Reconstructing with checks for 'm' option does not
414 work if we end up adding more options than 'm' in the future. */
415 delay = ast_strdupa(data);
416 options = strrchr(delay, '|');
417 if (options) {
418 arg = strchr(options, 'b');
419 if (arg) {
420 *arg = 'X';
421 pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
424 return 0;
427 res = ast_monitor_start(chan, format, fname_base, 1);
428 if (res < 0)
429 res = ast_monitor_change_fname(chan, fname_base, 1);
430 ast_monitor_setjoinfiles(chan, joinfiles);
432 return res;
435 static int stop_monitor_exec(struct ast_channel *chan, void *data)
437 return ast_monitor_stop(chan, 1);
440 static int change_monitor_exec(struct ast_channel *chan, void *data)
442 return ast_monitor_change_fname(chan, (const char*)data, 1);
445 static char start_monitor_action_help[] =
446 "Description: The 'Monitor' action may be used to record the audio on a\n"
447 " specified channel. The following parameters may be used to control\n"
448 " this:\n"
449 " Channel - Required. Used to specify the channel to record.\n"
450 " File - Optional. Is the name of the file created in the\n"
451 " monitor spool directory. Defaults to the same name\n"
452 " as the channel (with slashes replaced with dashes).\n"
453 " Format - Optional. Is the audio recording format. Defaults\n"
454 " to \"wav\".\n"
455 " Mix - Optional. Boolean parameter as to whether to mix\n"
456 " the input and output channels together after the\n"
457 " recording is finished.\n";
459 static int start_monitor_action(struct mansession *s, struct message *m)
461 struct ast_channel *c = NULL;
462 char *name = astman_get_header(m, "Channel");
463 char *fname = astman_get_header(m, "File");
464 char *format = astman_get_header(m, "Format");
465 char *mix = astman_get_header(m, "Mix");
466 char *d;
468 if (ast_strlen_zero(name)) {
469 astman_send_error(s, m, "No channel specified");
470 return 0;
472 c = ast_get_channel_by_name_locked(name);
473 if (!c) {
474 astman_send_error(s, m, "No such channel");
475 return 0;
478 if (ast_strlen_zero(fname)) {
479 /* No filename base specified, default to channel name as per CLI */
480 if (!(fname = ast_strdup(c->name))) {
481 astman_send_error(s, m, "Could not start monitoring channel");
482 ast_channel_unlock(c);
483 return 0;
485 /* Channels have the format technology/channel_name - have to replace that / */
486 if ((d = strchr(fname, '/')))
487 *d = '-';
490 if (ast_monitor_start(c, format, fname, 1)) {
491 if (ast_monitor_change_fname(c, fname, 1)) {
492 astman_send_error(s, m, "Could not start monitoring channel");
493 ast_channel_unlock(c);
494 return 0;
498 if (ast_true(mix)) {
499 ast_monitor_setjoinfiles(c, 1);
502 ast_channel_unlock(c);
503 astman_send_ack(s, m, "Started monitoring channel");
504 return 0;
507 static char stop_monitor_action_help[] =
508 "Description: The 'StopMonitor' action may be used to end a previously\n"
509 " started 'Monitor' action. The only parameter is 'Channel', the name\n"
510 " of the channel monitored.\n";
512 static int stop_monitor_action(struct mansession *s, struct message *m)
514 struct ast_channel *c = NULL;
515 char *name = astman_get_header(m, "Channel");
516 int res;
517 if (ast_strlen_zero(name)) {
518 astman_send_error(s, m, "No channel specified");
519 return 0;
521 c = ast_get_channel_by_name_locked(name);
522 if (!c) {
523 astman_send_error(s, m, "No such channel");
524 return 0;
526 res = ast_monitor_stop(c, 1);
527 ast_channel_unlock(c);
528 if (res) {
529 astman_send_error(s, m, "Could not stop monitoring channel");
530 return 0;
532 astman_send_ack(s, m, "Stopped monitoring channel");
533 return 0;
536 static char change_monitor_action_help[] =
537 "Description: The 'ChangeMonitor' action may be used to change the file\n"
538 " started by a previous 'Monitor' action. The following parameters may\n"
539 " be used to control this:\n"
540 " Channel - Required. Used to specify the channel to record.\n"
541 " File - Required. Is the new name of the file created in the\n"
542 " monitor spool directory.\n";
544 static int change_monitor_action(struct mansession *s, struct message *m)
546 struct ast_channel *c = NULL;
547 char *name = astman_get_header(m, "Channel");
548 char *fname = astman_get_header(m, "File");
549 if (ast_strlen_zero(name)) {
550 astman_send_error(s, m, "No channel specified");
551 return 0;
553 if (ast_strlen_zero(fname)) {
554 astman_send_error(s, m, "No filename specified");
555 return 0;
557 c = ast_get_channel_by_name_locked(name);
558 if (!c) {
559 astman_send_error(s, m, "No such channel");
560 return 0;
562 if (ast_monitor_change_fname(c, fname, 1)) {
563 astman_send_error(s, m, "Could not change monitored filename of channel");
564 ast_channel_unlock(c);
565 return 0;
567 ast_channel_unlock(c);
568 astman_send_ack(s, m, "Changed monitor filename");
569 return 0;
572 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
574 if (chan->monitor)
575 chan->monitor->joinfiles = turnon;
578 #define IS_NULL_STRING(string) ((!(string)) || (ast_strlen_zero((string))))
580 enum MONITOR_PAUSING_ACTION
582 MONITOR_ACTION_PAUSE,
583 MONITOR_ACTION_UNPAUSE
586 static int do_pause_or_unpause(struct mansession *s, struct message *m, int action)
588 struct ast_channel *c = NULL;
589 char *name = astman_get_header(m, "Channel");
591 if (IS_NULL_STRING(name)) {
592 astman_send_error(s, m, "No channel specified");
593 return -1;
596 c = ast_get_channel_by_name_locked(name);
597 if (!c) {
598 astman_send_error(s, m, "No such channel");
599 return -1;
602 if (action == MONITOR_ACTION_PAUSE)
603 ast_monitor_pause(c);
604 else
605 ast_monitor_unpause(c);
607 ast_channel_unlock(c);
608 astman_send_ack(s, m, "Paused monitoring of the channel");
609 return 0;
612 static char pause_monitor_action_help[] =
613 "Description: The 'PauseMonitor' action may be used to temporarily stop the\n"
614 " recording of a channel. The following parameters may\n"
615 " be used to control this:\n"
616 " Channel - Required. Used to specify the channel to record.\n";
618 static int pause_monitor_action(struct mansession *s, struct message *m)
620 return do_pause_or_unpause(s, m, MONITOR_ACTION_PAUSE);
623 static char unpause_monitor_action_help[] =
624 "Description: The 'UnpauseMonitor' action may be used to re-enable recording\n"
625 " of a channel after calling PauseMonitor. The following parameters may\n"
626 " be used to control this:\n"
627 " Channel - Required. Used to specify the channel to record.\n";
629 static int unpause_monitor_action(struct mansession *s, struct message *m)
631 return do_pause_or_unpause(s, m, MONITOR_ACTION_UNPAUSE);
635 static int load_module(void)
637 ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
638 ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
639 ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
640 ast_register_application("PauseMonitor", pause_monitor_exec, pausemonitor_synopsis, pausemonitor_descrip);
641 ast_register_application("UnpauseMonitor", unpause_monitor_exec, unpausemonitor_synopsis, unpausemonitor_descrip);
642 ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
643 ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
644 ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
645 ast_manager_register2("PauseMonitor", EVENT_FLAG_CALL, pause_monitor_action, pausemonitor_synopsis, pause_monitor_action_help);
646 ast_manager_register2("UnpauseMonitor", EVENT_FLAG_CALL, unpause_monitor_action, unpausemonitor_synopsis, unpause_monitor_action_help);
648 return 0;
651 static int unload_module(void)
653 ast_unregister_application("Monitor");
654 ast_unregister_application("StopMonitor");
655 ast_unregister_application("ChangeMonitor");
656 ast_unregister_application("PauseMonitor");
657 ast_unregister_application("UnpauseMonitor");
658 ast_manager_unregister("Monitor");
659 ast_manager_unregister("StopMonitor");
660 ast_manager_unregister("ChangeMonitor");
661 ast_unregister_application("PauseMonitor");
662 ast_unregister_application("UnpauseMonitor");
664 return 0;
667 /* usecount semantics need to be defined */
668 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Call Monitoring Resource",
669 .load = load_module,
670 .unload = unload_module,