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 #include "asterisk.h"
00029
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
00031
00032 #include <stdio.h>
00033 #include <string.h>
00034 #include <unistd.h>
00035 #include <sys/socket.h>
00036 #include <errno.h>
00037 #include <stdlib.h>
00038 #include <fcntl.h>
00039 #include <netdb.h>
00040 #include <netinet/in.h>
00041 #include <arpa/inet.h>
00042 #include <sys/signal.h>
00043
00044 #include "asterisk/lock.h"
00045 #include "asterisk/channel.h"
00046 #include "asterisk/config.h"
00047 #include "asterisk/logger.h"
00048 #include "asterisk/module.h"
00049 #include "asterisk/pbx.h"
00050 #include "asterisk/options.h"
00051 #include "asterisk/lock.h"
00052 #include "asterisk/sched.h"
00053 #include "asterisk/io.h"
00054 #include "asterisk/rtp.h"
00055 #include "asterisk/acl.h"
00056 #include "asterisk/callerid.h"
00057 #include "asterisk/file.h"
00058 #include "asterisk/cli.h"
00059 #include "asterisk/app.h"
00060 #include "asterisk/musiconhold.h"
00061 #include "asterisk/manager.h"
00062 #include "asterisk/stringfields.h"
00063 #include "asterisk/devicestate.h"
00064
00065 static const char tdesc[] = "Local Proxy Channel Driver";
00066
00067 #define IS_OUTBOUND(a,b) (a == b->chan ? 1 : 0)
00068
00069 static struct ast_channel *local_request(const char *type, int format, void *data, int *cause);
00070 static int local_digit_begin(struct ast_channel *ast, char digit);
00071 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
00072 static int local_call(struct ast_channel *ast, char *dest, int timeout);
00073 static int local_hangup(struct ast_channel *ast);
00074 static int local_answer(struct ast_channel *ast);
00075 static struct ast_frame *local_read(struct ast_channel *ast);
00076 static int local_write(struct ast_channel *ast, struct ast_frame *f);
00077 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
00078 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
00079 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen);
00080 static int local_sendtext(struct ast_channel *ast, const char *text);
00081 static int local_devicestate(void *data);
00082
00083
00084 static const struct ast_channel_tech local_tech = {
00085 .type = "Local",
00086 .description = tdesc,
00087 .capabilities = -1,
00088 .requester = local_request,
00089 .send_digit_begin = local_digit_begin,
00090 .send_digit_end = local_digit_end,
00091 .call = local_call,
00092 .hangup = local_hangup,
00093 .answer = local_answer,
00094 .read = local_read,
00095 .write = local_write,
00096 .write_video = local_write,
00097 .exception = local_read,
00098 .indicate = local_indicate,
00099 .fixup = local_fixup,
00100 .send_html = local_sendhtml,
00101 .send_text = local_sendtext,
00102 .devicestate = local_devicestate,
00103 };
00104
00105 struct local_pvt {
00106 ast_mutex_t lock;
00107 unsigned int flags;
00108 char context[AST_MAX_CONTEXT];
00109 char exten[AST_MAX_EXTENSION];
00110 int reqformat;
00111 struct ast_channel *owner;
00112 struct ast_channel *chan;
00113 struct ast_module_user *u_owner;
00114 struct ast_module_user *u_chan;
00115 AST_LIST_ENTRY(local_pvt) list;
00116 };
00117
00118 #define LOCAL_GLARE_DETECT (1 << 0)
00119 #define LOCAL_CANCEL_QUEUE (1 << 1)
00120 #define LOCAL_ALREADY_MASQED (1 << 2)
00121 #define LOCAL_LAUNCHED_PBX (1 << 3)
00122 #define LOCAL_NO_OPTIMIZATION (1 << 4)
00123
00124 static AST_LIST_HEAD_STATIC(locals, local_pvt);
00125
00126
00127 static int local_devicestate(void *data)
00128 {
00129 char *exten = ast_strdupa(data);
00130 char *context = NULL, *opts = NULL;
00131 int res;
00132
00133 if (!(context = strchr(exten, '@'))) {
00134 ast_log(LOG_WARNING, "Someone used Local/%s somewhere without a @context. This is bad.\n", exten);
00135 return AST_DEVICE_INVALID;
00136 }
00137
00138 *context++ = '\0';
00139
00140
00141 if ((opts = strchr(context, '/')))
00142 *opts = '\0';
00143
00144 if (option_debug > 2)
00145 ast_log(LOG_DEBUG, "Checking if extension %s@%s exists (devicestate)\n", exten, context);
00146 res = ast_exists_extension(NULL, context, exten, 1, NULL);
00147 if (!res)
00148 return AST_DEVICE_INVALID;
00149 else
00150 return AST_DEVICE_UNKNOWN;
00151 }
00152
00153 static int local_queue_frame(struct local_pvt *p, int isoutbound, struct ast_frame *f, struct ast_channel *us)
00154 {
00155 struct ast_channel *other = NULL;
00156
00157 retrylock:
00158
00159
00160 other = isoutbound ? p->owner : p->chan;
00161
00162
00163 ast_set_flag(p, LOCAL_GLARE_DETECT);
00164 if (ast_test_flag(p, LOCAL_CANCEL_QUEUE)) {
00165
00166
00167 ast_mutex_unlock(&p->lock);
00168 ast_mutex_destroy(&p->lock);
00169 free(p);
00170 return -1;
00171 }
00172 if (!other) {
00173 ast_clear_flag(p, LOCAL_GLARE_DETECT);
00174 return 0;
00175 }
00176 if (ast_mutex_trylock(&other->lock)) {
00177
00178 ast_mutex_unlock(&p->lock);
00179 if (us) {
00180 if (ast_mutex_unlock(&us->lock)) {
00181 ast_log(LOG_WARNING, "%s wasn't locked while sending %d/%d\n",
00182 us->name, f->frametype, f->subclass);
00183 us = NULL;
00184 }
00185 }
00186
00187 usleep(1);
00188
00189 if (us)
00190 ast_mutex_lock(&us->lock);
00191 ast_mutex_lock(&p->lock);
00192 goto retrylock;
00193 }
00194 ast_queue_frame(other, f);
00195 ast_mutex_unlock(&other->lock);
00196 ast_clear_flag(p, LOCAL_GLARE_DETECT);
00197 return 0;
00198 }
00199
00200 static int local_answer(struct ast_channel *ast)
00201 {
00202 struct local_pvt *p = ast->tech_pvt;
00203 int isoutbound;
00204 int res = -1;
00205
00206 if (!p)
00207 return -1;
00208
00209 ast_mutex_lock(&p->lock);
00210 isoutbound = IS_OUTBOUND(ast, p);
00211 if (isoutbound) {
00212
00213 struct ast_frame answer = { AST_FRAME_CONTROL, AST_CONTROL_ANSWER };
00214 res = local_queue_frame(p, isoutbound, &answer, ast);
00215 } else
00216 ast_log(LOG_WARNING, "Huh? Local is being asked to answer?\n");
00217 if (!res)
00218 ast_mutex_unlock(&p->lock);
00219 return res;
00220 }
00221
00222 static void check_bridge(struct local_pvt *p, int isoutbound)
00223 {
00224 if (ast_test_flag(p, LOCAL_ALREADY_MASQED) || ast_test_flag(p, LOCAL_NO_OPTIMIZATION) || !p->chan || !p->owner || (p->chan->_bridge != ast_bridged_channel(p->chan)))
00225 return;
00226
00227
00228
00229
00230
00231
00232 if (isoutbound && p->chan->_bridge && AST_LIST_EMPTY(&p->owner->readq)) {
00233
00234
00235
00236
00237 if (!ast_mutex_trylock(&(p->chan->_bridge)->lock)) {
00238 if (!p->chan->_bridge->_softhangup) {
00239 if (!ast_mutex_trylock(&p->owner->lock)) {
00240 if (!p->owner->_softhangup) {
00241 ast_channel_masquerade(p->owner, p->chan->_bridge);
00242 ast_set_flag(p, LOCAL_ALREADY_MASQED);
00243 }
00244 ast_mutex_unlock(&p->owner->lock);
00245 }
00246 ast_mutex_unlock(&(p->chan->_bridge)->lock);
00247 }
00248 }
00249
00250
00251
00252
00253 #if 0
00254 } else if (!isoutbound && p->owner && p->owner->_bridge && p->chan && AST_LIST_EMPTY(&p->chan->readq)) {
00255
00256 if (!ast_mutex_trylock(&(p->owner->_bridge)->lock)) {
00257 if (!p->owner->_bridge->_softhangup) {
00258 if (!ast_mutex_trylock(&p->chan->lock)) {
00259 if (!p->chan->_softhangup) {
00260 ast_channel_masquerade(p->chan, p->owner->_bridge);
00261 ast_set_flag(p, LOCAL_ALREADY_MASQED);
00262 }
00263 ast_mutex_unlock(&p->chan->lock);
00264 }
00265 }
00266 ast_mutex_unlock(&(p->owner->_bridge)->lock);
00267 }
00268 #endif
00269 }
00270 }
00271
00272 static struct ast_frame *local_read(struct ast_channel *ast)
00273 {
00274 return &ast_null_frame;
00275 }
00276
00277 static int local_write(struct ast_channel *ast, struct ast_frame *f)
00278 {
00279 struct local_pvt *p = ast->tech_pvt;
00280 int res = -1;
00281 int isoutbound;
00282
00283 if (!p)
00284 return -1;
00285
00286
00287 ast_mutex_lock(&p->lock);
00288 isoutbound = IS_OUTBOUND(ast, p);
00289 if (f && (f->frametype == AST_FRAME_VOICE || f->frametype == AST_FRAME_VIDEO))
00290 check_bridge(p, isoutbound);
00291 if (!ast_test_flag(p, LOCAL_ALREADY_MASQED))
00292 res = local_queue_frame(p, isoutbound, f, ast);
00293 else {
00294 if (option_debug)
00295 ast_log(LOG_DEBUG, "Not posting to queue since already masked on '%s'\n", ast->name);
00296 res = 0;
00297 }
00298 if (!res)
00299 ast_mutex_unlock(&p->lock);
00300 return res;
00301 }
00302
00303 static int local_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
00304 {
00305 struct local_pvt *p = newchan->tech_pvt;
00306
00307 if (!p)
00308 return -1;
00309
00310 ast_mutex_lock(&p->lock);
00311
00312 if ((p->owner != oldchan) && (p->chan != oldchan)) {
00313 ast_log(LOG_WARNING, "Old channel wasn't %p but was %p/%p\n", oldchan, p->owner, p->chan);
00314 ast_mutex_unlock(&p->lock);
00315 return -1;
00316 }
00317 if (p->owner == oldchan)
00318 p->owner = newchan;
00319 else
00320 p->chan = newchan;
00321 ast_mutex_unlock(&p->lock);
00322 return 0;
00323 }
00324
00325 static int local_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
00326 {
00327 struct local_pvt *p = ast->tech_pvt;
00328 int res = 0;
00329 struct ast_frame f = { AST_FRAME_CONTROL, };
00330 int isoutbound;
00331
00332 if (!p)
00333 return -1;
00334
00335
00336 if (condition == AST_CONTROL_HOLD) {
00337 ast_moh_start(ast, data, NULL);
00338 } else if (condition == AST_CONTROL_UNHOLD) {
00339 ast_moh_stop(ast);
00340 } else {
00341
00342 ast_mutex_lock(&p->lock);
00343 isoutbound = IS_OUTBOUND(ast, p);
00344 f.subclass = condition;
00345 f.data = (void*)data;
00346 f.datalen = datalen;
00347 if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
00348 ast_mutex_unlock(&p->lock);
00349 }
00350
00351 return res;
00352 }
00353
00354 static int local_digit_begin(struct ast_channel *ast, char digit)
00355 {
00356 struct local_pvt *p = ast->tech_pvt;
00357 int res = -1;
00358 struct ast_frame f = { AST_FRAME_DTMF_BEGIN, };
00359 int isoutbound;
00360
00361 if (!p)
00362 return -1;
00363
00364 ast_mutex_lock(&p->lock);
00365 isoutbound = IS_OUTBOUND(ast, p);
00366 f.subclass = digit;
00367 if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
00368 ast_mutex_unlock(&p->lock);
00369
00370 return res;
00371 }
00372
00373 static int local_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
00374 {
00375 struct local_pvt *p = ast->tech_pvt;
00376 int res = -1;
00377 struct ast_frame f = { AST_FRAME_DTMF_END, };
00378 int isoutbound;
00379
00380 if (!p)
00381 return -1;
00382
00383 ast_mutex_lock(&p->lock);
00384 isoutbound = IS_OUTBOUND(ast, p);
00385 f.subclass = digit;
00386 f.len = duration;
00387 if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
00388 ast_mutex_unlock(&p->lock);
00389
00390 return res;
00391 }
00392
00393 static int local_sendtext(struct ast_channel *ast, const char *text)
00394 {
00395 struct local_pvt *p = ast->tech_pvt;
00396 int res = -1;
00397 struct ast_frame f = { AST_FRAME_TEXT, };
00398 int isoutbound;
00399
00400 if (!p)
00401 return -1;
00402
00403 ast_mutex_lock(&p->lock);
00404 isoutbound = IS_OUTBOUND(ast, p);
00405 f.data = (char *) text;
00406 f.datalen = strlen(text) + 1;
00407 if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
00408 ast_mutex_unlock(&p->lock);
00409 return res;
00410 }
00411
00412 static int local_sendhtml(struct ast_channel *ast, int subclass, const char *data, int datalen)
00413 {
00414 struct local_pvt *p = ast->tech_pvt;
00415 int res = -1;
00416 struct ast_frame f = { AST_FRAME_HTML, };
00417 int isoutbound;
00418
00419 if (!p)
00420 return -1;
00421
00422 ast_mutex_lock(&p->lock);
00423 isoutbound = IS_OUTBOUND(ast, p);
00424 f.subclass = subclass;
00425 f.data = (char *)data;
00426 f.datalen = datalen;
00427 if (!(res = local_queue_frame(p, isoutbound, &f, ast)))
00428 ast_mutex_unlock(&p->lock);
00429 return res;
00430 }
00431
00432
00433
00434 static int local_call(struct ast_channel *ast, char *dest, int timeout)
00435 {
00436 struct local_pvt *p = ast->tech_pvt;
00437 int res;
00438 struct ast_var_t *varptr = NULL, *new;
00439 size_t len, namelen;
00440
00441 if (!p)
00442 return -1;
00443
00444 ast_mutex_lock(&p->lock);
00445
00446
00447
00448
00449
00450 p->chan->cid.cid_num = ast_strdup(p->owner->cid.cid_num);
00451 p->chan->cid.cid_name = ast_strdup(p->owner->cid.cid_name);
00452 p->chan->cid.cid_rdnis = ast_strdup(p->owner->cid.cid_rdnis);
00453 p->chan->cid.cid_ani = ast_strdup(p->owner->cid.cid_ani);
00454 p->chan->cid.cid_pres = p->owner->cid.cid_pres;
00455 ast_string_field_set(p->chan, language, p->owner->language);
00456 ast_string_field_set(p->chan, accountcode, p->owner->accountcode);
00457 p->chan->cdrflags = p->owner->cdrflags;
00458
00459
00460
00461 AST_LIST_TRAVERSE(&p->owner->varshead, varptr, entries) {
00462 namelen = strlen(varptr->name);
00463 len = sizeof(struct ast_var_t) + namelen + strlen(varptr->value) + 2;
00464 if ((new = ast_calloc(1, len))) {
00465 memcpy(new, varptr, len);
00466 new->value = &(new->name[0]) + namelen + 1;
00467 AST_LIST_INSERT_TAIL(&p->chan->varshead, new, entries);
00468 }
00469 }
00470
00471
00472 if (!(res = ast_pbx_start(p->chan)))
00473 ast_set_flag(p, LOCAL_LAUNCHED_PBX);
00474
00475 ast_mutex_unlock(&p->lock);
00476 return res;
00477 }
00478
00479
00480 static int local_hangup(struct ast_channel *ast)
00481 {
00482 struct local_pvt *p = ast->tech_pvt;
00483 int isoutbound;
00484 struct ast_frame f = { AST_FRAME_CONTROL, AST_CONTROL_HANGUP };
00485 struct ast_channel *ochan = NULL;
00486 int glaredetect = 0, res = 0;
00487
00488 if (!p)
00489 return -1;
00490
00491 ast_mutex_lock(&p->lock);
00492 isoutbound = IS_OUTBOUND(ast, p);
00493 if (isoutbound) {
00494 const char *status = pbx_builtin_getvar_helper(p->chan, "DIALSTATUS");
00495 if ((status) && (p->owner))
00496 pbx_builtin_setvar_helper(p->owner, "CHANLOCALSTATUS", status);
00497 p->chan = NULL;
00498 ast_clear_flag(p, LOCAL_LAUNCHED_PBX);
00499 ast_module_user_remove(p->u_chan);
00500 } else {
00501 p->owner = NULL;
00502 ast_module_user_remove(p->u_owner);
00503 }
00504
00505 ast->tech_pvt = NULL;
00506
00507 if (!p->owner && !p->chan) {
00508
00509 glaredetect = ast_test_flag(p, LOCAL_GLARE_DETECT);
00510
00511
00512 if (glaredetect)
00513 ast_set_flag(p, LOCAL_CANCEL_QUEUE);
00514 ast_mutex_unlock(&p->lock);
00515
00516 AST_LIST_LOCK(&locals);
00517 AST_LIST_REMOVE(&locals, p, list);
00518 AST_LIST_UNLOCK(&locals);
00519
00520 ast_mutex_lock(&p->lock);
00521 ast_mutex_unlock(&p->lock);
00522
00523 if (!glaredetect) {
00524 ast_mutex_destroy(&p->lock);
00525 free(p);
00526 }
00527 return 0;
00528 }
00529 if (p->chan && !ast_test_flag(p, LOCAL_LAUNCHED_PBX))
00530
00531 ochan = p->chan;
00532 else
00533 res = local_queue_frame(p, isoutbound, &f, NULL);
00534 if (!res)
00535 ast_mutex_unlock(&p->lock);
00536 if (ochan)
00537 ast_hangup(ochan);
00538 return 0;
00539 }
00540
00541
00542 static struct local_pvt *local_alloc(const char *data, int format)
00543 {
00544 struct local_pvt *tmp = NULL;
00545 char *c = NULL, *opts = NULL;
00546
00547 if (!(tmp = ast_calloc(1, sizeof(*tmp))))
00548 return NULL;
00549
00550
00551 ast_mutex_init(&tmp->lock);
00552 ast_copy_string(tmp->exten, data, sizeof(tmp->exten));
00553
00554
00555 if ((opts = strchr(tmp->exten, '/'))) {
00556 *opts++ = '\0';
00557 if (strchr(opts, 'n'))
00558 ast_set_flag(tmp, LOCAL_NO_OPTIMIZATION);
00559 }
00560
00561
00562 if ((c = strchr(tmp->exten, '@')))
00563 *c++ = '\0';
00564
00565 ast_copy_string(tmp->context, c ? c : "default", sizeof(tmp->context));
00566
00567 tmp->reqformat = format;
00568
00569 if (!ast_exists_extension(NULL, tmp->context, tmp->exten, 1, NULL)) {
00570 ast_log(LOG_NOTICE, "No such extension/context %s@%s creating local channel\n", tmp->exten, tmp->context);
00571 ast_mutex_destroy(&tmp->lock);
00572 free(tmp);
00573 tmp = NULL;
00574 } else {
00575
00576 AST_LIST_LOCK(&locals);
00577 AST_LIST_INSERT_HEAD(&locals, tmp, list);
00578 AST_LIST_UNLOCK(&locals);
00579 }
00580
00581 return tmp;
00582 }
00583
00584
00585 static struct ast_channel *local_new(struct local_pvt *p, int state)
00586 {
00587 struct ast_channel *tmp = NULL, *tmp2 = NULL;
00588 int randnum = ast_random() & 0xffff, fmt = 0;
00589 const char *t;
00590 int ama;
00591
00592
00593
00594 if (p->owner && p->owner->accountcode)
00595 t = p->owner->accountcode;
00596 else
00597 t = "";
00598
00599 if (p->owner)
00600 ama = p->owner->amaflags;
00601 else
00602 ama = 0;
00603 if (!(tmp = ast_channel_alloc(1, state, 0, 0, t, p->exten, p->context, ama, "Local/%s@%s-%04x,1", p->exten, p->context, randnum))
00604 || !(tmp2 = ast_channel_alloc(1, AST_STATE_RING, 0, 0, t, p->exten, p->context, ama, "Local/%s@%s-%04x,2", p->exten, p->context, randnum))) {
00605 if (tmp)
00606 ast_channel_free(tmp);
00607 if (tmp2)
00608 ast_channel_free(tmp2);
00609 ast_log(LOG_WARNING, "Unable to allocate channel structure(s)\n");
00610 return NULL;
00611 }
00612
00613 tmp2->tech = tmp->tech = &local_tech;
00614
00615 tmp->nativeformats = p->reqformat;
00616 tmp2->nativeformats = p->reqformat;
00617
00618
00619 fmt = ast_best_codec(p->reqformat);
00620 tmp->writeformat = fmt;
00621 tmp2->writeformat = fmt;
00622 tmp->rawwriteformat = fmt;
00623 tmp2->rawwriteformat = fmt;
00624 tmp->readformat = fmt;
00625 tmp2->readformat = fmt;
00626 tmp->rawreadformat = fmt;
00627 tmp2->rawreadformat = fmt;
00628
00629 tmp->tech_pvt = p;
00630 tmp2->tech_pvt = p;
00631
00632 p->owner = tmp;
00633 p->chan = tmp2;
00634 p->u_owner = ast_module_user_add(p->owner);
00635 p->u_chan = ast_module_user_add(p->chan);
00636
00637 ast_copy_string(tmp->context, p->context, sizeof(tmp->context));
00638 ast_copy_string(tmp2->context, p->context, sizeof(tmp2->context));
00639 ast_copy_string(tmp2->exten, p->exten, sizeof(tmp->exten));
00640 tmp->priority = 1;
00641 tmp2->priority = 1;
00642
00643 return tmp;
00644 }
00645
00646
00647
00648 static struct ast_channel *local_request(const char *type, int format, void *data, int *cause)
00649 {
00650 struct local_pvt *p = NULL;
00651 struct ast_channel *chan = NULL;
00652
00653
00654 if ((p = local_alloc(data, format)))
00655 chan = local_new(p, AST_STATE_DOWN);
00656
00657 return chan;
00658 }
00659
00660
00661 static int locals_show(int fd, int argc, char **argv)
00662 {
00663 struct local_pvt *p = NULL;
00664
00665 if (argc != 3)
00666 return RESULT_SHOWUSAGE;
00667
00668 AST_LIST_LOCK(&locals);
00669 if (!AST_LIST_EMPTY(&locals)) {
00670 AST_LIST_TRAVERSE(&locals, p, list) {
00671 ast_mutex_lock(&p->lock);
00672 ast_cli(fd, "%s -- %s@%s\n", p->owner ? p->owner->name : "<unowned>", p->exten, p->context);
00673 ast_mutex_unlock(&p->lock);
00674 }
00675 } else
00676 ast_cli(fd, "No local channels in use\n");
00677 AST_LIST_UNLOCK(&locals);
00678
00679 return RESULT_SUCCESS;
00680 }
00681
00682 static char show_locals_usage[] =
00683 "Usage: local show channels\n"
00684 " Provides summary information on active local proxy channels.\n";
00685
00686 static struct ast_cli_entry cli_local[] = {
00687 { { "local", "show", "channels", NULL },
00688 locals_show, "List status of local channels",
00689 show_locals_usage },
00690 };
00691
00692
00693 static int load_module(void)
00694 {
00695
00696 if (ast_channel_register(&local_tech)) {
00697 ast_log(LOG_ERROR, "Unable to register channel class 'Local'\n");
00698 return -1;
00699 }
00700 ast_cli_register_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
00701 return 0;
00702 }
00703
00704
00705 static int unload_module(void)
00706 {
00707 struct local_pvt *p = NULL;
00708
00709
00710 ast_cli_unregister_multiple(cli_local, sizeof(cli_local) / sizeof(struct ast_cli_entry));
00711 ast_channel_unregister(&local_tech);
00712 if (!AST_LIST_LOCK(&locals)) {
00713
00714 AST_LIST_TRAVERSE(&locals, p, list) {
00715 if (p->owner)
00716 ast_softhangup(p->owner, AST_SOFTHANGUP_APPUNLOAD);
00717 }
00718 AST_LIST_UNLOCK(&locals);
00719 AST_LIST_HEAD_DESTROY(&locals);
00720 } else {
00721 ast_log(LOG_WARNING, "Unable to lock the monitor\n");
00722 return -1;
00723 }
00724 return 0;
00725 }
00726
00727 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Local Proxy Channel");