Add descriptions for monitor action events (bug #3610)
[asterisk/asterisk.git] / res / res_monitor.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <errno.h>
4 #include <string.h>
5 #include <sys/types.h>
6 #include <sys/stat.h>
7 #include <libgen.h>             /* dirname() */
8
9 #include <asterisk/lock.h>
10 #include <asterisk/channel.h>
11 #include <asterisk/logger.h>
12 #include <asterisk/file.h>
13 #include <asterisk/pbx.h>
14 #include <asterisk/module.h>
15 #include <asterisk/manager.h>
16 #include <asterisk/cli.h>
17 #include <asterisk/monitor.h>
18 #include <asterisk/app.h>
19 #include <asterisk/utils.h>
20 #include <asterisk/config.h>
21 #include "../asterisk.h"
22 #include "../astconf.h"
23
24 #define AST_MONITOR_DIR AST_SPOOL_DIR "/monitor"
25
26 AST_MUTEX_DEFINE_STATIC(monitorlock);
27
28 static unsigned long seq = 0;
29
30 static char *monitor_synopsis = "Monitor a channel";
31
32 static char *monitor_descrip = "Monitor([file_format[:urlbase]|[fname_base]|[options]]):\n"
33 "Used to start monitoring a channel. The channel's input and output\n"
34 "voice packets are logged to files until the channel hangs up or\n"
35 "monitoring is stopped by the StopMonitor application.\n"
36 "  file_format          optional, if not set, defaults to \"wav\"\n"
37 "  fname_base           if set, changes the filename used to the one specified.\n"
38 "  options:\n"
39 "    m   - when the recording ends mix the two leg files into one and\n"
40 "          delete the two leg files.  If the variable MONITOR_EXEC is set, the\n"
41 "          application referenced in it will be executed instead of\n"
42 "          soxmix and the raw leg files will NOT be deleted automatically.\n"
43 "          soxmix or MONITOR_EXEC is handed 3 arguments, the two leg files\n"
44 "          and a target mixed file name which is the same as the leg file names\n"
45 "          only without the in/out designator.\n"
46 "          If MONITOR_EXEC_ARGS is set, the contents will be passed on as\n"
47 "          additional arguements to MONITOR_EXEC\n"
48 "          Both MONITOR_EXEC and the Mix flag can be set from the\n"
49 "          administrator interface\n"
50 "\n"
51 "    b   - Don't begin recording unless a call is bridged to another channel\n"
52 "\nReturns -1 if monitor files can't be opened or if the channel is already\n"
53 "monitored, otherwise 0.\n"
54 ;
55
56 static char *stopmonitor_synopsis = "Stop monitoring a channel";
57
58 static char *stopmonitor_descrip = "StopMonitor\n"
59         "Stops monitoring a channel. Has no effect if the channel is not monitored\n";
60
61 static char *changemonitor_synopsis = "Change monitoring filename of a channel";
62
63 static char *changemonitor_descrip = "ChangeMonitor(filename_base)\n"
64         "Changes monitoring filename of a channel. Has no effect if the channel is not monitored\n"
65         "The argument is the new filename base to use for monitoring this channel.\n";
66
67 /* Start monitoring a channel */
68 int ast_monitor_start(  struct ast_channel *chan, const char *format_spec,
69                 const char *fname_base, int need_lock)
70 {
71         int res = 0;
72         char tmp[256];
73
74         if (need_lock) {
75                 if (ast_mutex_lock(&chan->lock)) {
76                         ast_log(LOG_WARNING, "Unable to lock channel\n");
77                         return -1;
78                 }
79         }
80
81         if (!(chan->monitor)) {
82                 struct ast_channel_monitor *monitor;
83                 char *channel_name, *p;
84
85                 /* Create monitoring directory if needed */
86                 if (mkdir(AST_MONITOR_DIR, 0770) < 0) {
87                         if (errno != EEXIST) {
88                                 ast_log(LOG_WARNING, "Unable to create audio monitor directory: %s\n",
89                                         strerror(errno));
90                         }
91                 }
92
93                 monitor = malloc(sizeof(struct ast_channel_monitor));
94                 if (!monitor) {
95                         if (need_lock) 
96                                 ast_mutex_unlock(&chan->lock);
97                         return -1;
98                 }
99                 memset(monitor, 0, sizeof(struct ast_channel_monitor));
100
101                 /* Determine file names */
102                 if (fname_base && !ast_strlen_zero(fname_base)) {
103                         int directory = strchr(fname_base, '/') ? 1 : 0;
104                         /* try creating the directory just in case it doesn't exist */
105                         if (directory) {
106                                 char *name = strdup(fname_base);
107                                 snprintf(tmp, sizeof(tmp), "mkdir -p \"%s\"",dirname(name));
108                                 free(name);
109                                 ast_safe_system(tmp);
110                         }
111                         snprintf(monitor->read_filename, FILENAME_MAX, "%s/%s-in",
112                                                 directory ? "" : AST_MONITOR_DIR, fname_base);
113                         snprintf(monitor->write_filename, FILENAME_MAX, "%s/%s-out",
114                                                 directory ? "" : AST_MONITOR_DIR, fname_base);
115                         strncpy(monitor->filename_base, fname_base, sizeof(monitor->filename_base) - 1);
116                 } else {
117                         ast_mutex_lock(&monitorlock);
118                         snprintf(monitor->read_filename, FILENAME_MAX, "%s/audio-in-%ld",
119                                                 AST_MONITOR_DIR, seq);
120                         snprintf(monitor->write_filename, FILENAME_MAX, "%s/audio-out-%ld",
121                                                 AST_MONITOR_DIR, seq);
122                         seq++;
123                         ast_mutex_unlock(&monitorlock);
124
125                         if((channel_name = ast_strdupa(chan->name))) {
126                                 while((p = strchr(channel_name, '/'))) {
127                                         *p = '-';
128                                 }
129                                 snprintf(monitor->filename_base, FILENAME_MAX, "%s/%ld-%s",
130                                                  AST_MONITOR_DIR, time(NULL),channel_name);
131                                 monitor->filename_changed = 1;
132                         } else {
133                                 ast_log(LOG_ERROR,"Failed to allocate Memory\n");
134                                 return -1;
135                         }
136                 }
137
138                 monitor->stop = ast_monitor_stop;
139
140                 /* Determine file format */
141                 if (format_spec && !ast_strlen_zero(format_spec)) {
142                         monitor->format = strdup(format_spec);
143                 } else {
144                         monitor->format = strdup("wav");
145                 }
146                 
147                 /* open files */
148                 if (ast_fileexists(monitor->read_filename, NULL, NULL) > 0) {
149                         ast_filedelete(monitor->read_filename, NULL);
150                 }
151                 if (!(monitor->read_stream = ast_writefile(monitor->read_filename,
152                                                 monitor->format, NULL,
153                                                 O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
154                         ast_log(LOG_WARNING, "Could not create file %s\n",
155                                                 monitor->read_filename);
156                         free(monitor);
157                         ast_mutex_unlock(&chan->lock);
158                         return -1;
159                 }
160                 if (ast_fileexists(monitor->write_filename, NULL, NULL) > 0) {
161                         ast_filedelete(monitor->write_filename, NULL);
162                 }
163                 if (!(monitor->write_stream = ast_writefile(monitor->write_filename,
164                                                 monitor->format, NULL,
165                                                 O_CREAT|O_TRUNC|O_WRONLY, 0, 0644))) {
166                         ast_log(LOG_WARNING, "Could not create file %s\n",
167                                                 monitor->write_filename);
168                         ast_closestream(monitor->read_stream);
169                         free(monitor);
170                         ast_mutex_unlock(&chan->lock);
171                         return -1;
172                 }
173                 chan->monitor = monitor;
174                 /* so we know this call has been monitored in case we need to bill for it or something */
175                 pbx_builtin_setvar_helper(chan, "__MONITORED","true");
176         } else {
177                 ast_log(LOG_DEBUG,"Cannot start monitoring %s, already monitored\n",
178                                         chan->name);
179                 res = -1;
180         }
181
182         if (need_lock) {
183                 ast_mutex_unlock(&chan->lock);
184         }
185         return res;
186 }
187
188 /* Stop monitoring a channel */
189 int ast_monitor_stop(struct ast_channel *chan, int need_lock)
190 {
191         char *execute, *execute_args;
192         int delfiles = 0;
193
194         if (need_lock) {
195                 if (ast_mutex_lock(&chan->lock)) {
196                         ast_log(LOG_WARNING, "Unable to lock channel\n");
197                         return -1;
198                 }
199         }
200
201         if (chan->monitor) {
202                 char filename[ FILENAME_MAX ];
203
204                 if (chan->monitor->read_stream) {
205                         ast_closestream(chan->monitor->read_stream);
206                 }
207                 if (chan->monitor->write_stream) {
208                         ast_closestream(chan->monitor->write_stream);
209                 }
210
211                 if (chan->monitor->filename_changed && !ast_strlen_zero(chan->monitor->filename_base)) {
212                         if (ast_fileexists(chan->monitor->read_filename,NULL,NULL) > 0) {
213                                 snprintf(filename, FILENAME_MAX, "%s-in", chan->monitor->filename_base);
214                                 if (ast_fileexists(filename, NULL, NULL) > 0) {
215                                         ast_filedelete(filename, NULL);
216                                 }
217                                 ast_filerename(chan->monitor->read_filename, filename, chan->monitor->format);
218                         } else {
219                                 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->read_filename);
220                         }
221
222                         if (ast_fileexists(chan->monitor->write_filename,NULL,NULL) > 0) {
223                                 snprintf(filename, FILENAME_MAX, "%s-out", chan->monitor->filename_base);
224                                 if (ast_fileexists(filename, NULL, NULL) > 0) {
225                                         ast_filedelete(filename, NULL);
226                                 }
227                                 ast_filerename(chan->monitor->write_filename, filename, chan->monitor->format);
228                         } else {
229                                 ast_log(LOG_WARNING, "File %s not found\n", chan->monitor->write_filename);
230                         }
231                 }
232
233                 if (chan->monitor->joinfiles && !ast_strlen_zero(chan->monitor->filename_base)) {
234                         char tmp[1024];
235                         char tmp2[1024];
236                         char *format = !strcasecmp(chan->monitor->format,"wav49") ? "WAV" : chan->monitor->format;
237                         char *name = chan->monitor->filename_base;
238                         int directory = strchr(name, '/') ? 1 : 0;
239                         char *dir = directory ? "" : AST_MONITOR_DIR;
240
241                         /* Set the execute application */
242                         execute = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC");
243                         if (!execute || ast_strlen_zero(execute)) { 
244                                 execute = "nice -n 19 soxmix";
245                                 delfiles = 1;
246                         } 
247                         execute_args = pbx_builtin_getvar_helper(chan, "MONITOR_EXEC_ARGS");
248                         if (!execute_args || ast_strlen_zero(execute_args)) {
249                                 execute_args = "";
250                         }
251                         
252                         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);
253                         if (delfiles) {
254                                 snprintf(tmp2,sizeof(tmp2), "( %s& rm -f \"%s/%s-\"* ) &",tmp, dir ,name); /* remove legs when done mixing */
255                                 strncpy(tmp, tmp2, sizeof(tmp) - 1);
256                         }
257                         ast_log(LOG_DEBUG,"monitor executing %s\n",tmp);
258                         if (ast_safe_system(tmp) == -1)
259                                 ast_log(LOG_WARNING, "Execute of %s failed.\n",tmp);
260                 }
261                 
262                 free(chan->monitor->format);
263                 free(chan->monitor);
264                 chan->monitor = NULL;
265         }
266
267         if (need_lock)
268                 ast_mutex_unlock(&chan->lock);
269         return 0;
270 }
271
272 /* Change monitoring filename of a channel */
273 int ast_monitor_change_fname(struct ast_channel *chan, const char *fname_base, int need_lock)
274 {
275         char tmp[256];
276         if ((!fname_base) || (ast_strlen_zero(fname_base))) {
277                 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to null", chan->name);
278                 return -1;
279         }
280         
281         if (need_lock) {
282                 if (ast_mutex_lock(&chan->lock)) {
283                         ast_log(LOG_WARNING, "Unable to lock channel\n");
284                         return -1;
285                 }
286         }
287
288         if (chan->monitor) {
289                 int directory = strchr(fname_base, '/') ? 1 : 0;
290                 /* try creating the directory just in case it doesn't exist */
291                 if (directory) {
292                         char *name = strdup(fname_base);
293                         snprintf(tmp, sizeof(tmp), "mkdir -p %s",dirname(name));
294                         free(name);
295                         ast_safe_system(tmp);
296                 }
297
298                 snprintf(chan->monitor->filename_base, FILENAME_MAX, "%s/%s", directory ? "" : AST_MONITOR_DIR, fname_base);
299         } else {
300                 ast_log(LOG_WARNING, "Cannot change monitor filename of channel %s to %s, monitoring not started", chan->name, fname_base);
301         }
302
303         if (need_lock)
304                 ast_mutex_unlock(&chan->lock);
305
306         return 0;
307 }
308
309 static int start_monitor_exec(struct ast_channel *chan, void *data)
310 {
311         char *arg = NULL;
312         char *format = NULL;
313         char *fname_base = NULL;
314         char *options = NULL;
315         char *delay = NULL;
316         char *urlprefix = NULL;
317         char tmp[256];
318         int joinfiles = 0;
319         int waitforbridge = 0;
320         int res = 0;
321         
322         /* Parse arguments. */
323         if (data && !ast_strlen_zero((char*)data)) {
324                 arg = ast_strdupa((char*)data);
325                 format = arg;
326                 fname_base = strchr(arg, '|');
327                 if (fname_base) {
328                         *fname_base = 0;
329                         fname_base++;
330                         if ((options = strchr(fname_base, '|'))) {
331                                 *options = 0;
332                                 options++;
333                                 if (strchr(options, 'm'))
334                                         joinfiles = 1;
335                                 if (strchr(options, 'b'))
336                                         waitforbridge = 1;
337                         }
338                 }
339                 arg = strchr(format,':');
340                 if (arg) {
341                         *arg++ = 0;
342                         urlprefix = arg;
343                 }
344         }
345         if (urlprefix) {
346                 snprintf(tmp,sizeof(tmp) - 1,"%s/%s.%s",urlprefix,fname_base,
347                         ((strcmp(format,"gsm")) ? "wav" : "gsm"));
348                 if (!chan->cdr)
349                         chan->cdr = ast_cdr_alloc();
350                 ast_cdr_setuserfield(chan, tmp);
351         }
352         if (waitforbridge) {
353                 /* We must remove the "b" option if listed.  In principle none of
354                    the following could give NULL results, but we check just to
355                    be pedantic. Reconstructing with checks for 'm' option does not
356                    work if we end up adding more options than 'm' in the future. */
357                 delay = ast_strdupa((char*)data);
358                 if (delay) {
359                         options = strrchr(delay, '|');
360                         if (options) {
361                                 arg = strchr(options, 'b');
362                                 if (arg) {
363                                         *arg = 'X';
364                                         pbx_builtin_setvar_helper(chan,"AUTO_MONITOR",delay);
365                                 }
366                         }
367                 }
368                 return 0;
369         }
370
371         res = ast_monitor_start(chan, format, fname_base, 1);
372         if (res < 0)
373                 res = ast_monitor_change_fname(chan, fname_base, 1);
374         ast_monitor_setjoinfiles(chan, joinfiles);
375
376         return res;
377 }
378
379 static int stop_monitor_exec(struct ast_channel *chan, void *data)
380 {
381         return ast_monitor_stop(chan, 1);
382 }
383
384 static int change_monitor_exec(struct ast_channel *chan, void *data)
385 {
386         return ast_monitor_change_fname(chan, (const char*)data, 1);
387 }
388
389 static char start_monitor_action_help[] =
390 "Description: The 'Monitor' action may be used to record the audio on a\n"
391 "  specified channel.  The following parameters may be used to control\n"
392 "  this:\n"
393 "  Channel     - Required.  Used to specify the channel to record.\n"
394 "  File        - Optional.  Is the name of the file created in the\n"
395 "                monitor spool directory.  Defaults to the same name\n"
396 "                as the channel (with slashes replaced with dashes).\n"
397 "  Format      - Optional.  Is the audio recording format.  Defaults\n"
398 "                to \"wav\".\n"
399 "  Mix         - Optional.  Boolean parameter as to whether to mix\n"
400 "                the input and output channels together after the\n"
401 "                recording is finished.\n";
402
403 static int start_monitor_action(struct mansession *s, struct message *m)
404 {
405         struct ast_channel *c = NULL;
406         char *name = astman_get_header(m, "Channel");
407         char *fname = astman_get_header(m, "File");
408         char *format = astman_get_header(m, "Format");
409         char *mix = astman_get_header(m, "Mix");
410         char *d;
411         
412         if ((!name) || (ast_strlen_zero(name))) {
413                 astman_send_error(s, m, "No channel specified");
414                 return 0;
415         }
416         c = ast_channel_walk_locked(NULL);
417         while (c) {
418                 if (!strcasecmp(c->name, name)) {
419                         break;
420                 }
421                 ast_mutex_unlock(&c->lock);
422                 c = ast_channel_walk_locked(c);
423         }
424         if (!c) {
425                 astman_send_error(s, m, "No such channel");
426                 return 0;
427         }
428
429         if ((!fname) || (ast_strlen_zero(fname))) {
430                 /* No filename base specified, default to channel name as per CLI */
431                 fname = malloc (FILENAME_MAX);
432                 if (!fname) {
433                         astman_send_error(s, m, "Could not start monitoring channel");
434                         ast_mutex_unlock(&c->lock);
435                         return 0;
436                 }
437                 memset(fname, 0, FILENAME_MAX);
438                 strncpy(fname, c->name, FILENAME_MAX-1);
439                 /* Channels have the format technology/channel_name - have to replace that /  */
440                 if ((d=strchr(fname, '/'))) *d='-';
441         }
442         
443         if (ast_monitor_start(c, format, fname, 1)) {
444                 if (ast_monitor_change_fname(c, fname, 1)) {
445                         astman_send_error(s, m, "Could not start monitoring channel");
446                         ast_mutex_unlock(&c->lock);
447                         return 0;
448                 }
449         }
450
451         if (ast_true(mix)) {
452                 ast_monitor_setjoinfiles(c, 1);
453         }
454
455         ast_mutex_unlock(&c->lock);
456         astman_send_ack(s, m, "Started monitoring channel");
457         return 0;
458 }
459
460 static char stop_monitor_action_help[] =
461 "Description: The 'StopMonitor' action may be used to end a previously\n"
462 "  started 'Monitor' action.  The only parameter is 'Channel', the name\n"
463 "  of the channel monitored.\n";
464
465 static int stop_monitor_action(struct mansession *s, struct message *m)
466 {
467         struct ast_channel *c = NULL;
468         char *name = astman_get_header(m, "Channel");
469         int res;
470         if ((!name) || (ast_strlen_zero(name))) {
471                 astman_send_error(s, m, "No channel specified");
472                 return 0;
473         }
474         c = ast_channel_walk_locked(NULL);
475         while(c) {
476                 if (!strcasecmp(c->name, name)) {
477                         break;
478                 }
479                 ast_mutex_unlock(&c->lock);
480                 c = ast_channel_walk_locked(c);
481         }
482         if (!c) {
483                 astman_send_error(s, m, "No such channel");
484                 return 0;
485         }
486         res = ast_monitor_stop(c, 1);
487         ast_mutex_unlock(&c->lock);
488         if (res) {
489                 astman_send_error(s, m, "Could not stop monitoring channel");
490                 return 0;
491         }
492         astman_send_ack(s, m, "Stopped monitoring channel");
493         return 0;
494 }
495
496 static char change_monitor_action_help[] =
497 "Description: The 'ChangeMonitor' action may be used to change the file\n"
498 "  started by a previous 'Monitor' action.  The following parameters may\n"
499 "  be used to control this:\n"
500 "  Channel     - Required.  Used to specify the channel to record.\n"
501 "  File        - Required.  Is the new name of the file created in the\n"
502 "                monitor spool directory.\n";
503
504 static int change_monitor_action(struct mansession *s, struct message *m)
505 {
506         struct ast_channel *c = NULL;
507         char *name = astman_get_header(m, "Channel");
508         char *fname = astman_get_header(m, "File");
509         if ((!name) || (ast_strlen_zero(name))) {
510                 astman_send_error(s, m, "No channel specified");
511                 return 0;
512         }
513         if ((!fname)||(ast_strlen_zero(fname))) {
514                 astman_send_error(s, m, "No filename specified");
515                 return 0;
516         }
517         c = ast_channel_walk_locked(NULL);
518         while(c) {
519                 if (!strcasecmp(c->name, name)) {
520                         break;
521                 }
522                 ast_mutex_unlock(&c->lock);
523                 c = ast_channel_walk_locked(c);
524         }
525         if (!c) {
526                 astman_send_error(s, m, "No such channel");
527                 return 0;
528         }
529         if (ast_monitor_change_fname(c, fname, 1)) {
530                 astman_send_error(s, m, "Could not change monitored filename of channel");
531                 ast_mutex_unlock(&c->lock);
532                 return 0;
533         }
534         ast_mutex_unlock(&c->lock);
535         astman_send_ack(s, m, "Stopped monitoring channel");
536         return 0;
537 }
538
539 void ast_monitor_setjoinfiles(struct ast_channel *chan, int turnon)
540 {
541         if (chan->monitor)
542                 chan->monitor->joinfiles = turnon;
543 }
544
545 int load_module(void)
546 {
547         ast_register_application("Monitor", start_monitor_exec, monitor_synopsis, monitor_descrip);
548         ast_register_application("StopMonitor", stop_monitor_exec, stopmonitor_synopsis, stopmonitor_descrip);
549         ast_register_application("ChangeMonitor", change_monitor_exec, changemonitor_synopsis, changemonitor_descrip);
550         ast_manager_register2("Monitor", EVENT_FLAG_CALL, start_monitor_action, monitor_synopsis, start_monitor_action_help);
551         ast_manager_register2("StopMonitor", EVENT_FLAG_CALL, stop_monitor_action, stopmonitor_synopsis, stop_monitor_action_help);
552         ast_manager_register2("ChangeMonitor", EVENT_FLAG_CALL, change_monitor_action, changemonitor_synopsis, change_monitor_action_help);
553
554         return 0;
555 }
556
557 int unload_module(void)
558 {
559         ast_unregister_application("Monitor");
560         ast_unregister_application("StopMonitor");
561         ast_unregister_application("ChangeMonitor");
562         ast_manager_unregister("Monitor");
563         ast_manager_unregister("StopMonitor");
564         ast_manager_unregister("ChangeMonitor");
565         return 0;
566 }
567
568 char *description(void)
569 {
570         return "Call Monitoring Resource";
571 }
572
573 int usecount(void)
574 {
575         /* Never allow monitor to be unloaded because it will
576            unresolve needed symbols in the channel */
577 #if 0
578         int res;
579         STANDARD_USECOUNT(res);
580         return res;
581 #else
582         return 1;
583 #endif
584 }
585
586 char *key()
587 {
588         return ASTERISK_GPL_KEY;
589 }