Documentation: A couple of trivial fixes in sip.conf.sample and func_cdr.c
[asterisk/asterisk.git] / funcs / func_cdr.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999-2006, Digium, Inc.
5  *
6  * Portions Copyright (C) 2005, Anthony Minessale II
7  *
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.
13  *
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.
17  */
18
19 /*! \file
20  *
21  * \brief  Call Detail Record related dialplan functions
22  *
23  * \author Anthony Minessale II
24  *
25  * \ingroup functions
26  */
27
28 /*** MODULEINFO
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 ASTERISK_REGISTER_FILE()
35
36 #include "asterisk/module.h"
37 #include "asterisk/channel.h"
38 #include "asterisk/pbx.h"
39 #include "asterisk/utils.h"
40 #include "asterisk/app.h"
41 #include "asterisk/cdr.h"
42 #include "asterisk/stasis.h"
43 #include "asterisk/stasis_message_router.h"
44
45 /*** DOCUMENTATION
46         <function name="CDR" language="en_US">
47                 <synopsis>
48                         Gets or sets a CDR variable.
49                 </synopsis>
50                 <syntax>
51                         <parameter name="name" required="true">
52                                 <para>CDR field name:</para>
53                                 <enumlist>
54                                         <enum name="clid">
55                                                 <para>Caller ID.</para>
56                                         </enum>
57                                         <enum name="lastdata">
58                                                 <para>Last application arguments.</para>
59                                         </enum>
60                                         <enum name="disposition">
61                                                 <para>The final state of the CDR.</para>
62                                                 <enumlist>
63                                                         <enum name="0">
64                                                                 <para><literal>NO ANSWER</literal></para>
65                                                         </enum>
66                                                         <enum name="1">
67                                                                 <para><literal>NO ANSWER</literal> (NULL record)</para>
68                                                         </enum>
69                                                         <enum name="2">
70                                                                 <para><literal>FAILED</literal></para>
71                                                         </enum>
72                                                         <enum name="4">
73                                                                 <para><literal>BUSY</literal></para>
74                                                         </enum>
75                                                         <enum name="8">
76                                                                 <para><literal>ANSWERED</literal></para>
77                                                         </enum>
78                                                         <enum name="16">
79                                                                 <para><literal>CONGESTION</literal></para>
80                                                         </enum>
81                                                 </enumlist>
82                                         </enum>
83                                         <enum name="src">
84                                                 <para>Source.</para>
85                                         </enum>
86                                         <enum name="start">
87                                                 <para>Time the call started.</para>
88                                         </enum>
89                                         <enum name="amaflags">
90                                                 <para>R/W the Automatic Message Accounting (AMA) flags on the channel.
91                                                 When read from a channel, the integer value will always be returned.
92                                                 When written to a channel, both the string format or integer value
93                                                 is accepted.</para>
94                                                 <enumlist>
95                                                         <enum name="1"><para><literal>OMIT</literal></para></enum>
96                                                         <enum name="2"><para><literal>BILLING</literal></para></enum>
97                                                         <enum name="3"><para><literal>DOCUMENTATION</literal></para></enum>
98                                                 </enumlist>
99                                                 <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
100                                         </enum>
101                                         <enum name="dst">
102                                                 <para>Destination.</para>
103                                         </enum>
104                                         <enum name="answer">
105                                                 <para>Time the call was answered.</para>
106                                         </enum>
107                                         <enum name="accountcode">
108                                                 <para>The channel's account code.</para>
109                                                 <warning><para>Accessing this setting is deprecated in CDR. Please use the CHANNEL function instead.</para></warning>
110                                         </enum>
111                                         <enum name="dcontext">
112                                                 <para>Destination context.</para>
113                                         </enum>
114                                         <enum name="end">
115                                                 <para>Time the call ended.</para>
116                                         </enum>
117                                         <enum name="uniqueid">
118                                                 <para>The channel's unique id.</para>
119                                         </enum>
120                                         <enum name="dstchannel">
121                                                 <para>Destination channel.</para>
122                                         </enum>
123                                         <enum name="duration">
124                                                 <para>Duration of the call.</para>
125                                         </enum>
126                                         <enum name="userfield">
127                                                 <para>The channel's user specified field.</para>
128                                         </enum>
129                                         <enum name="lastapp">
130                                                 <para>Last application.</para>
131                                         </enum>
132                                         <enum name="billsec">
133                                                 <para>Duration of the call once it was answered.</para>
134                                         </enum>
135                                         <enum name="channel">
136                                                 <para>Channel name.</para>
137                                         </enum>
138                                         <enum name="sequence">
139                                                 <para>CDR sequence number.</para>
140                                         </enum>
141                                 </enumlist>
142                         </parameter>
143                         <parameter name="options" required="false">
144                                 <optionlist>
145                                         <option name="f">
146                                                 <para>Returns billsec or duration fields as floating point values.</para>
147                                         </option>
148                                         <option name="u">
149                                                 <para>Retrieves the raw, unprocessed value.</para>
150                                                 <para>For example, 'start', 'answer', and 'end' will be retrieved as epoch
151                                                 values, when the <literal>u</literal> option is passed, but formatted as YYYY-MM-DD HH:MM:SS
152                                                 otherwise.  Similarly, disposition and amaflags will return their raw
153                                                 integral values.</para>
154                                         </option>
155                                 </optionlist>
156                         </parameter>
157                 </syntax>
158                 <description>
159                         <para>All of the CDR field names are read-only, except for <literal>accountcode</literal>,
160                         <literal>userfield</literal>, and <literal>amaflags</literal>. You may, however, supply
161                         a name not on the above list, and create your own variable, whose value can be changed
162                         with this function, and this variable will be stored on the CDR.</para>
163                         <note><para>CDRs can only be modified before the bridge between two channels is
164                         torn down. For example, CDRs may not be modified after the <literal>Dial</literal>
165                         application has returned.</para></note>
166                         <para>Example: exten => 1,1,Set(CDR(userfield)=test)</para>
167                 </description>
168         </function>
169         <function name="CDR_PROP" language="en_US">
170                 <synopsis>
171                         Set a property on a channel's CDR.
172                 </synopsis>
173                 <syntax>
174                         <parameter name="name" required="true">
175                                 <para>The property to set on the CDR.</para>
176                                 <enumlist>
177                                         <enum name="party_a">
178                                                 <para>Set this channel as the preferred Party A when
179                                                 channels are associated together.</para>
180                                                 <para>Write-Only</para>
181                                         </enum>
182                                         <enum name="disable">
183                                                 <para>Setting to 1 will disable CDRs for this channel.
184                                                 Setting to 0 will enable CDRs for this channel.</para>
185                                                 <para>Write-Only</para>
186                                         </enum>
187                                 </enumlist>
188                         </parameter>
189                 </syntax>
190                 <description>
191                         <para>This function sets a property on a channel's CDR. Properties
192                         alter the behavior of how the CDR operates for that channel.</para>
193                 </description>
194         </function>
195  ***/
196
197 enum cdr_option_flags {
198         OPT_UNPARSED = (1 << 1),
199         OPT_FLOAT = (1 << 2),
200 };
201
202 AST_APP_OPTIONS(cdr_func_options, {
203         AST_APP_OPTION('f', OPT_FLOAT),
204         AST_APP_OPTION('u', OPT_UNPARSED),
205 });
206
207 struct cdr_func_payload {
208         struct ast_channel *chan;
209         const char *cmd;
210         const char *arguments;
211         const char *value;
212         void *data;
213 };
214
215 struct cdr_func_data {
216         char *buf;
217         size_t len;
218 };
219
220 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_read_message_type);
221 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_write_message_type);
222 STASIS_MESSAGE_TYPE_DEFN_LOCAL(cdr_prop_write_message_type);
223
224 static void cdr_read_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
225 {
226         struct cdr_func_payload *payload = stasis_message_data(message);
227         struct cdr_func_data *output;
228         char *info;
229         char *value = NULL;
230         struct ast_flags flags = { 0 };
231         char tempbuf[512];
232         AST_DECLARE_APP_ARGS(args,
233                 AST_APP_ARG(variable);
234                 AST_APP_ARG(options);
235         );
236
237         if (cdr_read_message_type() != stasis_message_type(message)) {
238                 return;
239         }
240
241         ast_assert(payload != NULL);
242         output = payload->data;
243         ast_assert(output != NULL);
244
245         if (ast_strlen_zero(payload->arguments)) {
246                 ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable[,option]))\n)",
247                         payload->cmd, payload->cmd);
248                 return;
249         }
250         info = ast_strdupa(payload->arguments);
251         AST_STANDARD_APP_ARGS(args, info);
252
253         if (!ast_strlen_zero(args.options)) {
254                 ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
255         }
256
257         if (ast_strlen_zero(ast_channel_name(payload->chan))) {
258                 /* Format request on a dummy channel */
259                 ast_cdr_format_var(ast_channel_cdr(payload->chan), args.variable, &value, tempbuf, sizeof(tempbuf), 0);
260                 if (ast_strlen_zero(value)) {
261                         return;
262                 }
263                 ast_copy_string(tempbuf, value, sizeof(tempbuf));
264                 ast_set_flag(&flags, OPT_UNPARSED);
265         } else if (ast_cdr_getvar(ast_channel_name(payload->chan), args.variable, tempbuf, sizeof(tempbuf))) {
266                 return;
267         }
268
269         if (ast_test_flag(&flags, OPT_FLOAT)
270                 && (!strcasecmp("billsec", args.variable) || !strcasecmp("duration", args.variable))) {
271                 long ms;
272                 double dtime;
273
274                 if (sscanf(tempbuf, "%30ld", &ms) != 1) {
275                         ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
276                                 args.variable, tempbuf, ast_channel_name(payload->chan));
277                         return;
278                 }
279                 dtime = (double)(ms / 1000.0);
280                 snprintf(tempbuf, sizeof(tempbuf), "%lf", dtime);
281         } else if (!ast_test_flag(&flags, OPT_UNPARSED)) {
282                 if (!strcasecmp("start", args.variable)
283                         || !strcasecmp("end", args.variable)
284                         || !strcasecmp("answer", args.variable)) {
285                         struct timeval fmt_time;
286                         struct ast_tm tm;
287                         /* tv_usec is suseconds_t, which could be int or long */
288                         long int tv_sec;
289                         long int tv_usec;
290
291                         if (sscanf(tempbuf, "%ld.%ld", &tv_sec, &tv_usec) != 2) {
292                                 ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
293                                         args.variable, tempbuf, ast_channel_name(payload->chan));
294                                 return;
295                         }
296                         if (tv_sec) {
297                                 fmt_time.tv_sec = tv_sec;
298                                 fmt_time.tv_usec = tv_usec;
299                                 ast_localtime(&fmt_time, &tm, NULL);
300                                 ast_strftime(tempbuf, sizeof(tempbuf), "%Y-%m-%d %T", &tm);
301                         } else {
302                                 tempbuf[0] = '\0';
303                         }
304                 } else if (!strcasecmp("disposition", args.variable)) {
305                         int disposition;
306
307                         if (sscanf(tempbuf, "%8d", &disposition) != 1) {
308                                 ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
309                                         args.variable, tempbuf, ast_channel_name(payload->chan));
310                                 return;
311                         }
312                         snprintf(tempbuf, sizeof(tempbuf), "%s", ast_cdr_disp2str(disposition));
313                 } else if (!strcasecmp("amaflags", args.variable)) {
314                         int amaflags;
315
316                         if (sscanf(tempbuf, "%8d", &amaflags) != 1) {
317                                 ast_log(AST_LOG_WARNING, "Unable to parse %s (%s) from the CDR for channel %s\n",
318                                         args.variable, tempbuf, ast_channel_name(payload->chan));
319                                 return;
320                         }
321                         snprintf(tempbuf, sizeof(tempbuf), "%s", ast_channel_amaflags2string(amaflags));
322                 }
323         }
324
325         ast_copy_string(output->buf, tempbuf, output->len);
326 }
327
328 static void cdr_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
329 {
330         struct cdr_func_payload *payload = stasis_message_data(message);
331         struct ast_flags flags = { 0 };
332         AST_DECLARE_APP_ARGS(args,
333                 AST_APP_ARG(variable);
334                 AST_APP_ARG(options);
335         );
336         char *parse;
337
338         if (cdr_write_message_type() != stasis_message_type(message)) {
339                 return;
340         }
341
342         if (!payload) {
343                 return;
344         }
345
346         if (ast_strlen_zero(payload->arguments)) {
347                 ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
348                         payload->cmd, payload->cmd);
349                 return;
350         }
351         if (ast_strlen_zero(payload->value)) {
352                 ast_log(AST_LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
353                         payload->cmd, payload->cmd);
354                 return;
355         }
356         parse = ast_strdupa(payload->arguments);
357         AST_STANDARD_APP_ARGS(args, parse);
358
359         if (!ast_strlen_zero(args.options)) {
360                 ast_app_parse_options(cdr_func_options, &flags, NULL, args.options);
361         }
362
363         if (!strcasecmp(args.variable, "accountcode")) {
364                 ast_log(AST_LOG_WARNING, "Using the CDR function to set 'accountcode' is deprecated. Please use the CHANNEL function instead.\n");
365                 ast_channel_lock(payload->chan);
366                 ast_channel_accountcode_set(payload->chan, payload->value);
367                 ast_channel_unlock(payload->chan);
368         } else if (!strcasecmp(args.variable, "peeraccount")) {
369                 ast_log(AST_LOG_WARNING, "The 'peeraccount' setting is not supported. Please set the 'accountcode' on the appropriate channel using the CHANNEL function.\n");
370         } else if (!strcasecmp(args.variable, "userfield")) {
371                 ast_cdr_setuserfield(ast_channel_name(payload->chan), payload->value);
372         } else if (!strcasecmp(args.variable, "amaflags")) {
373                 ast_log(AST_LOG_WARNING, "Using the CDR function to set 'amaflags' is deprecated. Please use the CHANNEL function instead.\n");
374                 if (isdigit(*payload->value)) {
375                         int amaflags;
376                         sscanf(payload->value, "%30d", &amaflags);
377                         ast_channel_lock(payload->chan);
378                         ast_channel_amaflags_set(payload->chan, amaflags);
379                         ast_channel_unlock(payload->chan);
380                 } else {
381                         ast_channel_lock(payload->chan);
382                         ast_channel_amaflags_set(payload->chan, ast_channel_string2amaflag(payload->value));
383                         ast_channel_unlock(payload->chan);
384                 }
385         } else {
386                 ast_cdr_setvar(ast_channel_name(payload->chan), args.variable, payload->value);
387         }
388         return;
389 }
390
391 static void cdr_prop_write_callback(void *data, struct stasis_subscription *sub, struct stasis_message *message)
392 {
393         struct cdr_func_payload *payload = stasis_message_data(message);
394         enum ast_cdr_options option;
395         char *parse;
396         AST_DECLARE_APP_ARGS(args,
397                 AST_APP_ARG(variable);
398                 AST_APP_ARG(options);
399         );
400
401         if (cdr_prop_write_message_type() != stasis_message_type(message)) {
402                 return;
403         }
404
405         if (!payload) {
406                 return;
407         }
408
409         if (ast_strlen_zero(payload->arguments)) {
410                 ast_log(AST_LOG_WARNING, "%s requires a variable (%s(variable)=value)\n)",
411                         payload->cmd, payload->cmd);
412                 return;
413         }
414         if (ast_strlen_zero(payload->value)) {
415                 ast_log(AST_LOG_WARNING, "%s requires a value (%s(variable)=value)\n)",
416                         payload->cmd, payload->cmd);
417                 return;
418         }
419         parse = ast_strdupa(payload->arguments);
420         AST_STANDARD_APP_ARGS(args, parse);
421
422         if (!strcasecmp("party_a", args.variable)) {
423                 option = AST_CDR_FLAG_PARTY_A;
424         } else if (!strcasecmp("disable", args.variable)) {
425                 option = AST_CDR_FLAG_DISABLE_ALL;
426         } else {
427                 ast_log(AST_LOG_WARNING, "Unknown option %s used with %s\n", args.variable, payload->cmd);
428                 return;
429         }
430
431         if (ast_true(payload->value)) {
432                 ast_cdr_set_property(ast_channel_name(payload->chan), option);
433         } else {
434                 ast_cdr_clear_property(ast_channel_name(payload->chan), option);
435         }
436 }
437
438
439 static int cdr_read(struct ast_channel *chan, const char *cmd, char *parse,
440                     char *buf, size_t len)
441 {
442         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
443         RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
444         struct cdr_func_data output = { 0, };
445
446         if (!chan) {
447                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
448                 return -1;
449         }
450
451         if (!cdr_read_message_type()) {
452                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
453                         ast_channel_name(chan));
454                 return -1;
455         }
456
457         payload = ao2_alloc(sizeof(*payload), NULL);
458         if (!payload) {
459                 return -1;
460         }
461         payload->chan = chan;
462         payload->cmd = cmd;
463         payload->arguments = parse;
464         payload->data = &output;
465
466         buf[0] = '\0';/* Ensure the buffer is initialized. */
467         output.buf = buf;
468         output.len = len;
469
470         message = stasis_message_create(cdr_read_message_type(), payload);
471         if (!message) {
472                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
473                         ast_channel_name(chan));
474                 return -1;
475         }
476
477         /* If this is a request on a dummy channel, we're doing post-processing on an
478          * already dispatched CDR. Simply call the callback to calculate the value and
479          * return, instead of posting to Stasis as we would for a running channel.
480          */
481         if (ast_strlen_zero(ast_channel_name(chan))) {
482                 cdr_read_callback(NULL, NULL, message);
483         } else {
484                 RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
485
486                 if (!router) {
487                         ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
488                                 ast_channel_name(chan));
489                         return -1;
490                 }
491                 stasis_message_router_publish_sync(router, message);
492         }
493
494         return 0;
495 }
496
497 static int cdr_write(struct ast_channel *chan, const char *cmd, char *parse,
498                      const char *value)
499 {
500         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
501         RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
502         RAII_VAR(struct stasis_message_router *, router,
503                      ast_cdr_message_router(), ao2_cleanup);
504
505         if (!chan) {
506                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
507                 return -1;
508         }
509
510         if (!router) {
511                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
512                         ast_channel_name(chan));
513                 return -1;
514         }
515
516         if (!cdr_write_message_type()) {
517                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
518                         ast_channel_name(chan));
519                 return -1;
520         }
521
522         payload = ao2_alloc(sizeof(*payload), NULL);
523         if (!payload) {
524                 return -1;
525         }
526         payload->chan = chan;
527         payload->cmd = cmd;
528         payload->arguments = parse;
529         payload->value = value;
530
531         message = stasis_message_create(cdr_write_message_type(), payload);
532         if (!message) {
533                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
534                         ast_channel_name(chan));
535                 return -1;
536         }
537         stasis_message_router_publish_sync(router, message);
538
539         return 0;
540 }
541
542 static int cdr_prop_write(struct ast_channel *chan, const char *cmd, char *parse,
543                      const char *value)
544 {
545         RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
546         RAII_VAR(struct cdr_func_payload *, payload, NULL, ao2_cleanup);
547         RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
548
549         if (!chan) {
550                 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", cmd);
551                 return -1;
552         }
553
554         if (!router) {
555                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: no message router\n",
556                         ast_channel_name(chan));
557                 return -1;
558         }
559
560         if (!cdr_write_message_type()) {
561                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: message type not available\n",
562                         ast_channel_name(chan));
563                 return -1;
564         }
565
566         payload = ao2_alloc(sizeof(*payload), NULL);
567         if (!payload) {
568                 return -1;
569         }
570         payload->chan = chan;
571         payload->cmd = cmd;
572         payload->arguments = parse;
573         payload->value = value;
574
575         message = stasis_message_create(cdr_prop_write_message_type(), payload);
576         if (!message) {
577                 ast_log(AST_LOG_WARNING, "Failed to manipulate CDR for channel %s: unable to create message\n",
578                         ast_channel_name(chan));
579                 return -1;
580         }
581         stasis_message_router_publish_sync(router, message);
582
583         return 0;
584 }
585
586 static struct ast_custom_function cdr_function = {
587         .name = "CDR",
588         .read = cdr_read,
589         .write = cdr_write,
590 };
591
592 static struct ast_custom_function cdr_prop_function = {
593         .name = "CDR_PROP",
594         .read = NULL,
595         .write = cdr_prop_write,
596 };
597
598 static int unload_module(void)
599 {
600         RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
601         int res = 0;
602
603         if (router) {
604                 stasis_message_router_remove(router, cdr_prop_write_message_type());
605                 stasis_message_router_remove(router, cdr_write_message_type());
606                 stasis_message_router_remove(router, cdr_read_message_type());
607         }
608         STASIS_MESSAGE_TYPE_CLEANUP(cdr_read_message_type);
609         STASIS_MESSAGE_TYPE_CLEANUP(cdr_write_message_type);
610         STASIS_MESSAGE_TYPE_CLEANUP(cdr_prop_write_message_type);
611         res |= ast_custom_function_unregister(&cdr_function);
612         res |= ast_custom_function_unregister(&cdr_prop_function);
613
614         return res;
615 }
616
617 static int load_module(void)
618 {
619         RAII_VAR(struct stasis_message_router *, router, ast_cdr_message_router(), ao2_cleanup);
620         int res = 0;
621
622         if (!router) {
623                 return AST_MODULE_LOAD_FAILURE;
624         }
625
626         res |= STASIS_MESSAGE_TYPE_INIT(cdr_read_message_type);
627         res |= STASIS_MESSAGE_TYPE_INIT(cdr_write_message_type);
628         res |= STASIS_MESSAGE_TYPE_INIT(cdr_prop_write_message_type);
629         res |= ast_custom_function_register(&cdr_function);
630         res |= ast_custom_function_register(&cdr_prop_function);
631         res |= stasis_message_router_add(router, cdr_prop_write_message_type(),
632                                          cdr_prop_write_callback, NULL);
633         res |= stasis_message_router_add(router, cdr_write_message_type(),
634                                          cdr_write_callback, NULL);
635         res |= stasis_message_router_add(router, cdr_read_message_type(),
636                                          cdr_read_callback, NULL);
637
638         if (res) {
639                 return AST_MODULE_LOAD_FAILURE;
640         }
641         return AST_MODULE_LOAD_SUCCESS;
642 }
643
644 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Call Detail Record (CDR) dialplan functions");