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
00032 #include "asterisk.h"
00033
00034 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 414270 $")
00035
00036 #include <fcntl.h>
00037 #include <sys/signal.h>
00038
00039 #include "asterisk/lock.h"
00040 #include "asterisk/causes.h"
00041 #include "asterisk/channel.h"
00042 #include "asterisk/config.h"
00043 #include "asterisk/module.h"
00044 #include "asterisk/pbx.h"
00045 #include "asterisk/sched.h"
00046 #include "asterisk/io.h"
00047 #include "asterisk/acl.h"
00048 #include "asterisk/callerid.h"
00049 #include "asterisk/file.h"
00050 #include "asterisk/cli.h"
00051 #include "asterisk/app.h"
00052 #include "asterisk/musiconhold.h"
00053 #include "asterisk/manager.h"
00054 #include "asterisk/stringfields.h"
00055 #include "asterisk/devicestate.h"
00056 #include "asterisk/astobj2.h"
00057
00058
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077 static const char tdesc[] = "Local Proxy Channel Driver";
00078
00079 #define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
00080
00081
00082
00083
00084 static const int BUCKET_SIZE = 1;
00085
00086 static struct ao2_container *locals;
00087
00088 static unsigned int name_sequence = 0;
00089
00090 static struct ast_jb_conf g_jb_conf = {
00091 .flags = 0,
00092 .max_size = -1,
00093 .resync_threshold = -1,
00094 .impl = "",
00095 .target_extra = -1,
00096 };
00097
00098 static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause);
00099 static int local_digit_begin(struct ast_channel *ast, char digit);
00100 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
00101 static int local_call(struct ast_channel *ast, const char *dest, int timeout);
00102 static int local_hangup(struct ast_channel *ast);
00103 static int local_answer(struct ast_channel *ast);
00104 static struct ast_frame *local_read(struct ast_channel *ast);
00105 static int local_write(struct ast_channel *ast, struct ast_frame *f);
00106 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
00107 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00108 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
00109 static int local_sendtext(struct ast_channel *ast, const char *text);
00110 static int local_devicestate(const char *data);
00111 static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge);
00112 static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen);
00113 static int local_setoption(struct ast_channel *chan, int option, void *data, int datalen);
00114
00115
00116 static struct ast_channel_tech local_tech = {
00117 .type = "Local",
00118 .description = tdesc,
00119 .requester = local_request,
00120 .send_digit_begin = local_digit_begin,
00121 .send_digit_end = local_digit_end,
00122 .call = local_call,
00123 .hangup = local_hangup,
00124 .answer = local_answer,
00125 .read = local_read,
00126 .write = local_write,
00127 .write_video = local_write,
00128 .exception = local_read,
00129 .indicate = local_indicate,
00130 .fixup = local_fixup,
00131 .send_html = local_sendhtml,
00132 .send_text = local_sendtext,
00133 .devicestate = local_devicestate,
00134 .bridged_channel = local_bridgedchannel,
00135 .queryoption = local_queryoption,
00136 .setoption = local_setoption,
00137 };
00138
00139
00140
00141
00142
00143
00144
00145
00146 struct local_pvt {
00147 unsigned int flags;
00148 char context[AST_MAX_CONTEXT];
00149 char exten[AST_MAX_EXTENSION];
00150 struct ast_format_cap *reqcap;
00151 struct ast_jb_conf jb_conf;
00152 struct ast_channel *owner;
00153 struct ast_channel *chan;
00154 };
00155
00156 #define LOCAL_ALREADY_MASQED (1 << 0)
00157 #define LOCAL_LAUNCHED_PBX (1 << 1)
00158 #define LOCAL_NO_OPTIMIZATION (1 << 2)
00159 #define LOCAL_BRIDGE (1 << 3)
00160 #define LOCAL_MOH_PASSTHRU (1 << 4)
00161
00162
00163
00164
00165
00166
00167
00168
00169
00170
00171 static void awesome_locking(struct local_pvt *p, struct ast_channel **outchan, struct ast_channel **outowner)
00172 {
00173 struct ast_channel *chan = NULL;
00174 struct ast_channel *owner = NULL;
00175
00176 for (;;) {
00177 ao2_lock(p);
00178 if (p->chan) {
00179 chan = p->chan;
00180 ast_channel_ref(chan);
00181 }
00182 if (p->owner) {
00183 owner = p->owner;
00184 ast_channel_ref(owner);
00185 }
00186 ao2_unlock(p);
00187
00188
00189 if (!owner || !chan) {
00190 if (owner) {
00191 ast_channel_lock(owner);
00192 } else if(chan) {
00193 ast_channel_lock(chan);
00194 }
00195 ao2_lock(p);
00196 } else {
00197
00198 ast_channel_lock_both(chan, owner);
00199 ao2_lock(p);
00200 }
00201
00202
00203 if (p->owner != owner || p->chan != chan) {
00204 if (owner) {
00205 ast_channel_unlock(owner);
00206 owner = ast_channel_unref(owner);
00207 }
00208 if (chan) {
00209 ast_channel_unlock(chan);
00210 chan = ast_channel_unref(chan);
00211 }
00212 ao2_unlock(p);
00213 continue;
00214 }
00215
00216 break;
00217 }
00218 *outowner = p->owner;
00219 *outchan = p->chan;
00220 }
00221
00222
00223 static int local_setoption(struct ast_channel *ast, int option, void * data, int datalen)
00224 {
00225 int res = 0;
00226 struct local_pvt *p = NULL;
00227 struct ast_channel *otherchan = NULL;
00228 ast_chan_write_info_t *write_info;
00229
00230 if (option != AST_OPTION_CHANNEL_WRITE) {
00231 return -1;
00232 }
00233
00234 write_info = data;
00235
00236 if (write_info->version != AST_CHAN_WRITE_INFO_T_VERSION) {
00237 ast_log(LOG_ERROR, "The chan_write_info_t type has changed, and this channel hasn't been updated!\n");
00238 return -1;
00239 }
00240
00241 if (!strcmp(write_info->function, "CHANNEL")
00242 && !strncasecmp(write_info->data, "hangup_handler_", 15)) {
00243
00244 return 0;
00245 }
00246
00247
00248 if (!(p = ast_channel_tech_pvt(ast))) {
00249 return -1;
00250 }
00251 ao2_ref(p, 1);
00252 ast_channel_unlock(ast);
00253
00254
00255 ao2_lock(p);
00256 otherchan = (write_info->chan == p->owner) ? p->chan : p->owner;
00257 if (!otherchan || otherchan == write_info->chan) {
00258 res = -1;
00259 otherchan = NULL;
00260 ao2_unlock(p);
00261 goto setoption_cleanup;
00262 }
00263 ast_channel_ref(otherchan);
00264
00265
00266 ao2_unlock(p);
00267
00268 ast_channel_lock(otherchan);
00269 res = write_info->write_fn(otherchan, write_info->function, write_info->data, write_info->value);
00270 ast_channel_unlock(otherchan);
00271
00272 setoption_cleanup:
00273 if (p) {
00274 ao2_ref(p, -1);
00275 }
00276 if (otherchan) {
00277 ast_channel_unref(otherchan);
00278 }
00279 ast_channel_lock(ast);
00280 return res;
00281 }
00282
00283
00284 static int local_devicestate(const char *data)
00285 {
00286 char *exten = ast_strdupa(data);
00287 char *context = NULL, *opts = NULL;
00288 int res;
00289 struct local_pvt *lp;
00290 struct ao2_iterator it;
00291
00292 if (!(context = strchr(exten, '@'))) {
00293 ast_log(LOG_WARNING, "Someone used Local/%s somewhere without a @context. This is bad.\n", exten);
00294 return AST_DEVICE_INVALID;
00295 }
00296
00297 *context++ = '\0';
00298
00299
00300 if ((opts = strchr(context, '/')))
00301 *opts = '\0';
00302
00303 ast_debug(3, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
00304
00305 res = ast_exists_extension(NULL, context, exten, 1, NULL);
00306 if (!res)
00307 return AST_DEVICE_INVALID;
00308
00309 res = AST_DEVICE_NOT_INUSE;
00310
00311 it = ao2_iterator_init(locals, 0);
00312 for (; (lp = ao2_iterator_next(&it)); ao2_ref(lp, -1)) {
00313 int is_inuse;
00314
00315 ao2_lock(lp);
00316 is_inuse = !strcmp(exten, lp->exten)
00317 && !strcmp(context, lp->context)
00318 && lp->owner
00319 && ast_test_flag(lp, LOCAL_LAUNCHED_PBX);
00320 ao2_unlock(lp);
00321 if (is_inuse) {
00322 res = AST_DEVICE_INUSE;
00323 ao2_ref(lp, -1);
00324 break;
00325 }
00326 }
00327 ao2_iterator_destroy(&it);
00328
00329 return res;
00330 }
00331
00332
00333 static struct ast_channel *local_bridgedchannel(struct ast_channel *chan, struct ast_channel *bridge)
00334 {
00335 struct local_pvt *p = ast_channel_tech_pvt(bridge);
00336 struct ast_channel *bridged = bridge;
00337
00338 if (!p) {
00339 ast_debug(1, "Asked for bridged channel on '%s'/'%s', returning <none>\n",
00340 ast_channel_name(chan), ast_channel_name(bridge));
00341 return NULL;
00342 }
00343
00344 ao2_lock(p);
00345
00346 if (ast_test_flag(p, LOCAL_BRIDGE)) {
00347
00348 bridged = (bridge == p->owner ? p->chan : p->owner);
00349
00350
00351 if (!bridged) {
00352 bridged = bridge;
00353 } else if (ast_channel_internal_bridged_channel(bridged)) {
00354 bridged = ast_channel_internal_bridged_channel(bridged);
00355 }
00356 }
00357
00358 ao2_unlock(p);
00359
00360 return bridged;
00361 }
00362
00363
00364 static int local_queryoption(struct ast_channel *ast, int option, void *data, int *datalen)
00365 {
00366 struct local_pvt *p;
00367 struct ast_channel *bridged = NULL;
00368 struct ast_channel *tmp = NULL;
00369 int res = 0;
00370
00371 if (option != AST_OPTION_T38_STATE) {
00372
00373 return -1;
00374 }
00375
00376
00377 if (!(p = ast_channel_tech_pvt(ast))) {
00378 return -1;
00379 }
00380
00381 ao2_lock(p);
00382 if (!(tmp = IS_OUTBOUND(ast, p) ? p->owner : p->chan)) {
00383 ao2_unlock(p);
00384 return -1;
00385 }
00386 ast_channel_ref(tmp);
00387 ao2_unlock(p);
00388 ast_channel_unlock(ast);
00389
00390 ast_channel_lock(tmp);
00391 if (!(bridged = ast_bridged_channel(tmp))) {
00392 res = -1;
00393 ast_channel_unlock(tmp);
00394 goto query_cleanup;
00395 }
00396 ast_channel_ref(bridged);
00397 ast_channel_unlock(tmp);
00398
00399 query_cleanup:
00400 if (bridged) {
00401 res = ast_channel_queryoption(bridged, option, data, datalen, 0);
00402 bridged = ast_channel_unref(bridged);
00403 }
00404 if (tmp) {
00405 tmp = ast_channel_unref(tmp);
00406 }
00407 ast_channel_lock(ast);
00408
00409 return res;
00410 }
00411
00412
00413
00414
00415
00416
00417
00418
00419
00420 static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f,
00421 struct ast_channel *us, int us_locked)
00422 {
00423 struct ast_channel *other = NULL;
00424
00425
00426 other = isoutbound ? p->owner : p->chan;
00427
00428 if (!other) {
00429 return 0;
00430 }
00431
00432
00433 if (us
00434 && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)
00435 && ast_channel_generator(us)
00436 && ast_channel_generator(other)) {
00437 return 0;
00438 }
00439
00440
00441
00442 ast_channel_ref(other);
00443 if (us && us_locked) {
00444 ast_channel_unlock(us);
00445 }
00446 ao2_unlock(p);
00447
00448 if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_RINGING) {
00449 ast_setstate(other, AST_STATE_RINGING);
00450 }
00451 ast_queue_frame(other, f);
00452
00453 other = ast_channel_unref(other);
00454 if (us && us_locked) {
00455 ast_channel_lock(us);
00456 }
00457 ao2_lock(p);
00458
00459 return 0;
00460 }
00461
00462 static int local_answer(struct ast_channel *ast)
00463 {
00464 struct local_pvt *p = ast_channel_tech_pvt(ast);
00465 int isoutbound;
00466 int res = -1;
00467
00468 if (!p) {
00469 return -1;
00470 }
00471
00472 ao2_lock(p);
00473 ao2_ref(p, 1);
00474 isoutbound = IS_OUTBOUND(ast, p);
00475 if (isoutbound) {
00476
00477 struct ast_frame answer = { AST_FRAME_CONTROL, { AST_CONTROL_ANSWER } };
00478 res = local_queue_frame(p, isoutbound, &answer, ast, 1);
00479 } else {
00480 ast_log(LOG_WARNING, "Huh? Local is being asked to answer?\n");
00481 }
00482 ao2_unlock(p);
00483 ao2_ref(p, -1);
00484 return res;
00485 }
00486
00487
00488
00489
00490
00491
00492
00493 static void check_bridge(struct ast_channel *ast, struct local_pvt *p)
00494 {
00495 struct ast_channel *owner;
00496 struct ast_channel *chan;
00497 struct ast_channel *bridged_chan;
00498 struct ast_frame *f;
00499
00500
00501 if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
00502 || !p->chan || !p->owner) {
00503 return;
00504 }
00505
00506
00507 chan = ast_channel_ref(p->chan);
00508
00509 ao2_unlock(p);
00510 bridged_chan = ast_bridged_channel(chan);
00511 ao2_lock(p);
00512
00513 chan = ast_channel_unref(chan);
00514
00515
00516
00517
00518 if (ast_test_flag(p, LOCAL_NO_OPTIMIZATION | LOCAL_ALREADY_MASQED)
00519 || !p->chan || !p->owner
00520 || (ast_channel_internal_bridged_channel(p->chan) != bridged_chan)) {
00521 return;
00522 }
00523
00524
00525
00526
00527
00528
00529 if (!ast_channel_internal_bridged_channel(p->chan)
00530 || !AST_LIST_EMPTY(ast_channel_readq(p->owner))
00531 || ast != p->chan ) {
00532 return;
00533 }
00534
00535
00536
00537
00538
00539 if (ast_channel_trylock(ast_channel_internal_bridged_channel(p->chan))) {
00540 return;
00541 }
00542 if (ast_check_hangup(ast_channel_internal_bridged_channel(p->chan))
00543 || ast_channel_trylock(p->owner)) {
00544 ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
00545 return;
00546 }
00547
00548
00549
00550
00551
00552
00553
00554
00555 f = AST_LIST_FIRST(ast_channel_readq(p->chan));
00556 if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
00557 AST_LIST_REMOVE_HEAD(ast_channel_readq(p->chan), frame_list);
00558 ast_frfree(f);
00559 f = AST_LIST_FIRST(ast_channel_readq(p->chan));
00560 }
00561
00562 if (f
00563 || ast_check_hangup(p->owner)
00564 || ast_channel_masquerade(p->owner, ast_channel_internal_bridged_channel(p->chan))) {
00565 ast_channel_unlock(p->owner);
00566 ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
00567 return;
00568 }
00569
00570
00571 ast_debug(4, "Masquerading %s <- %s\n",
00572 ast_channel_name(p->owner),
00573 ast_channel_name(ast_channel_internal_bridged_channel(p->chan)));
00574 if (ast_channel_monitor(p->owner)
00575 && !ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan))) {
00576 struct ast_channel_monitor *tmp;
00577
00578
00579
00580
00581
00582
00583 tmp = ast_channel_monitor(p->owner);
00584 ast_channel_monitor_set(p->owner, ast_channel_monitor(ast_channel_internal_bridged_channel(p->chan)));
00585 ast_channel_monitor_set(ast_channel_internal_bridged_channel(p->chan), tmp);
00586 }
00587 if (ast_channel_audiohooks(p->chan)) {
00588 struct ast_audiohook_list *audiohooks_swapper;
00589
00590 audiohooks_swapper = ast_channel_audiohooks(p->chan);
00591 ast_channel_audiohooks_set(p->chan, ast_channel_audiohooks(p->owner));
00592 ast_channel_audiohooks_set(p->owner, audiohooks_swapper);
00593 }
00594
00595
00596
00597
00598
00599
00600
00601
00602 if (ast_channel_caller(p->owner)->id.name.valid || ast_channel_caller(p->owner)->id.number.valid
00603 || ast_channel_caller(p->owner)->id.subaddress.valid || ast_channel_caller(p->owner)->ani.name.valid
00604 || ast_channel_caller(p->owner)->ani.number.valid || ast_channel_caller(p->owner)->ani.subaddress.valid) {
00605 SWAP(*ast_channel_caller(p->owner), *ast_channel_caller(ast_channel_internal_bridged_channel(p->chan)));
00606 }
00607 if (ast_channel_redirecting(p->owner)->from.name.valid || ast_channel_redirecting(p->owner)->from.number.valid
00608 || ast_channel_redirecting(p->owner)->from.subaddress.valid || ast_channel_redirecting(p->owner)->to.name.valid
00609 || ast_channel_redirecting(p->owner)->to.number.valid || ast_channel_redirecting(p->owner)->to.subaddress.valid) {
00610 SWAP(*ast_channel_redirecting(p->owner), *ast_channel_redirecting(ast_channel_internal_bridged_channel(p->chan)));
00611 }
00612 if (ast_channel_dialed(p->owner)->number.str || ast_channel_dialed(p->owner)->subaddress.valid) {
00613 SWAP(*ast_channel_dialed(p->owner), *ast_channel_dialed(ast_channel_internal_bridged_channel(p->chan)));
00614 }
00615 ast_app_group_update(p->chan, p->owner);
00616 ast_set_flag(p, LOCAL_ALREADY_MASQED);
00617
00618 ast_channel_unlock(p->owner);
00619 ast_channel_unlock(ast_channel_internal_bridged_channel(p->chan));
00620
00621
00622 owner = ast_channel_ref(p->owner);
00623 ao2_unlock(p);
00624 ast_channel_unlock(ast);
00625 ast_do_masquerade(owner);
00626 ast_channel_unref(owner);
00627 ast_channel_lock(ast);
00628 ao2_lock(p);
00629 }
00630
00631 static struct ast_frame *local_read(struct ast_channel *ast)
00632 {
00633 return &ast_null_frame;
00634 }
00635
00636 static int local_write(struct ast_channel *ast, struct ast_frame *f)
00637 {
00638 struct local_pvt *p = ast_channel_tech_pvt(ast);
00639 int res = -1;
00640 int isoutbound;
00641
00642 if (!p) {
00643 return -1;
00644 }
00645
00646
00647 ao2_ref(p, 1);
00648 ao2_lock(p);
00649 isoutbound = IS_OUTBOUND(ast, p);
00650
00651 if (isoutbound
00652 && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO)) {
00653 check_bridge(ast, p);
00654 }
00655
00656 if (!ast_test_flag(p, LOCAL_ALREADY_MASQED)) {
00657 res = local_queue_frame(p, isoutbound, f, ast, 1);
00658 } else {
00659 ast_debug(1, "Not posting to '%s' queue since already masqueraded out\n",
00660 ast_channel_name(ast));
00661 res = 0;
00662 }
00663 ao2_unlock(p);
00664 ao2_ref(p, -1);
00665
00666 return res;
00667 }
00668
00669 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00670 {
00671 struct local_pvt *p = ast_channel_tech_pvt(newchan);
00672
00673 if (!p) {
00674 return -1;
00675 }
00676
00677 ao2_lock(p);
00678
00679 if ((p->owner != oldchan) && (p->chan != oldchan)) {
00680 ast_log(LOG_WARNING, "Old channel %p wasn't %p or %p\n", oldchan, p->owner, p->chan);
00681 ao2_unlock(p);
00682 return -1;
00683 }
00684 if (p->owner == oldchan) {
00685 p->owner = newchan;
00686 } else {
00687 p->chan = newchan;
00688 }
00689
00690
00691 if (!ast_check_hangup(newchan) && ((p->owner && ast_channel_internal_bridged_channel(p->owner) == p->chan) || (p->chan && ast_channel_internal_bridged_channel(p->chan) == p->owner))) {
00692 ast_log(LOG_WARNING, "You can not bridge a Local channel to itself!\n");
00693 ao2_unlock(p);
00694 ast_queue_hangup(newchan);
00695 return -1;
00696 }
00697
00698 ao2_unlock(p);
00699 return 0;
00700 }
00701
00702 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
00703 {
00704 struct local_pvt *p = ast_channel_tech_pvt(ast);
00705 int res = 0;
00706 struct ast_frame f = { AST_FRAME_CONTROL, };
00707 int isoutbound;
00708
00709 if (!p) {
00710 return -1;
00711 }
00712
00713 ao2_ref(p, 1);
00714
00715
00716 if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_HOLD) {
00717 ast_moh_start(ast, data, NULL);
00718 } else if (!ast_test_flag(p, LOCAL_MOH_PASSTHRU) && condition == AST_CONTROL_UNHOLD) {
00719 ast_moh_stop(ast);
00720 } else if (condition == AST_CONTROL_CONNECTED_LINE || condition == AST_CONTROL_REDIRECTING) {
00721 struct ast_channel *this_channel;
00722 struct ast_channel *the_other_channel;
00723
00724
00725
00726
00727
00728
00729
00730 ao2_lock(p);
00731 isoutbound = IS_OUTBOUND(ast, p);
00732 if (isoutbound) {
00733 this_channel = p->chan;
00734 the_other_channel = p->owner;
00735 } else {
00736 this_channel = p->owner;
00737 the_other_channel = p->chan;
00738 }
00739 if (the_other_channel) {
00740 unsigned char frame_data[1024];
00741 if (condition == AST_CONTROL_CONNECTED_LINE) {
00742 if (isoutbound) {
00743 ast_connected_line_copy_to_caller(ast_channel_caller(the_other_channel), ast_channel_connected(this_channel));
00744 }
00745 f.datalen = ast_connected_line_build_data(frame_data, sizeof(frame_data), ast_channel_connected(this_channel), NULL);
00746 } else {
00747 f.datalen = ast_redirecting_build_data(frame_data, sizeof(frame_data), ast_channel_redirecting(this_channel), NULL);
00748 }
00749 f.subclass.integer = condition;
00750 f.data.ptr = frame_data;
00751 res = local_queue_frame(p, isoutbound, &f, ast, 1);
00752 }
00753 ao2_unlock(p);
00754 } else {
00755
00756 ao2_lock(p);
00757
00758
00759
00760
00761
00762 if (0 <= condition || ast_test_flag(p, LOCAL_NO_OPTIMIZATION)) {
00763 isoutbound = IS_OUTBOUND(ast, p);
00764 f.subclass.integer = condition;
00765 f.data.ptr = (void *) data;
00766 f.datalen = datalen;
00767 res = local_queue_frame(p, isoutbound, &f, ast, 1);
00768
00769 if (!res && (condition == AST_CONTROL_T38_PARAMETERS) &&
00770 (datalen == sizeof(struct ast_control_t38_parameters))) {
00771 const struct ast_control_t38_parameters *parameters = data;
00772
00773 if (parameters->request_response == AST_T38_REQUEST_PARMS) {
00774 res = AST_T38_REQUEST_PARMS;
00775 }
00776 }
00777 } else {
00778 ast_debug(4, "Blocked indication %d\n", condition);
00779 }
00780 ao2_unlock(p);
00781 }
00782
00783 ao2_ref(p, -1);
00784 return res;
00785 }
00786
00787 static int local_digit_begin(struct ast_channel *ast, char digit)
00788 {
00789 struct local_pvt *p = ast_channel_tech_pvt(ast);
00790 int res = -1;
00791 struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
00792 int isoutbound;
00793
00794 if (!p) {
00795 return -1;
00796 }
00797
00798 ao2_ref(p, 1);
00799 ao2_lock(p);
00800 isoutbound = IS_OUTBOUND(ast, p);
00801 f.subclass.integer = digit;
00802 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00803 ao2_unlock(p);
00804 ao2_ref(p, -1);
00805
00806 return res;
00807 }
00808
00809 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
00810 {
00811 struct local_pvt *p = ast_channel_tech_pvt(ast);
00812 int res = -1;
00813 struct ast_frame f = { AST_FRAME_DTMF_END, };
00814 int isoutbound;
00815
00816 if (!p) {
00817 return -1;
00818 }
00819
00820 ao2_ref(p, 1);
00821 ao2_lock(p);
00822 isoutbound = IS_OUTBOUND(ast, p);
00823 f.subclass.integer = digit;
00824 f.len = duration;
00825 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00826 ao2_unlock(p);
00827 ao2_ref(p, -1);
00828
00829 return res;
00830 }
00831
00832 static int local_sendtext(struct ast_channel *ast, const char *text)
00833 {
00834 struct local_pvt *p = ast_channel_tech_pvt(ast);
00835 int res = -1;
00836 struct ast_frame f = { AST_FRAME_TEXT, };
00837 int isoutbound;
00838
00839 if (!p) {
00840 return -1;
00841 }
00842
00843 ao2_lock(p);
00844 ao2_ref(p, 1);
00845 isoutbound = IS_OUTBOUND(ast, p);
00846 f.data.ptr = (char *) text;
00847 f.datalen = strlen(text) + 1;
00848 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00849 ao2_unlock(p);
00850 ao2_ref(p, -1);
00851 return res;
00852 }
00853
00854 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
00855 {
00856 struct local_pvt *p = ast_channel_tech_pvt(ast);
00857 int res = -1;
00858 struct ast_frame f = { AST_FRAME_HTML, };
00859 int isoutbound;
00860
00861 if (!p) {
00862 return -1;
00863 }
00864
00865 ao2_lock(p);
00866 ao2_ref(p, 1);
00867 isoutbound = IS_OUTBOUND(ast, p);
00868 f.subclass.integer = subclass;
00869 f.data.ptr = (char *)data;
00870 f.datalen = datalen;
00871 res = local_queue_frame(p, isoutbound, &f, ast, 0);
00872 ao2_unlock(p);
00873 ao2_ref(p, -1);
00874
00875 return res;
00876 }
00877
00878
00879
00880 static int local_call(struct ast_channel *ast, const char *dest, int timeout)
00881 {
00882 struct local_pvt *p = ast_channel_tech_pvt(ast);
00883 int pvt_locked = 0;
00884
00885 struct ast_channel *owner = NULL;
00886 struct ast_channel *chan = NULL;
00887 int res;
00888 struct ast_var_t *varptr;
00889 struct ast_var_t *clone_var;
00890 char *reduced_dest = ast_strdupa(dest);
00891 char *slash;
00892 const char *exten;
00893 const char *context;
00894
00895 if (!p) {
00896 return -1;
00897 }
00898
00899
00900
00901 ao2_ref(p, 1);
00902 ast_channel_unlock(ast);
00903
00904 awesome_locking(p, &chan, &owner);
00905 pvt_locked = 1;
00906
00907 if (owner != ast) {
00908 res = -1;
00909 goto return_cleanup;
00910 }
00911
00912 if (!owner || !chan) {
00913 res = -1;
00914 goto return_cleanup;
00915 }
00916
00917
00918
00919
00920
00921
00922
00923
00924 ast_party_redirecting_copy(ast_channel_redirecting(chan), ast_channel_redirecting(owner));
00925
00926 ast_party_dialed_copy(ast_channel_dialed(chan), ast_channel_dialed(owner));
00927
00928 ast_connected_line_copy_to_caller(ast_channel_caller(chan), ast_channel_connected(owner));
00929 ast_connected_line_copy_from_caller(ast_channel_connected(chan), ast_channel_caller(owner));
00930
00931 ast_channel_language_set(chan, ast_channel_language(owner));
00932 ast_channel_accountcode_set(chan, ast_channel_accountcode(owner));
00933 ast_channel_musicclass_set(chan, ast_channel_musicclass(owner));
00934 ast_cdr_update(chan);
00935
00936 ast_channel_cc_params_init(chan, ast_channel_get_cc_config_params(owner));
00937
00938
00939 if (ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
00940 ast_channel_hangupcause_set(chan, AST_CAUSE_ANSWERED_ELSEWHERE);
00941 }
00942
00943
00944
00945 AST_LIST_TRAVERSE(ast_channel_varshead(owner), varptr, entries) {
00946 clone_var = ast_var_assign(varptr->name, varptr->value);
00947 if (clone_var) {
00948 AST_LIST_INSERT_TAIL(ast_channel_varshead(chan), clone_var, entries);
00949 }
00950 }
00951 ast_channel_datastore_inherit(owner, chan);
00952
00953
00954
00955
00956 if ((slash = strrchr(reduced_dest, '/'))) {
00957 *slash = '\0';
00958 }
00959 ast_set_cc_interfaces_chanvar(chan, reduced_dest);
00960
00961 exten = ast_strdupa(ast_channel_exten(chan));
00962 context = ast_strdupa(ast_channel_context(chan));
00963
00964 ao2_unlock(p);
00965 pvt_locked = 0;
00966
00967 ast_channel_unlock(chan);
00968
00969 if (!ast_exists_extension(chan, context, exten, 1,
00970 S_COR(ast_channel_caller(owner)->id.number.valid, ast_channel_caller(owner)->id.number.str, NULL))) {
00971 ast_log(LOG_NOTICE, "No such extension/context %s@%s while calling Local channel\n", exten, context);
00972 res = -1;
00973 chan = ast_channel_unref(chan);
00974 goto return_cleanup;
00975 }
00976
00977
00978
00979
00980
00981
00982
00983
00984
00985
00986
00987
00988
00989
00990
00991
00992
00993
00994
00995
00996
00997
00998
00999
01000
01001
01002 manager_event(EVENT_FLAG_CALL, "LocalBridge",
01003 "Channel1: %s\r\n"
01004 "Channel2: %s\r\n"
01005 "Uniqueid1: %s\r\n"
01006 "Uniqueid2: %s\r\n"
01007 "Context: %s\r\n"
01008 "Exten: %s\r\n"
01009 "LocalOptimization: %s\r\n",
01010 ast_channel_name(p->owner), ast_channel_name(p->chan), ast_channel_uniqueid(p->owner), ast_channel_uniqueid(p->chan),
01011 p->context, p->exten,
01012 ast_test_flag(p, LOCAL_NO_OPTIMIZATION) ? "No" : "Yes");
01013
01014
01015
01016 if (!(res = ast_pbx_start(chan))) {
01017 ao2_lock(p);
01018 ast_set_flag(p, LOCAL_LAUNCHED_PBX);
01019 ao2_unlock(p);
01020 }
01021 chan = ast_channel_unref(chan);
01022
01023 return_cleanup:
01024 if (p) {
01025 if (pvt_locked) {
01026 ao2_unlock(p);
01027 }
01028 ao2_ref(p, -1);
01029 }
01030 if (chan) {
01031 ast_channel_unlock(chan);
01032 chan = ast_channel_unref(chan);
01033 }
01034
01035
01036
01037 if (owner) {
01038 if (owner != ast) {
01039 ast_channel_unlock(owner);
01040 ast_channel_lock(ast);
01041 }
01042 owner = ast_channel_unref(owner);
01043 } else {
01044
01045 ast_channel_lock(ast);
01046 }
01047
01048 return res;
01049 }
01050
01051
01052 static int local_hangup(struct ast_channel *ast)
01053 {
01054 struct local_pvt *p = ast_channel_tech_pvt(ast);
01055 int isoutbound;
01056 int hangup_chan = 0;
01057 int res = 0;
01058 struct ast_frame f = { AST_FRAME_CONTROL, { AST_CONTROL_HANGUP }, .data.uint32 = ast_channel_hangupcause(ast) };
01059 struct ast_channel *owner = NULL;
01060 struct ast_channel *chan = NULL;
01061
01062 if (!p) {
01063 return -1;
01064 }
01065
01066
01067 ao2_ref(p, 1);
01068
01069
01070 ast_channel_unlock(ast);
01071
01072
01073 awesome_locking(p, &chan, &owner);
01074
01075 if (ast != chan && ast != owner) {
01076 res = -1;
01077 goto local_hangup_cleanup;
01078 }
01079
01080 isoutbound = IS_OUTBOUND(ast, p);
01081
01082 if (p->chan && ast_channel_hangupcause(ast) == AST_CAUSE_ANSWERED_ELSEWHERE) {
01083 ast_channel_hangupcause_set(p->chan, AST_CAUSE_ANSWERED_ELSEWHERE);
01084 ast_debug(2, "This local call has AST_CAUSE_ANSWERED_ELSEWHERE set.\n");
01085 }
01086
01087 if (isoutbound) {
01088 const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
01089
01090 if (status && p->owner) {
01091 ast_channel_hangupcause_set(p->owner, ast_channel_hangupcause(p->chan));
01092 pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
01093 }
01094
01095 ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
01096 p->chan = NULL;
01097 } else {
01098 if (p->chan) {
01099 ast_queue_hangup(p->chan);
01100 }
01101 p->owner = NULL;
01102 }
01103
01104 ast_channel_tech_pvt_set(ast, NULL);
01105
01106 if (!p->owner && !p->chan) {
01107 ao2_unlock(p);
01108
01109
01110 ao2_unlink(locals, p);
01111 ao2_ref(p, -1);
01112 p = NULL;
01113 res = 0;
01114 goto local_hangup_cleanup;
01115 }
01116 if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX)) {
01117
01118 hangup_chan = 1;
01119 } else {
01120 local_queue_frame(p, isoutbound, &f, NULL, 0);
01121 }
01122
01123 local_hangup_cleanup:
01124 if (p) {
01125 ao2_unlock(p);
01126 ao2_ref(p, -1);
01127 }
01128 if (owner) {
01129 ast_channel_unlock(owner);
01130 owner = ast_channel_unref(owner);
01131 }
01132 if (chan) {
01133 ast_channel_unlock(chan);
01134 if (hangup_chan) {
01135 ast_hangup(chan);
01136 }
01137 chan = ast_channel_unref(chan);
01138 }
01139
01140
01141 ast_channel_lock(ast);
01142 return res;
01143 }
01144
01145
01146
01147
01148
01149
01150
01151
01152
01153 static void local_pvt_destructor(void *vdoomed)
01154 {
01155 struct local_pvt *doomed = vdoomed;
01156
01157 doomed->reqcap = ast_format_cap_destroy(doomed->reqcap);
01158
01159 ast_module_unref(ast_module_info->self);
01160 }
01161
01162
01163 static struct local_pvt *local_alloc(const char *data, struct ast_format_cap *cap)
01164 {
01165 struct local_pvt *tmp = NULL;
01166 char *c = NULL, *opts = NULL;
01167
01168 if (!(tmp = ao2_alloc(sizeof(*tmp), local_pvt_destructor))) {
01169 return NULL;
01170 }
01171 if (!(tmp->reqcap = ast_format_cap_dup(cap))) {
01172 ao2_ref(tmp, -1);
01173 return NULL;
01174 }
01175
01176 ast_module_ref(ast_module_info->self);
01177
01178
01179 ast_copy_string(tmp->exten, data, sizeof(tmp->exten));
01180
01181 memcpy(&tmp->jb_conf, &g_jb_conf, sizeof(tmp->jb_conf));
01182
01183
01184 if ((opts = strchr(tmp->exten, '/'))) {
01185 *opts++ = '\0';
01186 if (strchr(opts, 'n'))
01187 ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
01188 if (strchr(opts, 'j')) {
01189 if (ast_test_flag(tmp, LOCAL_NO_OPTIMIZATION))
01190 ast_set_flag(&tmp->jb_conf, AST_JB_ENABLED);
01191 else {
01192 ast_log(LOG_ERROR, "You must use the 'n' option for chan_local "
01193 "to use the 'j' option to enable the jitterbuffer\n");
01194 }
01195 }
01196 if (strchr(opts, 'b')) {
01197 ast_set_flag(tmp, LOCAL_BRIDGE);
01198 }
01199 if (strchr(opts, 'm')) {
01200 ast_set_flag(tmp, LOCAL_MOH_PASSTHRU);
01201 }
01202 }
01203
01204
01205 if ((c = strchr(tmp->exten, '@')))
01206 *c++ = '\0';
01207
01208 ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
01209 #if 0
01210
01211
01212
01213 if (!ast_exists_extension(NULL, tmp->context, tmp->exten, 1, NULL)) {
01214 ast_log(LOG_NOTICE, "No such extension/context %s@%s creating local channel\n", tmp->exten, tmp->context);
01215 tmp = local_pvt_destroy(tmp);
01216 } else {
01217 #endif
01218
01219 ao2_link(locals, tmp);
01220 #if 0
01221 }
01222 #endif
01223 return tmp;
01224 }
01225
01226
01227 static struct ast_channel *local_new(struct local_pvt *p, int state, const char *linkedid, struct ast_callid *callid)
01228 {
01229 struct ast_channel *tmp = NULL, *tmp2 = NULL;
01230 struct ast_format fmt;
01231 int generated_seqno = ast_atomic_fetchadd_int((int *)&name_sequence, +1);
01232 const char *t;
01233 int ama;
01234
01235
01236
01237 if (p->owner && ast_channel_accountcode(p->owner))
01238 t = ast_channel_accountcode(p->owner);
01239 else
01240 t = "";
01241
01242 if (p->owner)
01243 ama = ast_channel_amaflags(p->owner);
01244 else
01245 ama = 0;
01246
01247
01248
01249 if (!(tmp = ast_channel_alloc(1, state, 0, 0, t, p->exten, p->context, linkedid, ama, "Local/%s@%s-%08x;1", p->exten, p->context, (unsigned)generated_seqno))
01250 || !(tmp2 = ast_channel_alloc(1, AST_STATE_RING, 0, 0, t, p->exten, p->context, ast_channel_linkedid(tmp), ama, "Local/%s@%s-%08x;2", p->exten, p->context, (unsigned)generated_seqno))) {
01251 if (tmp) {
01252 tmp = ast_channel_release(tmp);
01253 }
01254 ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
01255 return NULL;
01256 }
01257
01258 if (callid) {
01259 ast_channel_callid_set(tmp, callid);
01260 ast_channel_callid_set(tmp2, callid);
01261 }
01262
01263 ast_channel_tech_set(tmp, &local_tech);
01264 ast_channel_tech_set(tmp2, &local_tech);
01265
01266 ast_format_cap_copy(ast_channel_nativeformats(tmp), p->reqcap);
01267 ast_format_cap_copy(ast_channel_nativeformats(tmp2), p->reqcap);
01268
01269
01270 ast_best_codec(p->reqcap, &fmt);
01271 ast_format_copy(ast_channel_writeformat(tmp), &fmt);
01272 ast_format_copy(ast_channel_writeformat(tmp2), &fmt);
01273 ast_format_copy(ast_channel_rawwriteformat(tmp), &fmt);
01274 ast_format_copy(ast_channel_rawwriteformat(tmp2), &fmt);
01275 ast_format_copy(ast_channel_readformat(tmp), &fmt);
01276 ast_format_copy(ast_channel_readformat(tmp2), &fmt);
01277 ast_format_copy(ast_channel_rawreadformat(tmp), &fmt);
01278 ast_format_copy(ast_channel_rawreadformat(tmp2), &fmt);
01279
01280 ast_channel_tech_pvt_set(tmp, p);
01281 ast_channel_tech_pvt_set(tmp2, p);
01282
01283 ast_set_flag(ast_channel_flags(tmp), AST_FLAG_DISABLE_DEVSTATE_CACHE);
01284 ast_set_flag(ast_channel_flags(tmp2), AST_FLAG_DISABLE_DEVSTATE_CACHE);
01285
01286 p->owner = tmp;
01287 p->chan = tmp2;
01288
01289 ast_channel_context_set(tmp, p->context);
01290 ast_channel_context_set(tmp2, p->context);
01291 ast_channel_exten_set(tmp2, p->exten);
01292 ast_channel_priority_set(tmp, 1);
01293 ast_channel_priority_set(tmp2, 1);
01294
01295 ast_jb_configure(tmp, &p->jb_conf);
01296
01297 return tmp;
01298 }
01299
01300
01301 static struct ast_channel *local_request(const char *type, struct ast_format_cap *cap, const struct ast_channel *requestor, const char *data, int *cause)
01302 {
01303 struct local_pvt *p;
01304 struct ast_channel *chan;
01305 struct ast_callid *callid = ast_read_threadstorage_callid();
01306
01307
01308 p = local_alloc(data, cap);
01309 if (!p) {
01310 chan = NULL;
01311 goto local_request_end;
01312 }
01313 chan = local_new(p, AST_STATE_DOWN, requestor ? ast_channel_linkedid(requestor) : NULL, callid);
01314 if (!chan) {
01315 ao2_unlink(locals, p);
01316 } else if (ast_channel_cc_params_init(chan, requestor ? ast_channel_get_cc_config_params((struct ast_channel *)requestor) : NULL)) {
01317 ao2_unlink(locals, p);
01318 p->owner = ast_channel_release(p->owner);
01319 p->chan = ast_channel_release(p->chan);
01320 chan = NULL;
01321 }
01322 ao2_ref(p, -1);
01323
01324 local_request_end:
01325
01326 if (callid) {
01327 ast_callid_unref(callid);
01328 }
01329
01330 return chan;
01331 }
01332
01333
01334 static char *locals_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
01335 {
01336 struct local_pvt *p = NULL;
01337 struct ao2_iterator it;
01338
01339 switch (cmd) {
01340 case CLI_INIT:
01341 e->command = "local show channels";
01342 e->usage =
01343 "Usage: local show channels\n"
01344 " Provides summary information on active local proxy channels.\n";
01345 return NULL;
01346 case CLI_GENERATE:
01347 return NULL;
01348 }
01349
01350 if (a->argc != 3) {
01351 return CLI_SHOWUSAGE;
01352 }
01353
01354 if (ao2_container_count(locals) == 0) {
01355 ast_cli(a->fd, "No local channels in use\n");
01356 return RESULT_SUCCESS;
01357 }
01358
01359 it = ao2_iterator_init(locals, 0);
01360 while ((p = ao2_iterator_next(&it))) {
01361 ao2_lock(p);
01362 ast_cli(a->fd, "%s -- %s@%s\n", p->owner ? ast_channel_name(p->owner) : "<unowned>", p->exten, p->context);
01363 ao2_unlock(p);
01364 ao2_ref(p, -1);
01365 }
01366 ao2_iterator_destroy(&it);
01367
01368 return CLI_SUCCESS;
01369 }
01370
01371 static struct ast_cli_entry cli_local[] = {
01372 AST_CLI_DEFINE(locals_show, "List status of local channels"),
01373 };
01374
01375 static int manager_optimize_away(struct mansession *s, const struct message *m)
01376 {
01377 const char *channel;
01378 struct local_pvt *p, *tmp = NULL;
01379 struct ast_channel *c;
01380 int found = 0;
01381 struct ao2_iterator it;
01382
01383 channel = astman_get_header(m, "Channel");
01384
01385 if (ast_strlen_zero(channel)) {
01386 astman_send_error(s, m, "'Channel' not specified.");
01387 return 0;
01388 }
01389
01390 c = ast_channel_get_by_name(channel);
01391 if (!c) {
01392 astman_send_error(s, m, "Channel does not exist.");
01393 return 0;
01394 }
01395
01396 p = ast_channel_tech_pvt(c);
01397 ast_channel_unref(c);
01398 c = NULL;
01399
01400 it = ao2_iterator_init(locals, 0);
01401 while ((tmp = ao2_iterator_next(&it))) {
01402 if (tmp == p) {
01403 ao2_lock(tmp);
01404 found = 1;
01405 ast_clear_flag(tmp, LOCAL_NO_OPTIMIZATION);
01406 ao2_unlock(tmp);
01407 ao2_ref(tmp, -1);
01408 break;
01409 }
01410 ao2_ref(tmp, -1);
01411 }
01412 ao2_iterator_destroy(&it);
01413
01414 if (found) {
01415 astman_send_ack(s, m, "Queued channel to be optimized away");
01416 } else {
01417 astman_send_error(s, m, "Unable to find channel");
01418 }
01419
01420 return 0;
01421 }
01422
01423
01424 static int locals_cmp_cb(void *obj, void *arg, int flags)
01425 {
01426 return (obj == arg) ? CMP_MATCH : 0;
01427 }
01428
01429
01430 static int load_module(void)
01431 {
01432 if (!(local_tech.capabilities = ast_format_cap_alloc())) {
01433 return AST_MODULE_LOAD_FAILURE;
01434 }
01435 ast_format_cap_add_all(local_tech.capabilities);
01436
01437 if (!(locals = ao2_container_alloc(BUCKET_SIZE, NULL, locals_cmp_cb))) {
01438 ast_format_cap_destroy(local_tech.capabilities);
01439 return AST_MODULE_LOAD_FAILURE;
01440 }
01441
01442
01443 if (ast_channel_register(&local_tech)) {
01444 ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
01445 ao2_ref(locals, -1);
01446 ast_format_cap_destroy(local_tech.capabilities);
01447 return AST_MODULE_LOAD_FAILURE;
01448 }
01449 ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
01450 ast_manager_register_xml("LocalOptimizeAway", EVENT_FLAG_SYSTEM|EVENT_FLAG_CALL, manager_optimize_away);
01451
01452 return AST_MODULE_LOAD_SUCCESS;
01453 }
01454
01455
01456 static int unload_module(void)
01457 {
01458 struct local_pvt *p = NULL;
01459 struct ao2_iterator it;
01460
01461
01462 ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
01463 ast_manager_unregister("LocalOptimizeAway");
01464 ast_channel_unregister(&local_tech);
01465
01466 it = ao2_iterator_init(locals, 0);
01467 while ((p = ao2_iterator_next(&it))) {
01468 if (p->owner) {
01469 ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
01470 }
01471 ao2_ref(p, -1);
01472 }
01473 ao2_iterator_destroy(&it);
01474 ao2_ref(locals, -1);
01475
01476 ast_format_cap_destroy(local_tech.capabilities);
01477 return 0;
01478 }
01479
01480 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_LOAD_ORDER, "Local Proxy Channel (Note: used internally by other modules)",
01481 .load = load_module,
01482 .unload = unload_module,
01483 .load_pri = AST_MODPRI_CHANNEL_DRIVER,
01484 );