Sat Sep 16 05:47:47 2006

Asterisk developer's documentation


res_features.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 1999 - 2006, Digium, Inc.
00005  *
00006  * Mark Spencer <markster@digium.com>
00007  *
00008  * See http://www.asterisk.org for more information about
00009  * the Asterisk project. Please do not directly contact
00010  * any of the maintainers of this project for assistance;
00011  * the project provides a web site, mailing lists and IRC
00012  * channels for your use.
00013  *
00014  * This program is free software, distributed under the terms of
00015  * the GNU General Public License Version 2. See the LICENSE file
00016  * at the top of the source tree.
00017  */
00018 
00019 /*! \file
00020  *
00021  * \brief Routines implementing call parking
00022  * 
00023  */
00024 
00025 #include <pthread.h>
00026 #include <stdlib.h>
00027 #include <errno.h>
00028 #include <unistd.h>
00029 #include <string.h>
00030 #include <stdlib.h>
00031 #include <stdio.h>
00032 #include <sys/time.h>
00033 #include <sys/signal.h>
00034 #include <netinet/in.h>
00035 
00036 #include "asterisk.h"
00037 
00038 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 38686 $")
00039 
00040 #include "asterisk/lock.h"
00041 #include "asterisk/file.h"
00042 #include "asterisk/logger.h"
00043 #include "asterisk/channel.h"
00044 #include "asterisk/pbx.h"
00045 #include "asterisk/options.h"
00046 #include "asterisk/causes.h"
00047 #include "asterisk/module.h"
00048 #include "asterisk/translate.h"
00049 #include "asterisk/app.h"
00050 #include "asterisk/say.h"
00051 #include "asterisk/features.h"
00052 #include "asterisk/musiconhold.h"
00053 #include "asterisk/config.h"
00054 #include "asterisk/cli.h"
00055 #include "asterisk/manager.h"
00056 #include "asterisk/utils.h"
00057 #include "asterisk/adsi.h"
00058 #include "asterisk/monitor.h"
00059 
00060 #ifdef __AST_DEBUG_MALLOC
00061 static void FREE(void *ptr)
00062 {
00063    free(ptr);
00064 }
00065 #else
00066 #define FREE free
00067 #endif
00068 
00069 #define DEFAULT_PARK_TIME 45000
00070 #define DEFAULT_TRANSFER_DIGIT_TIMEOUT 3000
00071 #define DEFAULT_FEATURE_DIGIT_TIMEOUT 500
00072 
00073 #define AST_MAX_WATCHERS 256
00074 
00075 /*! List of meter maids (parking lot watchers) */
00076 struct features_parkwatch {
00077    void (*callback)(char *exten, char *context);   /*! Callback for notification */
00078    int id;                 /*! Meter maid ID */
00079    char *context;             /*! Watched context */
00080    struct features_parkwatch *next;    /*! Next in this simple list */
00081 };
00082 
00083 /*! Metermaid ID */
00084 struct features_parkwatch *metermaids;
00085 int metermaidid = 0;
00086 
00087 static char *parkedcall = "ParkedCall";
00088 
00089 /* No more than 45 seconds parked before you do something with them */
00090 static int parkingtime = DEFAULT_PARK_TIME;
00091 
00092 /* Context for which parking is made accessible */
00093 static char parking_con[AST_MAX_EXTENSION];
00094 
00095 /* Context for dialback for parking (KLUDGE) */
00096 static char parking_con_dial[AST_MAX_EXTENSION];
00097 
00098 /* Extension you type to park the call */
00099 static char parking_ext[AST_MAX_EXTENSION];
00100 
00101 static char pickup_ext[AST_MAX_EXTENSION];
00102 
00103 static int parkaddhints = 0;
00104 
00105 /* Default sounds */
00106 static char courtesytone[256];
00107 static char xfersound[256];
00108 static char xferfailsound[256];
00109 
00110 /* First available extension for parking */
00111 static int parking_start;
00112 
00113 /* Last available extension for parking */
00114 static int parking_stop;
00115 
00116 static int parking_offset;
00117 
00118 static int parkfindnext;
00119 
00120 static int adsipark;
00121 
00122 static int transferdigittimeout;
00123 static int featuredigittimeout;
00124 
00125 /* Default courtesy tone played when party joins conference */
00126 
00127 /* Registrar for operations */
00128 static char *registrar = "res_features";
00129 
00130 static char *synopsis = "Answer a parked call";
00131 
00132 static char *descrip = "ParkedCall(exten):"
00133 "Used to connect to a parked call.  This application is always\n"
00134 "registered internally and does not need to be explicitly added\n"
00135 "into the dialplan, although you should include the 'parkedcalls'\n"
00136 "context.\n";
00137 
00138 static char *parkcall = "Park";
00139 
00140 static char *synopsis2 = "Park yourself";
00141 
00142 static char *descrip2 = "Park():"
00143 "Used to park yourself (typically in combination with a supervised\n"
00144 "transfer to know the parking space). This application is always\n"
00145 "registered internally and does not need to be explicitly added\n"
00146 "into the dialplan, although you should include the 'parkedcalls'\n"
00147 "context.\n";
00148 
00149 static struct ast_app *monitor_app=NULL;
00150 static int monitor_ok=1;
00151 
00152 struct parkeduser {
00153    struct ast_channel *chan;
00154    struct timeval start;
00155    int parkingnum;
00156    /* Where to go if our parking time expires */
00157    char context[AST_MAX_CONTEXT];
00158    char exten[AST_MAX_EXTENSION];
00159    int priority;
00160    int parkingtime;
00161    int notquiteyet;
00162    char peername[1024];
00163    unsigned char moh_trys;
00164    struct parkeduser *next;
00165 };
00166 
00167 static struct parkeduser *parkinglot;
00168 
00169 AST_MUTEX_DEFINE_STATIC(parking_lock);
00170 
00171 static pthread_t parking_thread;
00172 
00173 STANDARD_LOCAL_USER;
00174 
00175 LOCAL_USER_DECL;
00176 
00177 char *ast_parking_ext(void)
00178 {
00179    return parking_ext;
00180 }
00181 
00182 char *ast_pickup_ext(void)
00183 {
00184    return pickup_ext;
00185 }
00186 
00187 struct ast_bridge_thread_obj 
00188 {
00189    struct ast_bridge_config bconfig;
00190    struct ast_channel *chan;
00191    struct ast_channel *peer;
00192 };
00193 
00194 static void check_goto_on_transfer(struct ast_channel *chan) 
00195 {
00196    struct ast_channel *xferchan;
00197    char *goto_on_transfer;
00198 
00199    goto_on_transfer = pbx_builtin_getvar_helper(chan, "GOTO_ON_BLINDXFR");
00200 
00201    if (!ast_strlen_zero(goto_on_transfer) && (xferchan = ast_channel_alloc(0))) {
00202       char *x;
00203       struct ast_frame *f;
00204       
00205       for (x = goto_on_transfer; x && *x; x++)
00206          if (*x == '^')
00207             *x = '|';
00208 
00209       strcpy(xferchan->name, chan->name);
00210       /* Make formats okay */
00211       xferchan->readformat = chan->readformat;
00212       xferchan->writeformat = chan->writeformat;
00213       ast_channel_masquerade(xferchan, chan);
00214       ast_parseable_goto(xferchan, goto_on_transfer);
00215       xferchan->_state = AST_STATE_UP;
00216       ast_clear_flag(xferchan, AST_FLAGS_ALL);  
00217       xferchan->_softhangup = 0;
00218       if ((f = ast_read(xferchan))) {
00219          ast_frfree(f);
00220          f = NULL;
00221          ast_pbx_start(xferchan);
00222       } else {
00223          ast_hangup(xferchan);
00224       }
00225    }
00226 }
00227 
00228 static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name);
00229 
00230 
00231 static void *ast_bridge_call_thread(void *data) 
00232 {
00233    struct ast_bridge_thread_obj *tobj = data;
00234 
00235    tobj->chan->appl = "Transferred Call";
00236    tobj->chan->data = tobj->peer->name;
00237    tobj->peer->appl = "Transferred Call";
00238    tobj->peer->data = tobj->chan->name;
00239    if (tobj->chan->cdr) {
00240       ast_cdr_reset(tobj->chan->cdr, NULL);
00241       ast_cdr_setdestchan(tobj->chan->cdr, tobj->peer->name);
00242    }
00243    if (tobj->peer->cdr) {
00244       ast_cdr_reset(tobj->peer->cdr, NULL);
00245       ast_cdr_setdestchan(tobj->peer->cdr, tobj->chan->name);
00246    }
00247 
00248    ast_bridge_call(tobj->peer, tobj->chan, &tobj->bconfig);
00249    ast_hangup(tobj->chan);
00250    ast_hangup(tobj->peer);
00251    tobj->chan = tobj->peer = NULL;
00252    free(tobj);
00253    tobj=NULL;
00254    return NULL;
00255 }
00256 
00257 static void ast_bridge_call_thread_launch(void *data) 
00258 {
00259    pthread_t thread;
00260    pthread_attr_t attr;
00261    struct sched_param sched;
00262 
00263    pthread_attr_init(&attr);
00264    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
00265    ast_pthread_create(&thread, &attr,ast_bridge_call_thread, data);
00266    pthread_attr_destroy(&attr);
00267    memset(&sched, 0, sizeof(sched));
00268    pthread_setschedparam(thread, SCHED_RR, &sched);
00269 }
00270 
00271 
00272 
00273 static int adsi_announce_park(struct ast_channel *chan, int parkingnum)
00274 {
00275    int res;
00276    int justify[5] = {ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT, ADSI_JUST_CENT};
00277    char tmp[256];
00278    char *message[5] = {NULL, NULL, NULL, NULL, NULL};
00279 
00280    snprintf(tmp, sizeof(tmp), "Parked on %d", parkingnum);
00281    message[0] = tmp;
00282    res = adsi_load_session(chan, NULL, 0, 1);
00283    if (res == -1) {
00284       return res;
00285    }
00286    return adsi_print(chan, message, justify, 1);
00287 }
00288 
00289 /*! Add parking watcher (metermaid) to list. These will be notified when we create
00290    or remove parking extensions in the dial plan 
00291  */
00292 int ast_park_metermaid_add(void (*maid)(char *exten, char *context), char *context)
00293 {
00294    struct features_parkwatch *newmaid;
00295    struct features_parkwatch *maids = metermaids;
00296 
00297    newmaid = malloc(sizeof(struct features_parkwatch));
00298    if (!newmaid) {
00299       ast_log(LOG_ERROR, "Can't allocate parking watcher, out of memory.\n");
00300       return -1;
00301    }
00302    memset(newmaid, 0, sizeof(struct features_parkwatch));
00303 
00304    /* Spool till end of list */
00305    while(maids && maids->next) {
00306       maids = maids->next;
00307    }
00308 
00309    newmaid->callback = maid;
00310    if (context)
00311       newmaid->context = strdup(context);
00312    newmaid->id = metermaidid;
00313 
00314    /* Generate new ID */
00315    metermaidid++;
00316 
00317    /* Link the new object to the list */
00318    if (maids)
00319       maids->next = newmaid;
00320    else
00321       metermaids = newmaid;
00322    if (option_debug > 1)
00323       ast_log(LOG_DEBUG, "Added metermaid # %d\n", metermaidid);
00324    return metermaidid;
00325 }
00326 
00327 /*! Remove parking watcher */
00328 int ast_park_metermaid_remove(int id)
00329 {
00330    struct features_parkwatch *maids = metermaids;
00331    struct features_parkwatch *prev = NULL;
00332    struct features_parkwatch *kill = NULL;
00333 
00334    while (maids && !kill) {
00335       if (maids->id == id) {
00336          if (prev) {
00337             prev->next = maids->next;
00338          } else {
00339             metermaids = maids->next;
00340          }
00341          kill = maids;
00342       }
00343       prev = maids;
00344       maids = maids->next;
00345    }
00346    if (!kill)
00347       return -1;     /* Did not find id */
00348    if (kill->context)
00349       free(kill->context);
00350    free(kill);
00351    return 0;
00352 }
00353 
00354 /*! Notify metermaids that we've changed an extension */
00355 static void notify_metermaids(char *exten, char *context)
00356 {
00357    struct features_parkwatch *maid = metermaids;
00358    if (!maid)
00359       return;
00360    while (maid) {
00361       if (!maid->context || !strcmp(context, maid->context))
00362          maid->callback(exten, context);
00363       maid = maid->next;
00364    }
00365    if (option_debug > 3)
00366       ast_log(LOG_DEBUG, "Notification of state change to metermaids %s@%s\n", exten, context);
00367    return;
00368 }
00369 
00370 /*--- ast_park_call: Park a call */
00371 /* We put the user in the parking list, then wake up the parking thread to be sure it looks
00372       after these channels too */
00373 int ast_park_call(struct ast_channel *chan, struct ast_channel *peer, int timeout, int *extout)
00374 {
00375    struct parkeduser *pu, *cur;
00376    int i,x,parking_range;
00377    char exten[AST_MAX_EXTENSION];
00378    struct ast_context *con;
00379 
00380    pu = malloc(sizeof(struct parkeduser));
00381    if (!pu) {
00382       ast_log(LOG_WARNING, "Out of memory\n");
00383       return -1;
00384    }
00385    memset(pu, 0, sizeof(struct parkeduser));
00386    ast_mutex_lock(&parking_lock);
00387    parking_range = parking_stop - parking_start+1;
00388    for (i = 0; i < parking_range; i++) {
00389       x = (i + parking_offset) % parking_range + parking_start;
00390       cur = parkinglot;
00391       while(cur) {
00392          if (cur->parkingnum == x) 
00393             break;
00394          cur = cur->next;
00395       }
00396       if (!cur)
00397          break;
00398    }
00399 
00400    if (!(i < parking_range)) {
00401       ast_log(LOG_WARNING, "No more parking spaces\n");
00402       free(pu);
00403       ast_mutex_unlock(&parking_lock);
00404       return -1;
00405    }
00406    if (parkfindnext) 
00407       parking_offset = x - parking_start + 1;
00408    chan->appl = "Parked Call";
00409    chan->data = NULL; 
00410 
00411    pu->chan = chan;
00412    /* Start music on hold */
00413    if (chan != peer) {
00414       ast_indicate(pu->chan, AST_CONTROL_HOLD);
00415       ast_moh_start(pu->chan, NULL);
00416    }
00417    pu->start = ast_tvnow();
00418    pu->parkingnum = x;
00419    if (timeout > 0)
00420       pu->parkingtime = timeout;
00421    else
00422       pu->parkingtime = parkingtime;
00423    if (extout)
00424       *extout = x;
00425    if (peer) 
00426       ast_copy_string(pu->peername, peer->name, sizeof(pu->peername));
00427 
00428    /* Remember what had been dialed, so that if the parking
00429       expires, we try to come back to the same place */
00430    if (!ast_strlen_zero(chan->macrocontext))
00431       ast_copy_string(pu->context, chan->macrocontext, sizeof(pu->context));
00432    else
00433       ast_copy_string(pu->context, chan->context, sizeof(pu->context));
00434    if (!ast_strlen_zero(chan->macroexten))
00435       ast_copy_string(pu->exten, chan->macroexten, sizeof(pu->exten));
00436    else
00437       ast_copy_string(pu->exten, chan->exten, sizeof(pu->exten));
00438    if (chan->macropriority)
00439       pu->priority = chan->macropriority;
00440    else
00441       pu->priority = chan->priority;
00442    pu->next = parkinglot;
00443    parkinglot = pu;
00444    /* If parking a channel directly, don't quiet yet get parking running on it */
00445    if (peer == chan) 
00446       pu->notquiteyet = 1;
00447    ast_mutex_unlock(&parking_lock);
00448    /* Wake up the (presumably select()ing) thread */
00449    pthread_kill(parking_thread, SIGURG);
00450    if (option_verbose > 1) 
00451       ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d. Will timeout back to extension [%s] %s, %d in %d seconds\n", pu->chan->name, pu->parkingnum, pu->context, pu->exten, pu->priority, (pu->parkingtime/1000));
00452 
00453    manager_event(EVENT_FLAG_CALL, "ParkedCall",
00454       "Exten: %d\r\n"
00455       "Channel: %s\r\n"
00456       "From: %s\r\n"
00457       "Timeout: %ld\r\n"
00458       "CallerID: %s\r\n"
00459       "CallerIDName: %s\r\n"
00460       ,pu->parkingnum, pu->chan->name, peer ? peer->name : ""
00461       ,(long)pu->start.tv_sec + (long)(pu->parkingtime/1000) - (long)time(NULL)
00462       ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
00463       ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
00464       );
00465 
00466    if (peer) {
00467       if (adsipark && adsi_available(peer)) {
00468          adsi_announce_park(peer, pu->parkingnum);
00469       }
00470       if (adsipark && adsi_available(peer)) {
00471          adsi_unload_session(peer);
00472       }
00473    }
00474    con = ast_context_find(parking_con);
00475    if (!con) {
00476       con = ast_context_create(NULL, parking_con, registrar);
00477       if (!con) {
00478          ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
00479       }
00480    }
00481    if (con) {
00482       snprintf(exten, sizeof(exten), "%d", x);
00483    
00484       if (ast_add_extension2(con, 1, exten, 1, NULL, NULL, parkedcall, strdup(exten), FREE, registrar) == 0)
00485          notify_metermaids(exten, parking_con); /* Notify watchers */
00486    }
00487    if (peer) 
00488       ast_say_digits(peer, pu->parkingnum, "", peer->language);
00489    if (pu->notquiteyet) {
00490       /* Wake up parking thread if we're really done */
00491       ast_moh_start(pu->chan, NULL);
00492       pu->notquiteyet = 0;
00493       pthread_kill(parking_thread, SIGURG);
00494    }
00495    return 0;
00496 }
00497 
00498 int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer, int timeout, int *extout)
00499 {
00500    struct ast_channel *chan;
00501    struct ast_frame *f;
00502 
00503    /* Make a new, fake channel that we'll use to masquerade in the real one */
00504    chan = ast_channel_alloc(0);
00505    if (chan) {
00506       /* Let us keep track of the channel name */
00507       snprintf(chan->name, sizeof (chan->name), "Parked/%s",rchan->name);
00508 
00509       /* Make formats okay */
00510       chan->readformat = rchan->readformat;
00511       chan->writeformat = rchan->writeformat;
00512       ast_channel_masquerade(chan, rchan);
00513 
00514       /* Setup the extensions and such */
00515       ast_copy_string(chan->context, rchan->context, sizeof(chan->context));
00516       ast_copy_string(chan->exten, rchan->exten, sizeof(chan->exten));
00517       chan->priority = rchan->priority;
00518 
00519       /* Make the masq execute */
00520       f = ast_read(chan);
00521       if (f)
00522          ast_frfree(f);
00523       ast_park_call(chan, peer, timeout, extout);
00524    } else {
00525       ast_log(LOG_WARNING, "Unable to create parked channel\n");
00526       return -1;
00527    }
00528    return 0;
00529 }
00530 
00531 
00532 #define FEATURE_RETURN_HANGUP    -1
00533 #define FEATURE_RETURN_SUCCESSBREAK  0
00534 #define FEATURE_RETURN_PBX_KEEPALIVE   AST_PBX_KEEPALIVE
00535 #define FEATURE_RETURN_NO_HANGUP_PEER  AST_PBX_NO_HANGUP_PEER
00536 #define FEATURE_RETURN_PASSDIGITS    21
00537 #define FEATURE_RETURN_STOREDIGITS   22
00538 #define FEATURE_RETURN_SUCCESS       23
00539 
00540 #define FEATURE_SENSE_CHAN (1 << 0)
00541 #define FEATURE_SENSE_PEER (1 << 1)
00542 
00543 
00544 static int builtin_automonitor(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
00545 {
00546    char *touch_monitor = NULL, *caller_chan_id = NULL, *callee_chan_id = NULL, *args = NULL, *touch_format = NULL;
00547    int x = 0;
00548    size_t len;
00549    struct ast_channel *caller_chan = NULL, *callee_chan = NULL;
00550 
00551 
00552    if(sense == 2) {
00553       caller_chan = peer;
00554       callee_chan = chan;
00555    } else {
00556       callee_chan = peer;
00557       caller_chan = chan;
00558    }
00559    
00560    if (!monitor_ok) {
00561       ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
00562       return -1;
00563    }
00564 
00565    if (!monitor_app) { 
00566       if (!(monitor_app = pbx_findapp("Monitor"))) {
00567          monitor_ok=0;
00568          ast_log(LOG_ERROR,"Cannot record the call. The monitor application is disabled.\n");
00569          return -1;
00570       }
00571    }
00572    if (!ast_strlen_zero(courtesytone)) {
00573       if (ast_autoservice_start(callee_chan))
00574          return -1;
00575       if (!ast_streamfile(caller_chan, courtesytone, caller_chan->language)) {
00576          if (ast_waitstream(caller_chan, "") < 0) {
00577             ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
00578             ast_autoservice_stop(callee_chan);
00579             return -1;
00580          }
00581       }
00582       if (ast_autoservice_stop(callee_chan))
00583          return -1;
00584    }
00585    
00586    if (callee_chan->monitor) {
00587       if (option_verbose > 3)
00588          ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to stop recording call.\n", code);
00589       ast_monitor_stop(callee_chan, 1);
00590       return FEATURE_RETURN_SUCCESS;
00591    }
00592 
00593    if (caller_chan && callee_chan) {
00594       touch_format = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR_FORMAT");
00595       if (!touch_format)
00596          touch_format = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR_FORMAT");
00597 
00598       touch_monitor = pbx_builtin_getvar_helper(caller_chan, "TOUCH_MONITOR");
00599       if (!touch_monitor)
00600          touch_monitor = pbx_builtin_getvar_helper(callee_chan, "TOUCH_MONITOR");
00601       
00602       if (touch_monitor) {
00603          len = strlen(touch_monitor) + 50;
00604          args = alloca(len);
00605          snprintf(args, len, "%s|auto-%ld-%s|m", (touch_format) ? touch_format : "wav", time(NULL), touch_monitor);
00606       } else {
00607          caller_chan_id = ast_strdupa(caller_chan->cid.cid_num ? caller_chan->cid.cid_num : caller_chan->name);
00608          callee_chan_id = ast_strdupa(callee_chan->cid.cid_num ? callee_chan->cid.cid_num : callee_chan->name);
00609          len = strlen(caller_chan_id) + strlen(callee_chan_id) + 50;
00610          args = alloca(len);
00611          snprintf(args, len, "%s|auto-%ld-%s-%s|m", (touch_format) ? touch_format : "wav", time(NULL), caller_chan_id, callee_chan_id);
00612       }
00613 
00614       for( x = 0; x < strlen(args); x++)
00615          if (args[x] == '/')
00616             args[x] = '-';
00617       
00618       if (option_verbose > 3)
00619          ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to record call. filename: %s\n", code, args);
00620 
00621       pbx_exec(callee_chan, monitor_app, args, 1);
00622       
00623       return FEATURE_RETURN_SUCCESS;
00624    }
00625    
00626    ast_log(LOG_NOTICE,"Cannot record the call. One or both channels have gone away.\n");  
00627    return -1;
00628 }
00629 
00630 static int builtin_disconnect(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
00631 {
00632    if (option_verbose > 3)
00633       ast_verbose(VERBOSE_PREFIX_3 "User hit '%s' to disconnect call.\n", code);
00634    return FEATURE_RETURN_HANGUP;
00635 }
00636 
00637 static int builtin_blindtransfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
00638 {
00639    struct ast_channel *transferer;
00640    struct ast_channel *transferee;
00641    char *transferer_real_context;
00642    char newext[256];
00643    int res;
00644 
00645    if (sense == FEATURE_SENSE_PEER) {
00646       transferer = peer;
00647       transferee = chan;
00648    } else {
00649       transferer = chan;
00650       transferee = peer;
00651    }
00652    if (!(transferer_real_context = pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
00653       !(transferer_real_context = pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
00654       /* Use the non-macro context to transfer the call */
00655       if (!ast_strlen_zero(transferer->macrocontext))
00656          transferer_real_context = transferer->macrocontext;
00657       else
00658          transferer_real_context = transferer->context;
00659    }
00660    /* Start autoservice on chan while we talk
00661       to the originator */
00662    ast_indicate(transferee, AST_CONTROL_HOLD);
00663    ast_autoservice_start(transferee);
00664    ast_moh_start(transferee, NULL);
00665 
00666    memset(newext, 0, sizeof(newext));
00667    
00668    /* Transfer */
00669    if ((res=ast_streamfile(transferer, "pbx-transfer", transferer->language))) {
00670       ast_moh_stop(transferee);
00671       ast_autoservice_stop(transferee);
00672       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00673       return res;
00674    }
00675    if ((res=ast_waitstream(transferer, AST_DIGIT_ANY)) < 0) {
00676       ast_moh_stop(transferee);
00677       ast_autoservice_stop(transferee);
00678       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00679       return res;
00680    } else if (res > 0) {
00681       /* If they've typed a digit already, handle it */
00682       newext[0] = (char) res;
00683    }
00684 
00685    ast_stopstream(transferer);
00686    res = ast_app_dtget(transferer, transferer_real_context, newext, sizeof(newext), 100, transferdigittimeout);
00687    if (res < 0) {
00688       ast_moh_stop(transferee);
00689       ast_autoservice_stop(transferee);
00690       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00691       return res;
00692    }
00693    if (!strcmp(newext, ast_parking_ext())) {
00694       ast_moh_stop(transferee);
00695 
00696       res = ast_autoservice_stop(transferee);
00697       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00698       if (res)
00699          res = -1;
00700       else if (!ast_park_call(transferee, transferer, 0, NULL)) {
00701          /* We return non-zero, but tell the PBX not to hang the channel when
00702             the thread dies -- We have to be careful now though.  We are responsible for 
00703             hanging up the channel, else it will never be hung up! */
00704 
00705          if (transferer == peer)
00706             res = AST_PBX_KEEPALIVE;
00707          else
00708             res = AST_PBX_NO_HANGUP_PEER;
00709          return res;
00710       } else {
00711          ast_log(LOG_WARNING, "Unable to park call %s\n", transferee->name);
00712       }
00713       /* XXX Maybe we should have another message here instead of invalid extension XXX */
00714    } else if (ast_exists_extension(transferee, transferer_real_context, newext, 1, transferer->cid.cid_num)) {
00715       pbx_builtin_setvar_helper(peer, "BLINDTRANSFER", chan->name);
00716       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", peer->name);
00717       ast_moh_stop(transferee);
00718       res=ast_autoservice_stop(transferee);
00719       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00720       if (!transferee->pbx) {
00721          /* Doh!  Use our handy async_goto functions */
00722          if (option_verbose > 2) 
00723             ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n"
00724                         ,transferee->name, newext, transferer_real_context);
00725          if (ast_async_goto(transferee, transferer_real_context, newext, 1))
00726             ast_log(LOG_WARNING, "Async goto failed :-(\n");
00727          res = -1;
00728       } else {
00729          /* Set the channel's new extension, since it exists, using transferer context */
00730          ast_copy_string(transferee->exten, newext, sizeof(transferee->exten));
00731          ast_copy_string(transferee->context, transferer_real_context, sizeof(transferee->context));
00732          transferee->priority = 0;
00733       }
00734       check_goto_on_transfer(transferer);
00735       return res;
00736    } else {
00737       if (option_verbose > 2) 
00738          ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context '%s'\n", newext, transferer_real_context);
00739    }
00740    if (!ast_strlen_zero(xferfailsound))
00741       res = ast_streamfile(transferer, xferfailsound, transferer->language);
00742    else
00743       res = 0;
00744    if (res) {
00745       ast_moh_stop(transferee);
00746       ast_autoservice_stop(transferee);
00747       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00748       return res;
00749    }
00750    res = ast_waitstream(transferer, AST_DIGIT_ANY);
00751    ast_stopstream(transferer);
00752    ast_moh_stop(transferee);
00753    res = ast_autoservice_stop(transferee);
00754    ast_indicate(transferee, AST_CONTROL_UNHOLD);
00755    if (res) {
00756       if (option_verbose > 1)
00757          ast_verbose(VERBOSE_PREFIX_2 "Hungup during autoservice stop on '%s'\n", transferee->name);
00758       return res;
00759    }
00760    return FEATURE_RETURN_SUCCESS;
00761 }
00762 
00763 static int builtin_atxfer(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
00764 {
00765    struct ast_channel *transferer;
00766    struct ast_channel *transferee;
00767    struct ast_channel *newchan, *xferchan=NULL;
00768    int outstate=0;
00769    struct ast_bridge_config bconfig;
00770    char *transferer_real_context;
00771    char xferto[256],dialstr[265];
00772    char *cid_num;
00773    char *cid_name;
00774    int res;
00775    struct ast_frame *f = NULL;
00776    struct ast_bridge_thread_obj *tobj;
00777 
00778    ast_log(LOG_DEBUG, "Executing Attended Transfer %s, %s (sense=%d) XXX\n", chan->name, peer->name, sense);
00779    if (sense == FEATURE_SENSE_PEER) {
00780       transferer = peer;
00781       transferee = chan;
00782    } else {
00783       transferer = chan;
00784       transferee = peer;
00785    }
00786    if (!(transferer_real_context=pbx_builtin_getvar_helper(transferee, "TRANSFER_CONTEXT")) &&
00787       !(transferer_real_context=pbx_builtin_getvar_helper(transferer, "TRANSFER_CONTEXT"))) {
00788       /* Use the non-macro context to transfer the call */
00789       if (!ast_strlen_zero(transferer->macrocontext))
00790          transferer_real_context = transferer->macrocontext;
00791       else
00792          transferer_real_context = transferer->context;
00793    }
00794    /* Start autoservice on chan while we talk
00795       to the originator */
00796    ast_indicate(transferee, AST_CONTROL_HOLD);
00797    ast_autoservice_start(transferee);
00798    ast_moh_start(transferee, NULL);
00799    memset(xferto, 0, sizeof(xferto));
00800    /* Transfer */
00801    if ((res = ast_streamfile(transferer, "pbx-transfer", transferer->language))) {
00802       ast_moh_stop(transferee);
00803       ast_autoservice_stop(transferee);
00804       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00805       return res;
00806    }
00807    if ((res=ast_waitstream(transferer, AST_DIGIT_ANY)) < 0) {
00808       ast_moh_stop(transferee);
00809       ast_autoservice_stop(transferee);
00810       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00811       return res;
00812    } else if(res > 0) {
00813       /* If they've typed a digit already, handle it */
00814       xferto[0] = (char) res;
00815    }
00816    if ((ast_app_dtget(transferer, transferer_real_context, xferto, sizeof(xferto), 100, transferdigittimeout))) {
00817       cid_num = transferer->cid.cid_num;
00818       cid_name = transferer->cid.cid_name;
00819       if (ast_exists_extension(transferer, transferer_real_context,xferto, 1, cid_num)) {
00820          snprintf(dialstr, sizeof(dialstr), "%s@%s/n", xferto, transferer_real_context);
00821          newchan = ast_feature_request_and_dial(transferer, "Local", ast_best_codec(transferer->nativeformats), dialstr, 15000, &outstate, cid_num, cid_name);
00822          ast_indicate(transferer, -1);
00823          if (newchan) {
00824             res = ast_channel_make_compatible(transferer, newchan);
00825             if (res < 0) {
00826                ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", transferer->name, newchan->name);
00827                ast_hangup(newchan);
00828                return -1;
00829             }
00830             memset(&bconfig,0,sizeof(struct ast_bridge_config));
00831             ast_set_flag(&(bconfig.features_caller), AST_FEATURE_DISCONNECT);
00832             ast_set_flag(&(bconfig.features_callee), AST_FEATURE_DISCONNECT);
00833             res = ast_bridge_call(transferer,newchan,&bconfig);
00834             if (newchan->_softhangup || newchan->_state != AST_STATE_UP || !transferer->_softhangup) {
00835                ast_hangup(newchan);
00836                if (f) {
00837                   ast_frfree(f);
00838                   f = NULL;
00839                }
00840                if (!ast_strlen_zero(xfersound) && !ast_streamfile(transferer, xfersound, transferer->language)) {
00841                   if (ast_waitstream(transferer, "") < 0) {
00842                      ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
00843                   }
00844                }
00845                ast_moh_stop(transferee);
00846                ast_autoservice_stop(transferee);
00847                ast_indicate(transferee, AST_CONTROL_UNHOLD);
00848                transferer->_softhangup = 0;
00849                return FEATURE_RETURN_SUCCESS;
00850             }
00851             
00852             res = ast_channel_make_compatible(transferee, newchan);
00853             if (res < 0) {
00854                ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", transferee->name, newchan->name);
00855                ast_hangup(newchan);
00856                return -1;
00857             }
00858             
00859             
00860             ast_moh_stop(transferee);
00861             
00862             if ((ast_autoservice_stop(transferee) < 0)
00863                || (ast_waitfordigit(transferee, 100) < 0)
00864                || (ast_waitfordigit(newchan, 100) < 0) 
00865                || ast_check_hangup(transferee) 
00866                || ast_check_hangup(newchan)) {
00867                ast_hangup(newchan);
00868                res = -1;
00869                return -1;
00870             }
00871 
00872             if ((xferchan = ast_channel_alloc(0))) {
00873                snprintf(xferchan->name, sizeof (xferchan->name), "Transfered/%s",transferee->name);
00874                /* Make formats okay */
00875                xferchan->readformat = transferee->readformat;
00876                xferchan->writeformat = transferee->writeformat;
00877                ast_channel_masquerade(xferchan, transferee);
00878                ast_explicit_goto(xferchan, transferee->context, transferee->exten, transferee->priority);
00879                xferchan->_state = AST_STATE_UP;
00880                ast_clear_flag(xferchan, AST_FLAGS_ALL);  
00881                xferchan->_softhangup = 0;
00882 
00883                if ((f = ast_read(xferchan))) {
00884                   ast_frfree(f);
00885                   f = NULL;
00886                }
00887                
00888             } else {
00889                ast_hangup(newchan);
00890                return -1;
00891             }
00892 
00893             newchan->_state = AST_STATE_UP;
00894             ast_clear_flag(newchan, AST_FLAGS_ALL);   
00895             newchan->_softhangup = 0;
00896 
00897             tobj = malloc(sizeof(struct ast_bridge_thread_obj));
00898             if (tobj) {
00899                memset(tobj,0,sizeof(struct ast_bridge_thread_obj));
00900                tobj->chan = xferchan;
00901                tobj->peer = newchan;
00902                tobj->bconfig = *config;
00903    
00904                if (!ast_strlen_zero(xfersound) && !ast_streamfile(newchan, xfersound, newchan->language)) {
00905                   if (ast_waitstream(newchan, "") < 0) {
00906                      ast_log(LOG_WARNING, "Failed to play transfer sound!\n");
00907                   }
00908                }
00909                ast_bridge_call_thread_launch(tobj);
00910             } else {
00911                ast_log(LOG_WARNING, "Out of memory!\n");
00912                ast_hangup(xferchan);
00913                ast_hangup(newchan);
00914             }
00915             return -1;
00916             
00917          } else {
00918             ast_moh_stop(transferee);
00919             ast_autoservice_stop(transferee);
00920             ast_indicate(transferee, AST_CONTROL_UNHOLD);
00921             /* any reason besides user requested cancel and busy triggers the failed sound */
00922             if (outstate != AST_CONTROL_UNHOLD && outstate != AST_CONTROL_BUSY && !ast_strlen_zero(xferfailsound)) {
00923                res = ast_streamfile(transferer, xferfailsound, transferer->language);
00924                if (!res && (ast_waitstream(transferer, "") < 0)) {
00925                   return -1;
00926                }
00927             }
00928             return FEATURE_RETURN_SUCCESS;
00929          }
00930       } else {
00931          ast_log(LOG_WARNING, "Extension %s does not exist in context %s\n",xferto,transferer_real_context);
00932          ast_moh_stop(transferee);
00933          ast_autoservice_stop(transferee);
00934          ast_indicate(transferee, AST_CONTROL_UNHOLD);
00935          res = ast_streamfile(transferer, "beeperr", transferer->language);
00936          if (!res && (ast_waitstream(transferer, "") < 0)) {
00937             return -1;
00938          }
00939       }
00940    }  else {
00941       ast_log(LOG_WARNING, "Did not read data.\n");
00942       ast_moh_stop(transferee);
00943       ast_autoservice_stop(transferee);
00944       ast_indicate(transferee, AST_CONTROL_UNHOLD);
00945       res = ast_streamfile(transferer, "beeperr", transferer->language);
00946       if (ast_waitstream(transferer, "") < 0) {
00947          return -1;
00948       }
00949    }
00950    ast_moh_stop(transferee);
00951    ast_autoservice_stop(transferee);
00952    ast_indicate(transferee, AST_CONTROL_UNHOLD);
00953 
00954    return FEATURE_RETURN_SUCCESS;
00955 }
00956 
00957 
00958 /* add atxfer and automon as undefined so you can only use em if you configure them */
00959 #define FEATURES_COUNT (sizeof(builtin_features) / sizeof(builtin_features[0]))
00960 struct ast_call_feature builtin_features[] = 
00961  {
00962    { AST_FEATURE_REDIRECT, "Blind Transfer", "blindxfer", "#", "#", builtin_blindtransfer, AST_FEATURE_FLAG_NEEDSDTMF },
00963    { AST_FEATURE_REDIRECT, "Attended Transfer", "atxfer", "", "", builtin_atxfer, AST_FEATURE_FLAG_NEEDSDTMF },
00964    { AST_FEATURE_AUTOMON, "One Touch Monitor", "automon", "", "", builtin_automonitor, AST_FEATURE_FLAG_NEEDSDTMF },
00965    { AST_FEATURE_DISCONNECT, "Disconnect Call", "disconnect", "*", "*", builtin_disconnect, AST_FEATURE_FLAG_NEEDSDTMF },
00966 };
00967 
00968 
00969 static AST_LIST_HEAD_STATIC(feature_list,ast_call_feature);
00970 
00971 /* register new feature into feature_list*/
00972 void ast_register_feature(struct ast_call_feature *feature)
00973 {
00974    if (!feature) {
00975       ast_log(LOG_NOTICE,"You didn't pass a feature!\n");
00976          return;
00977    }
00978   
00979    AST_LIST_LOCK(&feature_list);
00980    AST_LIST_INSERT_HEAD(&feature_list,feature,feature_entry);
00981    AST_LIST_UNLOCK(&feature_list);
00982 
00983    if (option_verbose >= 2) 
00984       ast_verbose(VERBOSE_PREFIX_2 "Registered Feature '%s'\n",feature->sname);
00985 }
00986 
00987 /* unregister feature from feature_list */
00988 void ast_unregister_feature(struct ast_call_feature *feature)
00989 {
00990    if (!feature) return;
00991 
00992    AST_LIST_LOCK(&feature_list);
00993    AST_LIST_REMOVE(&feature_list,feature,feature_entry);
00994    AST_LIST_UNLOCK(&feature_list);
00995    free(feature);
00996 }
00997 
00998 static void ast_unregister_features(void)
00999 {
01000    struct ast_call_feature *feature;
01001 
01002    AST_LIST_LOCK(&feature_list);
01003    while ((feature = AST_LIST_REMOVE_HEAD(&feature_list,feature_entry)))
01004       free(feature);
01005    AST_LIST_UNLOCK(&feature_list);
01006 }
01007 
01008 /* find a feature by name */
01009 static struct ast_call_feature *find_feature(char *name)
01010 {
01011    struct ast_call_feature *tmp;
01012 
01013    AST_LIST_LOCK(&feature_list);
01014    AST_LIST_TRAVERSE(&feature_list, tmp, feature_entry) {
01015       if (!strcasecmp(tmp->sname, name))
01016          break;
01017    }
01018    AST_LIST_UNLOCK(&feature_list);
01019 
01020    return tmp;
01021 }
01022 
01023 /* exec an app by feature */
01024 static int feature_exec_app(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
01025 {
01026    struct ast_app *app;
01027    struct ast_call_feature *feature;
01028    int res;
01029 
01030    AST_LIST_LOCK(&feature_list);
01031    AST_LIST_TRAVERSE(&feature_list,feature,feature_entry) {
01032       if (!strcasecmp(feature->exten,code)) break;
01033    }
01034    AST_LIST_UNLOCK(&feature_list);
01035 
01036    if (!feature) { /* shouldn't ever happen! */
01037       ast_log(LOG_NOTICE, "Found feature before, but at execing we've lost it??\n");
01038       return -1; 
01039    }
01040    
01041    app = pbx_findapp(feature->app);
01042    if (app) {
01043       struct ast_channel *work = chan;
01044       if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLEE))
01045          work = peer;
01046       res = pbx_exec(work, app, feature->app_args, 1);
01047       if (res == AST_PBX_KEEPALIVE)
01048          return FEATURE_RETURN_PBX_KEEPALIVE;
01049       else if (res == AST_PBX_NO_HANGUP_PEER)
01050          return FEATURE_RETURN_NO_HANGUP_PEER;
01051       else if (res)
01052          return FEATURE_RETURN_SUCCESSBREAK;
01053    } else {
01054       ast_log(LOG_WARNING, "Could not find application (%s)\n", feature->app);
01055       return -2;
01056    }
01057    
01058    return FEATURE_RETURN_SUCCESS;
01059 }
01060 
01061 static void unmap_features(void)
01062 {
01063    int x;
01064    for (x = 0; x < FEATURES_COUNT; x++)
01065       strcpy(builtin_features[x].exten, builtin_features[x].default_exten);
01066 }
01067 
01068 static int remap_feature(const char *name, const char *value)
01069 {
01070    int x;
01071    int res = -1;
01072    for (x = 0; x < FEATURES_COUNT; x++) {
01073       if (!strcasecmp(name, builtin_features[x].sname)) {
01074          ast_copy_string(builtin_features[x].exten, value, sizeof(builtin_features[x].exten));
01075          if (option_verbose > 1)
01076             ast_verbose(VERBOSE_PREFIX_2 "Remapping feature %s (%s) to sequence '%s'\n", builtin_features[x].fname, builtin_features[x].sname, builtin_features[x].exten);
01077          res = 0;
01078       } else if (!strcmp(value, builtin_features[x].exten)) 
01079          ast_log(LOG_WARNING, "Sequence '%s' already mapped to function %s (%s) while assigning to %s\n", value, builtin_features[x].fname, builtin_features[x].sname, name);
01080    }
01081    return res;
01082 }
01083 
01084 static int ast_feature_interpret(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config, char *code, int sense)
01085 {
01086    int x;
01087    struct ast_flags features;
01088    int res = FEATURE_RETURN_PASSDIGITS;
01089    struct ast_call_feature *feature;
01090    char *dynamic_features=pbx_builtin_getvar_helper(chan,"DYNAMIC_FEATURES");
01091 
01092    if (sense == FEATURE_SENSE_CHAN)
01093       ast_copy_flags(&features, &(config->features_caller), AST_FLAGS_ALL);   
01094    else
01095       ast_copy_flags(&features, &(config->features_callee), AST_FLAGS_ALL);   
01096    ast_log(LOG_DEBUG, "Feature interpret: chan=%s, peer=%s, sense=%d, features=%d\n", chan->name, peer->name, sense, features.flags);
01097 
01098    for (x=0; x < FEATURES_COUNT; x++) {
01099       if ((ast_test_flag(&features, builtin_features[x].feature_mask)) &&
01100           !ast_strlen_zero(builtin_features[x].exten)) {
01101          /* Feature is up for consideration */
01102          if (!strcmp(builtin_features[x].exten, code)) {
01103             res = builtin_features[x].operation(chan, peer, config, code, sense);
01104             break;
01105          } else if (!strncmp(builtin_features[x].exten, code, strlen(code))) {
01106             if (res == FEATURE_RETURN_PASSDIGITS)
01107                res = FEATURE_RETURN_STOREDIGITS;
01108          }
01109       }
01110    }
01111 
01112 
01113    if (!ast_strlen_zero(dynamic_features)) {
01114       char *tmp = ast_strdupa(dynamic_features);
01115       char *tok;
01116 
01117       if (!tmp)
01118          return res;
01119       
01120       while ((tok = strsep(&tmp, "#")) != NULL) {
01121          feature = find_feature(tok);
01122          
01123          if (feature) {
01124             /* Feature is up for consideration */
01125             if (!strcmp(feature->exten, code)) {
01126                if (option_verbose > 2)
01127                   ast_verbose(VERBOSE_PREFIX_3 " Feature Found: %s exten: %s\n",feature->sname, tok);
01128                if (sense == FEATURE_SENSE_CHAN)
01129                   res = feature->operation(chan, peer, config, code, sense);
01130                else
01131                   res = feature->operation(peer, chan, config, code, sense);
01132                break;
01133             } else if (!strncmp(feature->exten, code, strlen(code))) {
01134                res = FEATURE_RETURN_STOREDIGITS;
01135             }
01136          }
01137       }
01138    }
01139    
01140    return res;
01141 }
01142 
01143 static void set_config_flags(struct ast_channel *chan, struct ast_channel *peer, struct ast_bridge_config *config)
01144 {
01145    int x;
01146    
01147    ast_clear_flag(config, AST_FLAGS_ALL); 
01148    for (x = 0; x < FEATURES_COUNT; x++) {
01149       if (ast_test_flag(builtin_features + x, AST_FEATURE_FLAG_NEEDSDTMF)) {
01150          if (ast_test_flag(&(config->features_caller), builtin_features[x].feature_mask))
01151             ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
01152 
01153          if (ast_test_flag(&(config->features_callee), builtin_features[x].feature_mask))
01154             ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
01155       }
01156    }
01157    
01158    if (chan && peer && !(ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_0) && ast_test_flag(config, AST_BRIDGE_DTMF_CHANNEL_1))) {
01159       char *dynamic_features;
01160 
01161       dynamic_features = pbx_builtin_getvar_helper(chan, "DYNAMIC_FEATURES");
01162 
01163       if (dynamic_features) {
01164          char *tmp = ast_strdupa(dynamic_features);
01165          char *tok;
01166          struct ast_call_feature *feature;
01167 
01168          if (!tmp) {
01169             return;
01170          }
01171 
01172          /* while we have a feature */
01173          while (NULL != (tok = strsep(&tmp, "#"))) {
01174             if ((feature = find_feature(tok))) {
01175                if (ast_test_flag(feature, AST_FEATURE_FLAG_NEEDSDTMF)) {
01176                   if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLER))
01177                      ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_0);
01178                   if (ast_test_flag(feature, AST_FEATURE_FLAG_CALLEE))
01179                      ast_set_flag(config, AST_BRIDGE_DTMF_CHANNEL_1);
01180                }
01181             }
01182          }
01183       }
01184    }
01185 }
01186 
01187 
01188 static struct ast_channel *ast_feature_request_and_dial(struct ast_channel *caller, const char *type, int format, void *data, int timeout, int *outstate, const char *cid_num, const char *cid_name)
01189 {
01190    int state = 0;
01191    int cause = 0;
01192    int to;
01193    struct ast_channel *chan;
01194    struct ast_channel *monitor_chans[2];
01195    struct ast_channel *active_channel;
01196    struct ast_frame *f = NULL;
01197    int res = 0, ready = 0;
01198    
01199    if ((chan = ast_request(type, format, data, &cause))) {
01200       ast_set_callerid(chan, cid_num, cid_name, cid_num);
01201       ast_channel_inherit_variables(caller, chan); 
01202       if (!ast_call(chan, data, timeout)) {
01203          struct timeval started;
01204          int x, len = 0;
01205          char *disconnect_code = NULL, *dialed_code = NULL;
01206 
01207          ast_indicate(caller, AST_CONTROL_RINGING);
01208          /* support dialing of the featuremap disconnect code while performing an attended tranfer */
01209          for (x=0; x < FEATURES_COUNT; x++) {
01210             if (strcasecmp(builtin_features[x].sname, "disconnect"))
01211                continue;
01212 
01213             disconnect_code = builtin_features[x].exten;
01214             len = strlen(disconnect_code) + 1;
01215             dialed_code = alloca(len);
01216             memset(dialed_code, 0, len);
01217             break;
01218          }
01219          x = 0;
01220          started = ast_tvnow();
01221          to = timeout;
01222          while (!ast_check_hangup(caller) && timeout && (chan->_state != AST_STATE_UP)) {
01223             monitor_chans[0] = caller;
01224             monitor_chans[1] = chan;
01225             active_channel = ast_waitfor_n(monitor_chans, 2, &to);
01226 
01227             /* see if the timeout has been violated */
01228             if(ast_tvdiff_ms(ast_tvnow(), started) > timeout) {
01229                state = AST_CONTROL_UNHOLD;
01230                ast_log(LOG_NOTICE, "We exceeded our AT-timeout\n");
01231                break; /*doh! timeout*/
01232             }
01233 
01234             if (!active_channel) {
01235                continue;
01236             }
01237 
01238             if (chan && (chan == active_channel)){
01239                f = ast_read(chan);
01240                if (f == NULL) { /*doh! where'd he go?*/
01241                   state = AST_CONTROL_HANGUP;
01242                   res = 0;
01243                   break;
01244                }
01245                
01246                if (f->frametype == AST_FRAME_CONTROL || f->frametype == AST_FRAME_DTMF || f->frametype == AST_FRAME_TEXT) {
01247                   if (f->subclass == AST_CONTROL_RINGING) {
01248                      state = f->subclass;
01249                      if (option_verbose > 2)
01250                         ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", chan->name);
01251                      ast_indicate(caller, AST_CONTROL_RINGING);
01252                   } else if ((f->subclass == AST_CONTROL_BUSY) || (f->subclass == AST_CONTROL_CONGESTION)) {
01253                      state = f->subclass;
01254                      if (option_verbose > 2)
01255                         ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", chan->name);
01256                      ast_indicate(caller, AST_CONTROL_BUSY);
01257                      ast_frfree(f);
01258                      f = NULL;
01259                      break;
01260                   } else if (f->subclass == AST_CONTROL_ANSWER) {
01261                      /* This is what we are hoping for */
01262                      state = f->subclass;
01263                      ast_frfree(f);
01264                      f = NULL;
01265                      ready=1;
01266                      break;
01267                   } else {
01268                      ast_log(LOG_NOTICE, "Don't know what to do about control frame: %d\n", f->subclass);
01269                   }
01270                   /* else who cares */
01271                }
01272 
01273             } else if (caller && (active_channel == caller)) {
01274                f = ast_read(caller);
01275                if (f == NULL) { /*doh! where'd he go?*/
01276                   if (caller->_softhangup && !chan->_softhangup) {
01277                      /* make this a blind transfer */
01278                      ready = 1;
01279                      break;
01280                   }
01281                   state = AST_CONTROL_HANGUP;
01282                   res = 0;
01283                   break;
01284                }
01285                
01286                if (f->frametype == AST_FRAME_DTMF) {
01287                   dialed_code[x++] = f->subclass;
01288                   dialed_code[x] = '\0';
01289                   if (strlen(dialed_code) == len) {
01290                      x = 0;
01291                   } else if (x && strncmp(dialed_code, disconnect_code, x)) {
01292                      x = 0;
01293                      dialed_code[x] = '\0';
01294                   }
01295                   if (*dialed_code && !strcmp(dialed_code, disconnect_code)) {
01296                      /* Caller Canceled the call */
01297                      state = AST_CONTROL_UNHOLD;
01298                      ast_frfree(f);
01299                      f = NULL;
01300                      break;
01301                   }
01302                }
01303             }
01304             if (f) {
01305                ast_frfree(f);
01306             }
01307          }
01308       } else
01309          ast_log(LOG_NOTICE, "Unable to call channel %s/%s\n", type, (char *)data);
01310    } else {
01311       ast_log(LOG_NOTICE, "Unable to request channel %s/%s\n", type, (char *)data);
01312       switch(cause) {
01313       case AST_CAUSE_BUSY:
01314          state = AST_CONTROL_BUSY;
01315          break;
01316       case AST_CAUSE_CONGESTION:
01317          state = AST_CONTROL_CONGESTION;
01318          break;
01319       }
01320    }
01321    
01322    ast_indicate(caller, -1);
01323    if (chan && ready) {
01324       if (chan->_state == AST_STATE_UP) 
01325          state = AST_CONTROL_ANSWER;
01326       res = 0;
01327    } else if(chan) {
01328       res = -1;
01329       ast_hangup(chan);
01330       chan = NULL;
01331    } else {
01332       res = -1;
01333    }
01334    
01335    if (outstate)
01336       *outstate = state;
01337 
01338    if (chan && res <= 0) {
01339       if (!chan->cdr) {
01340          chan->cdr = ast_cdr_alloc();
01341       }
01342       if (chan->cdr) {
01343          char tmp[256];
01344          ast_cdr_init(chan->cdr, chan);
01345          snprintf(tmp, 256, "%s/%s", type, (char *)data);
01346          ast_cdr_setapp(chan->cdr,"Dial",tmp);
01347          ast_cdr_update(chan);
01348          ast_cdr_start(chan->cdr);
01349          ast_cdr_end(chan->cdr);
01350          /* If the cause wasn't handled properly */
01351          if (ast_cdr_disposition(chan->cdr,chan->hangupcause))
01352             ast_cdr_failed(chan->cdr);
01353       } else {
01354          ast_log(LOG_WARNING, "Unable to create Call Detail Record\n");
01355       }
01356    }
01357    
01358    return chan;
01359 }
01360 
01361 int ast_bridge_call(struct ast_channel *chan,struct ast_channel *peer,struct ast_bridge_config *config)
01362 {
01363    /* Copy voice back and forth between the two channels.  Give the peer
01364       the ability to transfer calls with '#<extension' syntax. */
01365    struct ast_frame *f;
01366    struct ast_channel *who;
01367    char chan_featurecode[FEATURE_MAX_LEN + 1]="";
01368    char peer_featurecode[FEATURE_MAX_LEN + 1]="";
01369    int res;
01370    int diff;
01371    int hasfeatures=0;
01372    int hadfeatures=0;
01373    struct ast_option_header *aoh;
01374    struct timeval start = { 0 , 0 };
01375    struct ast_bridge_config backup_config;
01376    char *monitor_exec;
01377 
01378    memset(&backup_config, 0, sizeof(backup_config));
01379 
01380    config->start_time = ast_tvnow();
01381 
01382    if (chan && peer) {
01383       pbx_builtin_setvar_helper(chan, "BRIDGEPEER", peer->name);
01384       pbx_builtin_setvar_helper(peer, "BRIDGEPEER", chan->name);
01385    } else if (chan)
01386       pbx_builtin_setvar_helper(chan, "BLINDTRANSFER", NULL);
01387 
01388    if (monitor_ok) {
01389       if (!monitor_app) { 
01390          if (!(monitor_app = pbx_findapp("Monitor")))
01391             monitor_ok=0;
01392       }
01393       if (monitor_app) {
01394          if ((monitor_exec = pbx_builtin_getvar_helper(chan, "AUTO_MONITOR"))) 
01395             pbx_exec(chan, monitor_app, monitor_exec, 1);
01396          else if ((monitor_exec = pbx_builtin_getvar_helper(peer, "AUTO_MONITOR")))
01397             pbx_exec(peer, monitor_app, monitor_exec, 1);
01398       }
01399    }
01400    
01401    set_config_flags(chan, peer, config);
01402    config->firstpass = 1;
01403 
01404    /* Answer if need be */
01405    if (ast_answer(chan))
01406       return -1;
01407    peer->appl = "Bridged Call";
01408    peer->data = chan->name;
01409 
01410    /* copy the userfield from the B-leg to A-leg if applicable */
01411    if (chan->cdr && peer->cdr && !ast_strlen_zero(peer->cdr->userfield)) {
01412       char tmp[256];
01413       if (!ast_strlen_zero(chan->cdr->userfield)) {
01414          snprintf(tmp, sizeof(tmp), "%s;%s", chan->cdr->userfield, peer->cdr->userfield);
01415          ast_cdr_appenduserfield(chan, tmp);
01416       } else
01417          ast_cdr_setuserfield(chan, peer->cdr->userfield);
01418       /* free the peer's cdr without ast_cdr_free complaining */
01419       free(peer->cdr);
01420       peer->cdr = NULL;
01421    }
01422    for (;;) {
01423       if (config->feature_timer)
01424          start = ast_tvnow();
01425 
01426       res = ast_channel_bridge(chan, peer, config, &f, &who);
01427 
01428       if (config->feature_timer) {
01429          /* Update time limit for next pass */
01430          diff = ast_tvdiff_ms(ast_tvnow(), start);
01431          config->feature_timer -= diff;
01432          if (hasfeatures) {
01433             /* Running on backup config, meaning a feature might be being
01434                activated, but that's no excuse to keep things going 
01435                indefinitely! */
01436             if (backup_config.feature_timer && ((backup_config.feature_timer -= diff) <= 0)) {
01437                ast_log(LOG_DEBUG, "Timed out, realtime this time!\n");
01438                config->feature_timer = 0;
01439                who = chan;
01440                if (f)
01441                   ast_frfree(f);
01442                f = NULL;
01443                res = 0;
01444             } else if (config->feature_timer <= 0) {
01445                /* Not *really* out of time, just out of time for
01446                   digits to come in for features. */
01447                ast_log(LOG_DEBUG, "Timed out for feature!\n");
01448                if (!ast_strlen_zero(peer_featurecode)) {
01449                   ast_dtmf_stream(chan, peer, peer_featurecode, 0);
01450                   memset(peer_featurecode, 0, sizeof(peer_featurecode));
01451                }
01452                if (!ast_strlen_zero(chan_featurecode)) {
01453                   ast_dtmf_stream(peer, chan, chan_featurecode, 0);
01454                   memset(chan_featurecode, 0, sizeof(chan_featurecode));
01455                }
01456                if (f)
01457                   ast_frfree(f);
01458                hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
01459                if (!hasfeatures) {
01460                   /* Restore original (possibly time modified) bridge config */
01461                   memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
01462                   memset(&backup_config, 0, sizeof(backup_config));
01463                }
01464                hadfeatures = hasfeatures;
01465                /* Continue as we were */
01466                continue;
01467             }
01468          } else {
01469             if (config->feature_timer <=0) {
01470                /* We ran out of time */
01471                config->feature_timer = 0;
01472                who = chan;
01473                if (f)
01474                   ast_frfree(f);
01475                f = NULL;
01476                res = 0;
01477             }
01478          }
01479       }
01480       if (res < 0) {
01481          ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", chan->name, peer->name);
01482          return -1;
01483       }
01484       
01485       if (!f || ((f->frametype == AST_FRAME_CONTROL) && ((f->subclass == AST_CONTROL_HANGUP) || (f->subclass == AST_CONTROL_BUSY) || 
01486          (f->subclass == AST_CONTROL_CONGESTION)))) {
01487             res = -1;
01488             break;
01489       }
01490       if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RINGING)) {
01491          if (who == chan)
01492             ast_indicate(peer, AST_CONTROL_RINGING);
01493          else
01494             ast_indicate(chan, AST_CONTROL_RINGING);
01495       }
01496       if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == -1)) {
01497          if (who == chan)
01498             ast_indicate(peer, -1);
01499          else
01500             ast_indicate(chan, -1);
01501       }
01502       if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_FLASH)) {
01503          if (who == chan)
01504             ast_indicate(peer, AST_CONTROL_FLASH);
01505          else
01506             ast_indicate(chan, AST_CONTROL_FLASH);
01507       }
01508       if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_OPTION)) {
01509          aoh = f->data;
01510          /* Forward option Requests */
01511          if (aoh && (aoh->flag == AST_OPTION_FLAG_REQUEST)) {
01512             if (who == chan)
01513                ast_channel_setoption(peer, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0);
01514             else
01515                ast_channel_setoption(chan, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0);
01516          }
01517       }
01518       /* check for '*', if we find it it's time to disconnect */
01519       if (f && (f->frametype == AST_FRAME_DTMF)) {
01520          char *featurecode;
01521          int sense;
01522          struct ast_channel *other;
01523 
01524          hadfeatures = hasfeatures;
01525          /* This cannot overrun because the longest feature is one shorter than our buffer */
01526          if (who == chan) {
01527             other = peer;
01528             sense = FEATURE_SENSE_CHAN;
01529             featurecode = chan_featurecode;
01530          } else  {
01531             other = chan;
01532             sense = FEATURE_SENSE_PEER;
01533             featurecode = peer_featurecode;
01534          }
01535          featurecode[strlen(featurecode)] = f->subclass;
01536          /* Get rid of the frame before we start doing "stuff" with the channels */
01537          ast_frfree(f);
01538          f = NULL;
01539          config->feature_timer = backup_config.feature_timer;
01540          res = ast_feature_interpret(chan, peer, config, featurecode, sense);
01541          switch(res) {
01542          case FEATURE_RETURN_PASSDIGITS:
01543             ast_dtmf_stream(other, who, featurecode, 0);
01544             /* Fall through */
01545          case FEATURE_RETURN_SUCCESS:
01546             memset(featurecode, 0, sizeof(chan_featurecode));
01547             break;
01548          }
01549          if (res >= FEATURE_RETURN_PASSDIGITS) {
01550             res = 0;
01551          } else 
01552             break;
01553          hasfeatures = !ast_strlen_zero(chan_featurecode) || !ast_strlen_zero(peer_featurecode);
01554          if (hadfeatures && !hasfeatures) {
01555             /* Restore backup */
01556             memcpy(config, &backup_config, sizeof(struct ast_bridge_config));
01557             memset(&backup_config, 0, sizeof(struct ast_bridge_config));
01558          } else if (hasfeatures) {
01559             if (!hadfeatures) {
01560                /* Backup configuration */
01561                memcpy(&backup_config, config, sizeof(struct ast_bridge_config));
01562                /* Setup temporary config options */
01563                config->play_warning = 0;
01564                ast_clear_flag(&(config->features_caller), AST_FEATURE_PLAY_WARNING);
01565                ast_clear_flag(&(config->features_callee), AST_FEATURE_PLAY_WARNING);
01566                config->warning_freq = 0;
01567                config->warning_sound = NULL;
01568                config->end_sound = NULL;
01569                config->start_sound = NULL;
01570                config->firstpass = 0;
01571             }
01572             config->start_time = ast_tvnow();
01573             config->feature_timer = featuredigittimeout;
01574             ast_log(LOG_DEBUG, "Set time limit to %ld\n", config->feature_timer);
01575          }
01576       }
01577       if (f)
01578          ast_frfree(f);
01579    }
01580    return res;
01581 }
01582 
01583 static void *do_parking_thread(void *ignore)
01584 {
01585    int ms, tms, max;
01586    struct parkeduser *pu, *pl, *pt = NULL;
01587    struct timeval tv;
01588    struct ast_frame *f;
01589    char exten[AST_MAX_EXTENSION];
01590    char *peername,*cp;
01591    char returnexten[AST_MAX_EXTENSION];
01592    struct ast_context *con;
01593    int x;
01594    fd_set rfds, efds;
01595    fd_set nrfds, nefds;
01596    FD_ZERO(&rfds);
01597    FD_ZERO(&efds);
01598 
01599    for (;;) {
01600       ms = -1;
01601       max = -1;
01602       ast_mutex_lock(&parking_lock);
01603       pl = NULL;
01604       pu = parkinglot;
01605       FD_ZERO(&nrfds);
01606       FD_ZERO(&nefds);
01607       while(pu) {
01608          if (pu->notquiteyet) {
01609             /* Pretend this one isn't here yet */
01610             pl = pu;
01611             pu = pu->next;
01612             continue;
01613          }
01614          tms = ast_tvdiff_ms(ast_tvnow(), pu->start);
01615          if (tms > pu->parkingtime) {
01616             /* Stop music on hold */
01617             ast_moh_stop(pu->chan);
01618             ast_indicate(pu->chan, AST_CONTROL_UNHOLD);
01619             /* Get chan, exten from derived kludge */
01620             if (pu->peername[0]) {
01621                peername = ast_strdupa(pu->peername);
01622                cp = strrchr(peername, '-');
01623                if (cp) 
01624                   *cp = 0;
01625                con = ast_context_find(parking_con_dial);
01626                if (!con) {
01627                   con = ast_context_create(NULL, parking_con_dial, registrar);
01628                   if (!con) {
01629                      ast_log(LOG_ERROR, "Parking dial context '%s' does not exist and unable to create\n", parking_con_dial);
01630                   }
01631                }
01632                if (con) {
01633                   snprintf(returnexten, sizeof(returnexten), "%s||t", peername);
01634                   ast_add_extension2(con, 1, peername, 1, NULL, NULL, "Dial", strdup(returnexten), FREE, registrar);
01635                }
01636                ast_copy_string(pu->chan->exten, peername, sizeof(pu->chan->exten));
01637                ast_copy_string(pu->chan->context, parking_con_dial, sizeof(pu->chan->context));
01638                pu->chan->priority = 1;
01639 
01640             } else {
01641                /* They've been waiting too long, send them back to where they came.  Theoretically they
01642                   should have their original extensions and such, but we copy to be on the safe side */
01643                ast_copy_string(pu->chan->exten, pu->exten, sizeof(pu->chan->exten));
01644                ast_copy_string(pu->chan->context, pu->context, sizeof(pu->chan->context));
01645                pu->chan->priority = pu->priority;
01646             }
01647 
01648             manager_event(EVENT_FLAG_CALL, "ParkedCallTimeOut",
01649                "Exten: %d\r\n"
01650                "Channel: %s\r\n"
01651                "CallerID: %s\r\n"
01652                "CallerIDName: %s\r\n"
01653                ,pu->parkingnum, pu->chan->name
01654                ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
01655                ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
01656                );
01657 
01658             if (option_verbose > 1) 
01659                ast_verbose(VERBOSE_PREFIX_2 "Timeout for %s parked on %d. Returning to %s,%s,%d\n", pu->chan->name, pu->parkingnum, pu->chan->context, pu->chan->exten, pu->chan->priority);
01660             /* Start up the PBX, or hang them up */
01661             if (ast_pbx_start(pu->chan))  {
01662                ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", pu->chan->name);
01663                ast_hangup(pu->chan);
01664             }
01665             /* And take them out of the parking lot */
01666             if (pl) 
01667                pl->next = pu->next;
01668             else
01669                parkinglot = pu->next;
01670             pt = pu;
01671             pu = pu->next;
01672             con = ast_context_find(parking_con);
01673             if (con) {
01674                snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
01675                if (ast_context_remove_extension2(con, exten, 1, NULL))
01676                   ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
01677                else
01678                   notify_metermaids(exten, parking_con); /* Notify watchers */
01679             } else
01680                ast_log(LOG_WARNING, "Whoa, no parking context?\n");
01681             free(pt);
01682          } else {
01683             for (x = 0; x < AST_MAX_FDS; x++) {
01684                if ((pu->chan->fds[x] > -1) && (FD_ISSET(pu->chan->fds[x], &rfds) || FD_ISSET(pu->chan->fds[x], &efds))) {
01685                   if (FD_ISSET(pu->chan->fds[x], &efds))
01686                      ast_set_flag(pu->chan, AST_FLAG_EXCEPTION);
01687                   else
01688                      ast_clear_flag(pu->chan, AST_FLAG_EXCEPTION);
01689                   pu->chan->fdno = x;
01690                   /* See if they need servicing */
01691                   f = ast_read(pu->chan);
01692                   if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass ==  AST_CONTROL_HANGUP))) {
01693                      if (f)
01694                         ast_frfree(f);
01695                      manager_event(EVENT_FLAG_CALL, "ParkedCallGiveUp",
01696                         "Exten: %d\r\n"
01697                         "Channel: %s\r\n"
01698                         "CallerID: %s\r\n"
01699                         "CallerIDName: %s\r\n"
01700                         ,pu->parkingnum, pu->chan->name
01701                         ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
01702                         ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
01703                         );
01704 
01705                      /* There's a problem, hang them up*/
01706                      if (option_verbose > 1) 
01707                         ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", pu->chan->name);
01708                      ast_hangup(pu->chan);
01709                      /* And take them out of the parking lot */
01710                      if (pl) 
01711                         pl->next = pu->next;
01712                      else
01713                         parkinglot = pu->next;
01714                      pt = pu;
01715                      pu = pu->next;
01716                      con = ast_context_find(parking_con);
01717                      if (con) {
01718                         snprintf(exten, sizeof(exten), "%d", pt->parkingnum);
01719                         if (ast_context_remove_extension2(con, exten, 1, NULL))
01720                            ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
01721                         else
01722                            notify_metermaids(exten, parking_con);
01723                      } else
01724                         ast_log(LOG_WARNING, "Whoa, no parking context?\n");
01725                      free(pt);
01726                      break;
01727                   } else {
01728                      /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */
01729                      ast_frfree(f);
01730                      if (pu->moh_trys < 3 && !pu->chan->generatordata) {
01731                         ast_log(LOG_DEBUG, "MOH on parked call stopped by outside source.  Restarting.\n");
01732                         ast_moh_start(pu->chan, NULL);
01733                         pu->moh_trys++;
01734                      }
01735                      goto std;   /* XXX Ick: jumping into an else statement??? XXX */
01736                   }
01737                }
01738             }
01739             if (x >= AST_MAX_FDS) {
01740 std:              for (x=0; x<AST_MAX_FDS; x++) {
01741                   /* Keep this one for next one */
01742                   if (pu->chan->fds[x] > -1) {
01743                      FD_SET(pu->chan->fds[x], &nrfds);
01744                      FD_SET(pu->chan->fds[x], &nefds);
01745                      if (pu->chan->fds[x] > max)
01746                         max = pu->chan->fds[x];
01747                   }
01748                }
01749                /* Keep track of our longest wait */
01750                if ((tms < ms) || (ms < 0))
01751                   ms = tms;
01752                pl = pu;
01753                pu = pu->next;
01754             }
01755          }
01756       }
01757       ast_mutex_unlock(&parking_lock);
01758       rfds = nrfds;
01759       efds = nefds;
01760       tv = ast_samp2tv(ms, 1000);
01761       /* Wait for something to happen */
01762       ast_select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL);
01763       pthread_testcancel();
01764    }
01765    return NULL;   /* Never reached */
01766 }
01767 
01768 static int park_call_exec(struct ast_channel *chan, void *data)
01769 {
01770    /* Data is unused at the moment but could contain a parking
01771       lot context eventually */
01772    int res=0;
01773    struct localuser *u;
01774    LOCAL_USER_ADD(u);
01775    /* Setup the exten/priority to be s/1 since we don't know
01776       where this call should return */
01777    strcpy(chan->exten, "s");
01778    chan->priority = 1;
01779    if (chan->_state != AST_STATE_UP)
01780       res = ast_answer(chan);
01781    if (!res)
01782       res = ast_safe_sleep(chan, 1000);
01783    if (!res)
01784       res = ast_park_call(chan, chan, 0, NULL);
01785    LOCAL_USER_REMOVE(u);
01786    if (!res)
01787       res = AST_PBX_KEEPALIVE;
01788    return res;
01789 }
01790 
01791 static int park_exec(struct ast_channel *chan, void *data)
01792 {
01793    int res=0;
01794    struct localuser *u;
01795    struct ast_channel *peer=NULL;
01796    struct parkeduser *pu, *pl=NULL;
01797    char exten[AST_MAX_EXTENSION];
01798    struct ast_context *con;
01799    int park;
01800    int dres;
01801    struct ast_bridge_config config;
01802 
01803    if (!data) {
01804       ast_log(LOG_WARNING, "Park requires an argument (extension number)\n");
01805       return -1;
01806    }
01807    LOCAL_USER_ADD(u);
01808    park = atoi((char *)data);
01809    ast_mutex_lock(&parking_lock);
01810    pu = parkinglot;
01811    while(pu) {
01812       if (pu->parkingnum == park) {
01813          if (pl)
01814             pl->next = pu->next;
01815          else
01816             parkinglot = pu->next;
01817          break;
01818       }
01819       pl = pu;
01820       pu = pu->next;
01821    }
01822    ast_mutex_unlock(&parking_lock);
01823    if (pu) {
01824       peer = pu->chan;
01825       con = ast_context_find(parking_con);
01826       if (con) {
01827          snprintf(exten, sizeof(exten), "%d", pu->parkingnum);
01828          if (ast_context_remove_extension2(con, exten, 1, NULL))
01829             ast_log(LOG_WARNING, "Whoa, failed to remove the extension!\n");
01830          else
01831             notify_metermaids(exten, parking_con);
01832       } else
01833          ast_log(LOG_WARNING, "Whoa, no parking context?\n");
01834 
01835       manager_event(EVENT_FLAG_CALL, "UnParkedCall",
01836          "Exten: %d\r\n"
01837          "Channel: %s\r\n"
01838          "From: %s\r\n"
01839          "CallerID: %s\r\n"
01840          "CallerIDName: %s\r\n"
01841          ,pu->parkingnum, pu->chan->name, chan->name
01842          ,(pu->chan->cid.cid_num ? pu->chan->cid.cid_num : "<unknown>")
01843          ,(pu->chan->cid.cid_name ? pu->chan->cid.cid_name : "<unknown>")
01844          );
01845 
01846       free(pu);
01847    }
01848    /* JK02: it helps to answer the channel if not already up */
01849    if (chan->_state != AST_STATE_UP) {
01850       ast_answer(chan);
01851    }
01852 
01853    if (peer) {
01854       /* Play a courtesy beep in the calling channel to prefix the bridge connecting */   
01855       if (!ast_strlen_zero(courtesytone)) {
01856          if (!ast_streamfile(chan, courtesytone, chan->language)) {
01857             if (ast_waitstream(chan, "") < 0) {
01858                ast_log(LOG_WARNING, "Failed to play courtesy tone!\n");
01859                ast_hangup(peer);
01860                return -1;
01861             }
01862          }
01863       }
01864  
01865       ast_moh_stop(peer);
01866       ast_indicate(peer, AST_CONTROL_UNHOLD);
01867       res = ast_channel_make_compatible(chan, peer);
01868       if (res < 0) {
01869          ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name);
01870          ast_hangup(peer);
01871          return -1;
01872       }
01873       /* This runs sorta backwards, since we give the incoming channel control, as if it
01874          were the person called. */
01875       if (option_verbose > 2) 
01876          ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park);
01877 
01878       memset(&config, 0, sizeof(struct ast_bridge_config));
01879       ast_set_flag(&(config.features_callee), AST_FEATURE_REDIRECT);
01880       ast_set_flag(&(config.features_caller), AST_FEATURE_REDIRECT);
01881       config.timelimit = 0;
01882       config.play_warning = 0;
01883       config.warning_freq = 0;
01884       config.warning_sound=NULL;
01885       res = ast_bridge_call(chan, peer, &config);
01886 
01887       /* Simulate the PBX hanging up */
01888       if (res != AST_PBX_NO_HANGUP_PEER)
01889          ast_hangup(peer);
01890       return res;
01891    } else {
01892       /* XXX Play a message XXX */
01893       dres = ast_streamfile(chan, "pbx-invalidpark", chan->language);
01894       if (!dres)
01895             dres = ast_waitstream(chan, "");
01896       else {
01897          ast_log(LOG_WARNING, "ast_streamfile of %s failed on %s\n", "pbx-invalidpark", chan->name);
01898          dres = 0;
01899       }
01900       if (option_verbose > 2) 
01901          ast_verbose(VERBOSE_PREFIX_3 "Channel %s tried to talk to nonexistent parked call %d\n", chan->name, park);
01902       res = -1;
01903    }
01904    LOCAL_USER_REMOVE(u);
01905    return res;
01906 }
01907 
01908 static int handle_showfeatures(int fd, int argc, char *argv[])
01909 {
01910    int i;
01911    int fcount;
01912    struct ast_call_feature *feature;
01913    char format[] = "%-25s %-7s %-7s\n";
01914 
01915    ast_cli(fd, format, "Builtin Feature", "Default", "Current");
01916    ast_cli(fd, format, "---------------", "-------", "-------");
01917 
01918    ast_cli(fd, format, "Pickup", "*8", ast_pickup_ext());      /* default hardcoded above, so we'll hardcode it here */
01919 
01920    fcount = sizeof(builtin_features) / sizeof(builtin_features[0]);
01921 
01922    for (i = 0; i < fcount; i++)
01923    {
01924       ast_cli(fd, format, builtin_features[i].fname, builtin_features[i].default_exten, builtin_features[i].exten);
01925    }
01926    ast_cli(fd, "\n");
01927    ast_cli(fd, format, "Dynamic Feature", "Default", "Current");
01928    ast_cli(fd, format, "---------------", "-------", "-------");
01929    if (AST_LIST_EMPTY(&feature_list)) {
01930       ast_cli(fd, "(none)\n");
01931    }
01932    else {
01933       AST_LIST_LOCK(&feature_list);
01934       AST_LIST_TRAVERSE(&feature_list, feature, feature_entry) {
01935          ast_cli(fd, format, feature->sname, "no def", feature->exten); 
01936       }
01937       AST_LIST_UNLOCK(&feature_list);
01938    }
01939    ast_cli(fd, "\nCall parking\n");
01940    ast_cli(fd, "------------\n");
01941    ast_cli(fd,"%-20s:   %s\n", "Parking extension", parking_ext);
01942    ast_cli(fd,"%-20s:   %s\n", "Parking context", parking_con);
01943    ast_cli(fd,"%-20s:   %d-%d\n", "Parked call extensions", parking_start, parking_stop);
01944    ast_cli(fd,"\n");
01945    
01946    return RESULT_SUCCESS;
01947 }
01948 
01949 static char showfeatures_help[] =
01950 "Usage: show features\n"
01951 "       Lists currently configured features.\n";
01952 
01953 static struct ast_cli_entry showfeatures =
01954 { { "show", "features", NULL }, handle_showfeatures, "Lists configured features", showfeatures_help };
01955 
01956 static int handle_parkedcalls(int fd, int argc, char *argv[])
01957 {
01958    struct parkeduser *cur;
01959    int numparked = 0;
01960 
01961    ast_cli(fd, "%4s %25s (%-15s %-12s %-4s) %-6s \n", "Num", "Channel"
01962       , "Context", "Extension", "Pri", "Timeout");
01963 
01964    ast_mutex_lock(&parking_lock);
01965 
01966    cur = parkinglot;
01967    while(cur) {
01968       ast_cli(fd, "%4d %25s (%-15s %-12s %-4d) %6lds\n"
01969          ,cur->parkingnum, cur->chan->name, cur->context, cur->exten
01970          ,cur->priority, cur->start.tv_sec + (cur->parkingtime/1000) - time(NULL));
01971 
01972       cur = cur->next;
01973       numparked++;
01974    }
01975    ast_cli(fd, "%d parked call%s.\n", numparked, (numparked != 1) ? "s" : "");
01976 
01977    ast_mutex_unlock(&parking_lock);
01978 
01979    return RESULT_SUCCESS;
01980 }
01981 
01982 static char showparked_help[] =
01983 "Usage: show parkedcalls\n"
01984 "       Lists currently parked calls.\n";
01985 
01986 static struct ast_cli_entry showparked =
01987 { { "show", "parkedcalls", NULL }, handle_parkedcalls, "Lists parked calls", showparked_help };
01988 
01989 /* Dump lot status */
01990 static int manager_parking_status( struct mansession *s, struct message *m )
01991 {
01992    struct parkeduser *cur;
01993    char *id = astman_get_header(m,"ActionID");
01994    char idText[256] = "";
01995 
01996    if (!ast_strlen_zero(id))
01997       snprintf(idText,256,"ActionID: %s\r\n",id);
01998 
01999    astman_send_ack(s, m, "Parked calls will follow");
02000 
02001         ast_mutex_lock(&parking_lock);
02002 
02003         cur=parkinglot;
02004         while(cur) {
02005          ast_cli(s->fd, "Event: ParkedCall\r\n"
02006          "Exten: %d\r\n"
02007          "Channel: %s\r\n"
02008          "Timeout: %ld\r\n"
02009          "CallerID: %s\r\n"
02010          "CallerIDName: %s\r\n"
02011          "%s"
02012          "\r\n"
02013                         ,cur->parkingnum, cur->chan->name
02014                         ,(long)cur->start.tv_sec + (long)(cur->parkingtime/1000) - (long)time(NULL)
02015          ,(cur->chan->cid.cid_num ? cur->chan->cid.cid_num : "")
02016          ,(cur->chan->cid.cid_name ? cur->chan->cid.cid_name : "")
02017          ,idText);
02018 
02019             cur = cur->next;
02020         }
02021 
02022    ast_cli(s->fd,
02023    "Event: ParkedCallsComplete\r\n"
02024    "%s"
02025    "\r\n",idText);
02026 
02027         ast_mutex_unlock(&parking_lock);
02028 
02029         return RESULT_SUCCESS;
02030 }
02031 
02032 
02033 int ast_pickup_call(struct ast_channel *chan)
02034 {
02035    struct ast_channel *cur = NULL;
02036    int res = -1;
02037 
02038    while ( (cur = ast_channel_walk_locked(cur)) != NULL) {
02039       if (!cur->pbx && 
02040          (cur != chan) &&
02041          (chan->pickupgroup & cur->callgroup) &&
02042          ((cur->_state == AST_STATE_RINGING) ||
02043           (cur->_state == AST_STATE_RING))) {
02044             break;
02045       }
02046       ast_mutex_unlock(&cur->lock);
02047    }
02048    if (cur) {
02049       if (option_debug)
02050          ast_log(LOG_DEBUG, "Call pickup on chan '%s' by '%s'\n",cur->name, chan->name);
02051       res = ast_answer(chan);
02052       if (res)
02053          ast_log(LOG_WARNING, "Unable to answer '%s'\n", chan->name);
02054       res = ast_queue_control(chan, AST_CONTROL_ANSWER);
02055       if (res)
02056          ast_log(LOG_WARNING, "Unable to queue answer on '%s'\n", chan->name);
02057       res = ast_channel_masquerade(cur, chan);
02058       if (res)
02059          ast_log(LOG_WARNING, "Unable to masquerade '%s' into '%s'\n", chan->name, cur->name);     /* Done */
02060       ast_mutex_unlock(&cur->lock);
02061    } else   {
02062       if (option_debug)
02063          ast_log(LOG_DEBUG, "No call pickup possible...\n");
02064    }
02065    return res;
02066 }
02067 
02068 static void park_add_hints(char *context, int start, int stop)
02069 {
02070    int numext;
02071    char device[AST_MAX_EXTENSION];
02072    char exten[10];
02073    for (numext = start; numext <= stop; numext++) {
02074       snprintf(exten, sizeof(exten), "%d", numext);
02075       snprintf(device, sizeof(device), "Local/%s@%s", exten, context);
02076       ast_add_extension(context, 1, exten, PRIORITY_HINT, NULL, NULL, device, NULL, NULL, registrar);
02077    }
02078 }
02079 
02080 static int load_config(void) 
02081 {
02082    int start = 0, end = 0;
02083    int res;
02084    struct ast_context *con = NULL;
02085    struct ast_config *cfg = NULL;
02086    struct ast_variable *var = NULL;
02087    char old_parking_ext[AST_MAX_EXTENSION];
02088    char old_parking_con[AST_MAX_EXTENSION] = "";
02089 
02090    if (!ast_strlen_zero(parking_con)) {
02091       strcpy(old_parking_ext, parking_ext);
02092       strcpy(old_parking_con, parking_con);
02093    } 
02094 
02095    /* Reset to defaults */
02096    strcpy(parking_con, "parkedcalls");
02097    strcpy(parking_con_dial, "park-dial");
02098    strcpy(parking_ext, "700");
02099    strcpy(pickup_ext, "*8");
02100    courtesytone[0] = '\0';
02101    strcpy(xfersound, "beep");
02102    strcpy(xferfailsound, "pbx-invalid");
02103    parking_start = 701;
02104    parking_stop = 750;
02105    parkfindnext = 0;
02106    parkaddhints = 0;
02107    adsipark = 0;
02108 
02109    transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
02110    featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
02111 
02112    cfg = ast_config_load("features.conf");
02113    if (!cfg) {
02114       cfg = ast_config_load("parking.conf");
02115       if (cfg)
02116          ast_log(LOG_NOTICE, "parking.conf is deprecated in favor of 'features.conf'.  Please rename it.\n");
02117    }
02118    if (cfg) {
02119       var = ast_variable_browse(cfg, "general");
02120       while(var) {
02121          if (!strcasecmp(var->name, "parkext")) {
02122             ast_copy_string(parking_ext, var->value, sizeof(parking_ext));
02123          } else if (!strcasecmp(var->name, "context")) {
02124             ast_copy_string(parking_con, var->value, sizeof(parking_con));
02125          } else if (!strcasecmp(var->name, "parkingtime")) {
02126             if ((sscanf(var->value, "%d", &parkingtime) != 1) || (parkingtime < 1)) {
02127                ast_log(LOG_WARNING, "%s is not a valid parkingtime\n", var->value);
02128                parkingtime = DEFAULT_PARK_TIME;
02129             } else
02130                parkingtime = parkingtime * 1000;
02131          } else if (!strcasecmp(var->name, "parkpos")) {
02132             if (sscanf(var->value, "%d-%d", &start, &end) != 2) {
02133                ast_log(LOG_WARNING, "Format for parking positions is a-b, where a and b are numbers at line %d of parking.conf\n", var->lineno);
02134             } else {
02135                parking_start = start;
02136                parking_stop = end;
02137             }
02138          } else if (!strcasecmp(var->name, "findslot")) {
02139             parkfindnext = (!strcasecmp(var->value, "next"));
02140          } else if (!strcasecmp(var->name, "parkhints")) {
02141             parkaddhints = ast_true(var->value);
02142          } else if (!strcasecmp(var->name, "adsipark")) {
02143             adsipark = ast_true(var->value);
02144          } else if (!strcasecmp(var->name, "transferdigittimeout")) {
02145             if ((sscanf(var->value, "%d", &transferdigittimeout) != 1) || (transferdigittimeout < 1)) {
02146                ast_log(LOG_WARNING, "%s is not a valid transferdigittimeout\n", var->value);
02147                transferdigittimeout = DEFAULT_TRANSFER_DIGIT_TIMEOUT;
02148             } else
02149                transferdigittimeout = transferdigittimeout * 1000;
02150          } else if (!strcasecmp(var->name, "featuredigittimeout")) {
02151             if ((sscanf(var->value, "%d", &featuredigittimeout) != 1) || (featuredigittimeout < 1)) {
02152                ast_log(LOG_WARNING, "%s is not a valid featuredigittimeout\n", var->value);
02153                featuredigittimeout = DEFAULT_FEATURE_DIGIT_TIMEOUT;
02154             }
02155          } else if (!strcasecmp(var->name, "courtesytone")) {
02156             ast_copy_string(courtesytone, var->value, sizeof(courtesytone));
02157          } else if (!strcasecmp(var->name, "xfersound")) {
02158             ast_copy_string(xfersound, var->value, sizeof(xfersound));
02159          } else if (!strcasecmp(var->name, "xferfailsound")) {
02160             ast_copy_string(xferfailsound, var->value, sizeof(xferfailsound));
02161          } else if (!strcasecmp(var->name, "pickupexten")) {
02162             ast_copy_string(pickup_ext, var->value, sizeof(pickup_ext));
02163          }
02164          var = var->next;
02165       }
02166 
02167       unmap_features();
02168       var = ast_variable_browse(cfg, "featuremap");
02169       while(var) {
02170          if (remap_feature(var->name, var->value))
02171             ast_log(LOG_NOTICE, "Unknown feature '%s'\n", var->name);
02172          var = var->next;
02173       }
02174 
02175       /* Map a key combination to an application*/
02176       ast_unregister_features();
02177       var = ast_variable_browse(cfg, "applicationmap");
02178       while(var) {
02179          char *tmp_val=strdup(var->value);
02180          char *exten, *party=NULL, *app=NULL, *app_args=NULL; 
02181 
02182          if (!tmp_val) { 
02183             ast_log(LOG_ERROR, "res_features: strdup failed");
02184             continue;
02185          }
02186          
02187 
02188          exten=strsep(&tmp_val,",");
02189          if (exten) party=strsep(&tmp_val,",");
02190          if (party) app=strsep(&tmp_val,",");
02191 
02192          if (app) app_args=strsep(&tmp_val,",");
02193 
02194          if (!(app && strlen(app)) || !(exten && strlen(exten)) || !(party && strlen(party)) || !(var->name && strlen(var->name))) {
02195             ast_log(LOG_NOTICE, "Please check the feature Mapping Syntax, either extension, name, or app aren't provided %s %s %s %s\n",app,exten,party,var->name);
02196             free(tmp_val);
02197             var = var->next;
02198             continue;
02199          }
02200 
02201          {
02202             struct ast_call_feature *feature=find_feature(var->name);
02203             int mallocd=0;
02204             
02205             if (!feature) {
02206                feature=malloc(sizeof(struct ast_call_feature));
02207                mallocd=1;
02208             }
02209             if (!feature) {
02210                ast_log(LOG_NOTICE, "Malloc failed at feature mapping\n");
02211                free(tmp_val);
02212                var = var->next;
02213                continue;
02214             }
02215 
02216             memset(feature,0,sizeof(struct ast_call_feature));
02217             ast_copy_string(feature->sname,var->name,FEATURE_SNAME_LEN);
02218             ast_copy_string(feature->app,app,FEATURE_APP_LEN);
02219             ast_copy_string(feature->exten, exten,FEATURE_EXTEN_LEN);
02220             free(tmp_val);
02221             
02222             if (app_args) 
02223                ast_copy_string(feature->app_args,app_args,FEATURE_APP_ARGS_LEN);
02224             
02225             ast_copy_string(feature->exten, exten,sizeof(feature->exten));
02226             feature->operation=feature_exec_app;
02227             ast_set_flag(feature,AST_FEATURE_FLAG_NEEDSDTMF);
02228             
02229             if (!strcasecmp(party,"caller"))
02230                ast_set_flag(feature,AST_FEATURE_FLAG_CALLER);
02231             else if (!strcasecmp(party, "callee"))
02232                ast_set_flag(feature,AST_FEATURE_FLAG_CALLEE);
02233             else {
02234                ast_log(LOG_NOTICE, "Invalid party specification for feature '%s', must be caller, or callee\n", var->name);
02235                var = var->next;
02236                continue;
02237             }
02238 
02239             ast_register_feature(feature);
02240             
02241             if (option_verbose >=1) ast_verbose(VERBOSE_PREFIX_2 "Mapping Feature '%s' to app '%s' with code '%s'\n", var->name, app, exten);  
02242          }
02243          var = var->next;
02244       }   
02245    }
02246    ast_config_destroy(cfg);
02247 
02248    /* Remove the old parking extension */
02249    if (!ast_strlen_zero(old_parking_con) && (con = ast_context_find(old_parking_con))) {
02250       if(!ast_context_remove_extension2(con, old_parking_ext, 1, registrar))
02251          notify_metermaids(old_parking_ext, old_parking_con);
02252       ast_log(LOG_DEBUG, "Removed old parking extension %s@%s\n", old_parking_ext, old_parking_con);
02253    }
02254    
02255    if (!(con = ast_context_find(parking_con))) {
02256       if (!(con = ast_context_create(NULL, parking_con, registrar))) {
02257          ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con);
02258          return -1;
02259       }
02260    }
02261    res = ast_add_extension2(con, 1, ast_parking_ext(), 1, NULL, NULL, parkcall, strdup(""), FREE, registrar);
02262    if (parkaddhints)
02263       park_add_hints(parking_con, parking_start, parking_stop);
02264    if (!res)
02265       notify_metermaids(ast_parking_ext(), parking_con);
02266    return res;
02267 }
02268 
02269 int reload(void) {
02270    return load_config();
02271 }
02272 
02273 int load_module(void)
02274 {
02275    int res;
02276    
02277    memset(parking_ext, 0, sizeof(parking_ext));
02278    memset(parking_con, 0, sizeof(parking_con));
02279 
02280    if ((res = load_config()))
02281       return res;
02282    ast_cli_register(&showparked);
02283    ast_cli_register(&showfeatures);
02284    ast_pthread_create(&parking_thread, NULL, do_parking_thread, NULL);
02285    res = ast_register_application(parkedcall, park_exec, synopsis, descrip);
02286    if (!res)
02287       res = ast_register_application(parkcall, park_call_exec, synopsis2, descrip2);
02288    if (!res) {
02289       ast_manager_register("ParkedCalls", 0, manager_parking_status, "List parked calls" );
02290    }
02291    return res;
02292 }
02293 
02294 
02295 int unload_module(void)
02296 {
02297    STANDARD_HANGUP_LOCALUSERS;
02298 
02299    ast_manager_unregister("ParkedCalls");
02300    ast_cli_unregister(&showfeatures);
02301    ast_cli_unregister(&showparked);
02302    ast_unregister_application(parkcall);
02303    return ast_unregister_application(parkedcall);
02304 }
02305 
02306 char *description(void)
02307 {
02308    return "Call Features Resource";
02309 }
02310 
02311 int usecount(void)
02312 {
02313    /* Never allow parking to be unloaded because it will
02314       unresolve needed symbols in the dialer */
02315 #if 0
02316    int res;
02317    STANDARD_USECOUNT(res);
02318    return res;
02319 #else
02320    return 1;
02321 #endif
02322 }
02323 
02324 char *key()
02325 {
02326    return ASTERISK_GPL_KEY;
02327 }

Generated on Sat Sep 16 05:47:47 2006 for Asterisk - the Open Source PBX by  doxygen 1.4.7