00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027
00028
00029
00030
00031 #include "asterisk.h"
00032
00033 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 411314 $")
00034
00035 #include <regex.h>
00036 #include <ctype.h>
00037
00038 #include "asterisk/module.h"
00039 #include "asterisk/channel.h"
00040 #include "asterisk/pbx.h"
00041 #include "asterisk/utils.h"
00042 #include "asterisk/app.h"
00043 #include "asterisk/indications.h"
00044 #include "asterisk/stringfields.h"
00045 #include "asterisk/global_datastores.h"
00046
00047
00048
00049
00050
00051
00052
00053
00054
00055
00056
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090
00091
00092
00093
00094
00095
00096
00097
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111
00112
00113
00114
00115
00116
00117
00118
00119
00120
00121
00122
00123
00124
00125
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140
00141
00142
00143
00144
00145
00146
00147
00148
00149
00150
00151
00152
00153
00154
00155
00156
00157
00158
00159
00160
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171
00172
00173
00174
00175
00176
00177
00178
00179
00180
00181
00182
00183
00184
00185
00186
00187
00188
00189
00190
00191
00192
00193
00194
00195
00196
00197
00198
00199
00200
00201
00202
00203
00204
00205
00206
00207
00208
00209
00210
00211
00212
00213
00214
00215
00216
00217
00218
00219
00220
00221
00222
00223
00224
00225
00226
00227
00228
00229
00230
00231
00232
00233
00234
00235
00236
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249
00250
00251
00252
00253
00254
00255
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266
00267
00268
00269
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282
00283
00284
00285
00286
00287
00288
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301
00302
00303
00304
00305
00306
00307
00308
00309
00310
00311
00312
00313
00314
00315
00316
00317
00318
00319
00320
00321
00322
00323
00324
00325
00326
00327
00328
00329
00330
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343
00344
00345
00346
00347
00348
00349
00350
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367
00368
00369
00370
00371
00372
00373
00374
00375
00376
00377
00378
00379
00380
00381
00382
00383 #define locked_copy_string(chan, dest, source, len) \
00384 do { \
00385 ast_channel_lock(chan); \
00386 ast_copy_string(dest, source, len); \
00387 ast_channel_unlock(chan); \
00388 } while (0)
00389 #define locked_string_field_set(chan, field, source) \
00390 do { \
00391 ast_channel_lock(chan); \
00392 ast_channel_##field##_set(chan, source); \
00393 ast_channel_unlock(chan); \
00394 } while (0)
00395
00396 static const char * const transfercapability_table[0x20] = {
00397 "SPEECH", "UNK", "UNK", "UNK", "UNK", "UNK", "UNK", "UNK",
00398 "DIGITAL", "RESTRICTED_DIGITAL", "UNK", "UNK", "UNK", "UNK", "UNK", "UNK",
00399 "3K1AUDIO", "DIGITAL_W_TONES", "UNK", "UNK", "UNK", "UNK", "UNK", "UNK",
00400 "VIDEO", "UNK", "UNK", "UNK", "UNK", "UNK", "UNK", "UNK", };
00401
00402 static int func_channel_read(struct ast_channel *chan, const char *function,
00403 char *data, char *buf, size_t len)
00404 {
00405 int ret = 0;
00406 struct ast_format_cap *tmpcap;
00407
00408 if (!chan) {
00409 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", function);
00410 return -1;
00411 }
00412
00413 if (!strcasecmp(data, "audionativeformat")) {
00414 char tmp[512];
00415
00416 if ((tmpcap = ast_format_cap_get_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_AUDIO))) {
00417 ast_copy_string(buf, ast_getformatname_multiple(tmp, sizeof(tmp), tmpcap), len);
00418 tmpcap = ast_format_cap_destroy(tmpcap);
00419 }
00420 } else if (!strcasecmp(data, "videonativeformat")) {
00421 char tmp[512];
00422
00423 if ((tmpcap = ast_format_cap_get_type(ast_channel_nativeformats(chan), AST_FORMAT_TYPE_VIDEO))) {
00424 ast_copy_string(buf, ast_getformatname_multiple(tmp, sizeof(tmp), tmpcap), len);
00425 tmpcap = ast_format_cap_destroy(tmpcap);
00426 }
00427 } else if (!strcasecmp(data, "audioreadformat")) {
00428 ast_copy_string(buf, ast_getformatname(ast_channel_readformat(chan)), len);
00429 } else if (!strcasecmp(data, "audiowriteformat")) {
00430 ast_copy_string(buf, ast_getformatname(ast_channel_writeformat(chan)), len);
00431 #ifdef CHANNEL_TRACE
00432 } else if (!strcasecmp(data, "trace")) {
00433 ast_channel_lock(chan);
00434 ast_copy_string(buf, ast_channel_trace_is_enabled(chan) ? "1" : "0", len);
00435 ast_channel_unlock(chan);
00436 #endif
00437 } else if (!strcasecmp(data, "tonezone") && ast_channel_zone(chan))
00438 locked_copy_string(chan, buf, ast_channel_zone(chan)->country, len);
00439 else if (!strcasecmp(data, "language"))
00440 locked_copy_string(chan, buf, ast_channel_language(chan), len);
00441 else if (!strcasecmp(data, "musicclass"))
00442 locked_copy_string(chan, buf, ast_channel_musicclass(chan), len);
00443 else if (!strcasecmp(data, "name")) {
00444 locked_copy_string(chan, buf, ast_channel_name(chan), len);
00445 } else if (!strcasecmp(data, "parkinglot"))
00446 locked_copy_string(chan, buf, ast_channel_parkinglot(chan), len);
00447 else if (!strcasecmp(data, "state"))
00448 locked_copy_string(chan, buf, ast_state2str(ast_channel_state(chan)), len);
00449 else if (!strcasecmp(data, "channeltype"))
00450 locked_copy_string(chan, buf, ast_channel_tech(chan)->type, len);
00451 else if (!strcasecmp(data, "accountcode"))
00452 locked_copy_string(chan, buf, ast_channel_accountcode(chan), len);
00453 else if (!strcasecmp(data, "checkhangup")) {
00454 ast_channel_lock(chan);
00455 ast_copy_string(buf, ast_check_hangup(chan) ? "1" : "0", len);
00456 ast_channel_unlock(chan);
00457 } else if (!strcasecmp(data, "peeraccount"))
00458 locked_copy_string(chan, buf, ast_channel_peeraccount(chan), len);
00459 else if (!strcasecmp(data, "hangupsource"))
00460 locked_copy_string(chan, buf, ast_channel_hangupsource(chan), len);
00461 else if (!strcasecmp(data, "appname") && ast_channel_appl(chan))
00462 locked_copy_string(chan, buf, ast_channel_appl(chan), len);
00463 else if (!strcasecmp(data, "appdata") && ast_channel_data(chan))
00464 locked_copy_string(chan, buf, ast_channel_data(chan), len);
00465 else if (!strcasecmp(data, "exten") && ast_channel_data(chan))
00466 locked_copy_string(chan, buf, ast_channel_exten(chan), len);
00467 else if (!strcasecmp(data, "context") && ast_channel_data(chan))
00468 locked_copy_string(chan, buf, ast_channel_context(chan), len);
00469 else if (!strcasecmp(data, "userfield") && ast_channel_data(chan))
00470 locked_copy_string(chan, buf, ast_channel_userfield(chan), len);
00471 else if (!strcasecmp(data, "channame") && ast_channel_data(chan))
00472 locked_copy_string(chan, buf, ast_channel_name(chan), len);
00473 else if (!strcasecmp(data, "linkedid")) {
00474 ast_channel_lock(chan);
00475 if (ast_strlen_zero(ast_channel_linkedid(chan))) {
00476
00477 ast_copy_string(buf, ast_channel_uniqueid(chan), len);
00478 }
00479 else {
00480 ast_copy_string(buf, ast_channel_linkedid(chan), len);
00481 }
00482 ast_channel_unlock(chan);
00483 } else if (!strcasecmp(data, "peer")) {
00484 struct ast_channel *p;
00485
00486 ast_channel_lock(chan);
00487 p = ast_bridged_channel(chan);
00488 if (p || ast_channel_tech(chan) || ast_channel_cdr(chan))
00489 ast_copy_string(buf, (p ? ast_channel_name(p) : ""), len);
00490 else {
00491
00492
00493 const char *pname = pbx_builtin_getvar_helper(chan, "BRIDGEPEER");
00494 if (!ast_strlen_zero(pname))
00495 ast_copy_string(buf, pname, len);
00496 else
00497 buf[0] = 0;
00498 }
00499 ast_channel_unlock(chan);
00500 } else if (!strcasecmp(data, "uniqueid")) {
00501 locked_copy_string(chan, buf, ast_channel_uniqueid(chan), len);
00502 } else if (!strcasecmp(data, "transfercapability")) {
00503 locked_copy_string(chan, buf, transfercapability_table[ast_channel_transfercapability(chan) & 0x1f], len);
00504 } else if (!strcasecmp(data, "callgroup")) {
00505 char groupbuf[256];
00506
00507 locked_copy_string(chan, buf, ast_print_group(groupbuf, sizeof(groupbuf), ast_channel_callgroup(chan)), len);
00508 } else if (!strcasecmp(data, "pickupgroup")) {
00509 char groupbuf[256];
00510
00511 locked_copy_string(chan, buf, ast_print_group(groupbuf, sizeof(groupbuf), ast_channel_pickupgroup(chan)), len);
00512 } else if (!strcasecmp(data, "namedcallgroup")) {
00513 struct ast_str *tmp_str = ast_str_alloca(1024);
00514
00515 locked_copy_string(chan, buf, ast_print_namedgroups(&tmp_str, ast_channel_named_callgroups(chan)), len);
00516 } else if (!strcasecmp(data, "namedpickupgroup")) {
00517 struct ast_str *tmp_str = ast_str_alloca(1024);
00518
00519 locked_copy_string(chan, buf, ast_print_namedgroups(&tmp_str, ast_channel_named_pickupgroups(chan)), len);
00520 } else if (!strcasecmp(data, "amaflags")) {
00521 ast_channel_lock(chan);
00522 snprintf(buf, len, "%d", ast_channel_amaflags(chan));
00523 ast_channel_unlock(chan);
00524 } else if (!strncasecmp(data, "secure_bridge_", 14)) {
00525 struct ast_datastore *ds;
00526
00527 ast_channel_lock(chan);
00528 if ((ds = ast_channel_datastore_find(chan, &secure_call_info, NULL))) {
00529 struct ast_secure_call_store *encrypt = ds->data;
00530 if (!strcasecmp(data, "secure_bridge_signaling")) {
00531 snprintf(buf, len, "%s", encrypt->signaling ? "1" : "");
00532 } else if (!strcasecmp(data, "secure_bridge_media")) {
00533 snprintf(buf, len, "%s", encrypt->media ? "1" : "");
00534 }
00535 }
00536 ast_channel_unlock(chan);
00537 } else if (!ast_channel_tech(chan) || !ast_channel_tech(chan)->func_channel_read || ast_channel_tech(chan)->func_channel_read(chan, function, data, buf, len)) {
00538 ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n", data);
00539 ret = -1;
00540 }
00541
00542 return ret;
00543 }
00544
00545 static int func_channel_write_real(struct ast_channel *chan, const char *function,
00546 char *data, const char *value)
00547 {
00548 int ret = 0;
00549 signed char gainset;
00550
00551 if (!strcasecmp(data, "language"))
00552 locked_string_field_set(chan, language, value);
00553 else if (!strcasecmp(data, "parkinglot"))
00554 locked_string_field_set(chan, parkinglot, value);
00555 else if (!strcasecmp(data, "musicclass"))
00556 locked_string_field_set(chan, musicclass, value);
00557 else if (!strcasecmp(data, "accountcode"))
00558 locked_string_field_set(chan, accountcode, value);
00559 else if (!strcasecmp(data, "userfield"))
00560 locked_string_field_set(chan, userfield, value);
00561 else if (!strcasecmp(data, "amaflags")) {
00562 ast_channel_lock(chan);
00563 if(isdigit(*value)) {
00564 int amaflags;
00565 sscanf(value, "%30d", &amaflags);
00566 ast_channel_amaflags_set(chan, amaflags);
00567 } else if (!strcasecmp(value,"OMIT")){
00568 ast_channel_amaflags_set(chan, 1);
00569 } else if (!strcasecmp(value,"BILLING")){
00570 ast_channel_amaflags_set(chan, 2);
00571 } else if (!strcasecmp(value,"DOCUMENTATION")){
00572 ast_channel_amaflags_set(chan, 3);
00573 }
00574 ast_channel_unlock(chan);
00575 } else if (!strcasecmp(data, "peeraccount"))
00576 locked_string_field_set(chan, peeraccount, value);
00577 else if (!strcasecmp(data, "hangupsource"))
00578
00579 ast_set_hangupsource(chan, value, 0);
00580 #ifdef CHANNEL_TRACE
00581 else if (!strcasecmp(data, "trace")) {
00582 ast_channel_lock(chan);
00583 if (ast_true(value))
00584 ret = ast_channel_trace_enable(chan);
00585 else if (ast_false(value))
00586 ret = ast_channel_trace_disable(chan);
00587 else {
00588 ret = -1;
00589 ast_log(LOG_WARNING, "Invalid value for CHANNEL(trace).\n");
00590 }
00591 ast_channel_unlock(chan);
00592 }
00593 #endif
00594 else if (!strcasecmp(data, "tonezone")) {
00595 struct ast_tone_zone *new_zone;
00596 if (!(new_zone = ast_get_indication_zone(value))) {
00597 ast_log(LOG_ERROR, "Unknown country code '%s' for tonezone. Check indications.conf for available country codes.\n", value);
00598 ret = -1;
00599 } else {
00600 ast_channel_lock(chan);
00601 if (ast_channel_zone(chan)) {
00602 ast_channel_zone_set(chan, ast_tone_zone_unref(ast_channel_zone(chan)));
00603 }
00604 ast_channel_zone_set(chan, ast_tone_zone_ref(new_zone));
00605 ast_channel_unlock(chan);
00606 new_zone = ast_tone_zone_unref(new_zone);
00607 }
00608 } else if (!strcasecmp(data, "callgroup")) {
00609 ast_channel_lock(chan);
00610 ast_channel_callgroup_set(chan, ast_get_group(value));
00611 ast_channel_unlock(chan);
00612 } else if (!strcasecmp(data, "pickupgroup")) {
00613 ast_channel_lock(chan);
00614 ast_channel_pickupgroup_set(chan, ast_get_group(value));
00615 ast_channel_unlock(chan);
00616 } else if (!strcasecmp(data, "namedcallgroup")) {
00617 struct ast_namedgroups *groups = ast_get_namedgroups(value);
00618
00619 ast_channel_lock(chan);
00620 ast_channel_named_callgroups_set(chan, groups);
00621 ast_channel_unlock(chan);
00622 ast_unref_namedgroups(groups);
00623 } else if (!strcasecmp(data, "namedpickupgroup")) {
00624 struct ast_namedgroups *groups = ast_get_namedgroups(value);
00625
00626 ast_channel_lock(chan);
00627 ast_channel_named_pickupgroups_set(chan, groups);
00628 ast_channel_unlock(chan);
00629 ast_unref_namedgroups(groups);
00630 } else if (!strcasecmp(data, "txgain")) {
00631 sscanf(value, "%4hhd", &gainset);
00632 ast_channel_setoption(chan, AST_OPTION_TXGAIN, &gainset, sizeof(gainset), 0);
00633 } else if (!strcasecmp(data, "rxgain")) {
00634 sscanf(value, "%4hhd", &gainset);
00635 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &gainset, sizeof(gainset), 0);
00636 } else if (!strcasecmp(data, "transfercapability")) {
00637 unsigned short i;
00638
00639 ast_channel_lock(chan);
00640 for (i = 0; i < 0x20; i++) {
00641 if (!strcasecmp(transfercapability_table[i], value) && strcmp(value, "UNK")) {
00642 ast_channel_transfercapability_set(chan, i);
00643 break;
00644 }
00645 }
00646 ast_channel_unlock(chan);
00647 } else if (!strcasecmp(data, "hangup_handler_pop")) {
00648
00649 ast_pbx_hangup_handler_pop(chan);
00650 ast_pbx_hangup_handler_push(chan, value);
00651 } else if (!strcasecmp(data, "hangup_handler_push")) {
00652 ast_pbx_hangup_handler_push(chan, value);
00653 } else if (!strcasecmp(data, "hangup_handler_wipe")) {
00654
00655 while (ast_pbx_hangup_handler_pop(chan)) {
00656 }
00657 ast_pbx_hangup_handler_push(chan, value);
00658 } else if (!strncasecmp(data, "secure_bridge_", 14)) {
00659 struct ast_datastore *ds;
00660 struct ast_secure_call_store *store;
00661
00662 if (!chan || !value) {
00663 return -1;
00664 }
00665
00666 ast_channel_lock(chan);
00667 if (!(ds = ast_channel_datastore_find(chan, &secure_call_info, NULL))) {
00668 if (!(ds = ast_datastore_alloc(&secure_call_info, NULL))) {
00669 ast_channel_unlock(chan);
00670 return -1;
00671 }
00672 if (!(store = ast_calloc(1, sizeof(*store)))) {
00673 ast_channel_unlock(chan);
00674 ast_free(ds);
00675 return -1;
00676 }
00677 ds->data = store;
00678 ast_channel_datastore_add(chan, ds);
00679 } else {
00680 store = ds->data;
00681 }
00682
00683 if (!strcasecmp(data, "secure_bridge_signaling")) {
00684 store->signaling = ast_true(value) ? 1 : 0;
00685 } else if (!strcasecmp(data, "secure_bridge_media")) {
00686 store->media = ast_true(value) ? 1 : 0;
00687 }
00688 ast_channel_unlock(chan);
00689 } else if (!ast_channel_tech(chan)->func_channel_write
00690 || ast_channel_tech(chan)->func_channel_write(chan, function, data, value)) {
00691 ast_log(LOG_WARNING, "Unknown or unavailable item requested: '%s'\n",
00692 data);
00693 ret = -1;
00694 }
00695
00696 return ret;
00697 }
00698
00699 static int func_channel_write(struct ast_channel *chan, const char *function, char *data, const char *value)
00700 {
00701 int res;
00702 ast_chan_write_info_t write_info = {
00703 .version = AST_CHAN_WRITE_INFO_T_VERSION,
00704 .write_fn = func_channel_write_real,
00705 .chan = chan,
00706 .function = function,
00707 .data = data,
00708 .value = value,
00709 };
00710
00711 if (!chan) {
00712 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", function);
00713 return -1;
00714 }
00715
00716 res = func_channel_write_real(chan, function, data, value);
00717 ast_channel_setoption(chan, AST_OPTION_CHANNEL_WRITE, &write_info, sizeof(write_info), 0);
00718
00719 return res;
00720 }
00721
00722 static struct ast_custom_function channel_function = {
00723 .name = "CHANNEL",
00724 .read = func_channel_read,
00725 .write = func_channel_write,
00726 };
00727
00728 static int func_channels_read(struct ast_channel *chan, const char *function, char *data, char *buf, size_t maxlen)
00729 {
00730 struct ast_channel *c = NULL;
00731 regex_t re;
00732 int res;
00733 size_t buflen = 0;
00734 struct ast_channel_iterator *iter;
00735
00736 buf[0] = '\0';
00737
00738 if (!ast_strlen_zero(data)) {
00739 if ((res = regcomp(&re, data, REG_EXTENDED | REG_ICASE | REG_NOSUB))) {
00740 regerror(res, &re, buf, maxlen);
00741 ast_log(LOG_WARNING, "Error compiling regular expression for %s(%s): %s\n", function, data, buf);
00742 return -1;
00743 }
00744 }
00745
00746 if (!(iter = ast_channel_iterator_all_new())) {
00747 if (!ast_strlen_zero(data)) {
00748 regfree(&re);
00749 }
00750 return -1;
00751 }
00752
00753 while ((c = ast_channel_iterator_next(iter))) {
00754 ast_channel_lock(c);
00755 if (ast_strlen_zero(data) || regexec(&re, ast_channel_name(c), 0, NULL, 0) == 0) {
00756 size_t namelen = strlen(ast_channel_name(c));
00757 if (buflen + namelen + (ast_strlen_zero(buf) ? 0 : 1) + 1 < maxlen) {
00758 if (!ast_strlen_zero(buf)) {
00759 strcat(buf, " ");
00760 buflen++;
00761 }
00762 strcat(buf, ast_channel_name(c));
00763 buflen += namelen;
00764 } else {
00765 ast_log(LOG_WARNING, "Number of channels exceeds the available buffer space. Output will be truncated!\n");
00766 }
00767 }
00768 ast_channel_unlock(c);
00769 c = ast_channel_unref(c);
00770 }
00771
00772 ast_channel_iterator_destroy(iter);
00773
00774 if (!ast_strlen_zero(data)) {
00775 regfree(&re);
00776 }
00777
00778 return 0;
00779 }
00780
00781 static struct ast_custom_function channels_function = {
00782 .name = "CHANNELS",
00783 .read = func_channels_read,
00784 };
00785
00786 static int func_mchan_read(struct ast_channel *chan, const char *function,
00787 char *data, struct ast_str **buf, ssize_t len)
00788 {
00789 struct ast_channel *mchan;
00790 char *template = ast_alloca(4 + strlen(data));
00791
00792 if (!chan) {
00793 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", function);
00794 return -1;
00795 }
00796
00797 mchan = ast_channel_get_by_name(ast_channel_linkedid(chan));
00798 sprintf(template, "${%s}", data);
00799 ast_str_substitute_variables(buf, len, mchan ? mchan : chan, template);
00800 if (mchan) {
00801 ast_channel_unref(mchan);
00802 }
00803 return 0;
00804 }
00805
00806 static int func_mchan_write(struct ast_channel *chan, const char *function,
00807 char *data, const char *value)
00808 {
00809 struct ast_channel *mchan;
00810
00811 if (!chan) {
00812 ast_log(LOG_WARNING, "No channel was provided to %s function.\n", function);
00813 return -1;
00814 }
00815
00816 mchan = ast_channel_get_by_name(ast_channel_linkedid(chan));
00817 pbx_builtin_setvar_helper(mchan ? mchan : chan, data, value);
00818 if (mchan) {
00819 ast_channel_unref(mchan);
00820 }
00821 return 0;
00822 }
00823
00824 static struct ast_custom_function mchan_function = {
00825 .name = "MASTER_CHANNEL",
00826 .read2 = func_mchan_read,
00827 .write = func_mchan_write,
00828 };
00829
00830 static int unload_module(void)
00831 {
00832 int res = 0;
00833
00834 res |= ast_custom_function_unregister(&channel_function);
00835 res |= ast_custom_function_unregister(&channels_function);
00836 res |= ast_custom_function_unregister(&mchan_function);
00837
00838 return res;
00839 }
00840
00841 static int load_module(void)
00842 {
00843 int res = 0;
00844
00845 res |= ast_custom_function_register(&channel_function);
00846 res |= ast_custom_function_register(&channels_function);
00847 res |= ast_custom_function_register(&mchan_function);
00848
00849 return res;
00850 }
00851
00852 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Channel information dialplan functions");