/* * Asterisk -- A telephony toolkit for Linux. * * Routines implementing call parking * * Copyright (C) 1999, Mark Spencer * * Mark Spencer * * This program is free software, distributed under the terms of * the GNU General Public License */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static char *parkedcall = "ParkedCall"; /* No more than 45 seconds parked before you do something with them */ static int parkingtime = 45000; /* Context for which parking is made accessible */ static char parking_con[AST_MAX_EXTENSION] = "parkedcalls"; /* Extension you type to park the call */ static char parking_ext[AST_MAX_EXTENSION] = "700"; /* First available extension for parking */ static int parking_start = 701; /* Last available extension for parking */ static int parking_stop = 750; /* Registrar for operations */ static char *registrar = "res_parking"; static char *synopsis = "Answer a parked call"; static char *descrip = "ParkedCall(exten):" "Used to connect to a parked call. This Application is always\n" "registered internally and does not need to be explicitly added\n" "into the dialplan, although you should include the 'parkedcalls'\n" "context.\n"; struct parkeduser { struct ast_channel *chan; struct timeval start; int parkingnum; /* Where to go if our parking time expires */ char context[AST_MAX_EXTENSION]; char exten[AST_MAX_EXTENSION]; int priority; struct parkeduser *next; }; static struct parkeduser *parkinglot; static pthread_mutex_t parking_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_t parking_thread; STANDARD_LOCAL_USER; LOCAL_USER_DECL; char *ast_parking_ext(void) { return parking_ext; } int ast_park_call(struct ast_channel *chan, struct ast_channel *peer) { /* We put the user in the parking list, then wake up the parking thread to be sure it looks after these channels too */ struct parkeduser *pu, *cur; int x; pu = malloc(sizeof(struct parkeduser)); if (pu) { ast_pthread_mutex_lock(&parking_lock); for (x=parking_start;x<=parking_stop;x++) { cur = parkinglot; while(cur) { if (cur->parkingnum == x) break; cur = cur->next; } if (!cur) break; } if (x <= parking_stop) { pu->chan = chan; gettimeofday(&pu->start, NULL); pu->parkingnum = x; /* Remember what had been dialed, so that if the parking expires, we try to come back to the same place */ strncpy(pu->context, chan->context, sizeof(pu->context)-1); strncpy(pu->exten, chan->exten, sizeof(pu->exten)-1); pu->priority = chan->priority; pu->next = parkinglot; parkinglot = pu; ast_pthread_mutex_unlock(&parking_lock); /* Wake up the (presumably select()ing) thread */ pthread_kill(parking_thread, SIGURG); if (option_verbose > 1) ast_verbose(VERBOSE_PREFIX_2 "Parked %s on %d\n", pu->chan->name, pu->parkingnum); ast_say_digits(peer, pu->parkingnum, "", peer->language); return 0; } else { ast_log(LOG_WARNING, "No more parking spaces\n"); free(pu); ast_pthread_mutex_unlock(&parking_lock); return -1; } } else { ast_log(LOG_WARNING, "Out of memory\n"); return -1; } return 0; } int ast_masq_park_call(struct ast_channel *rchan, struct ast_channel *peer) { struct ast_channel *chan; struct ast_frame *f; /* Make a new, fake channel that we'll use to masquerade in the real one */ chan = ast_channel_alloc(); if (chan) { /* Let us keep track of the channel name */ snprintf(chan->name, sizeof (chan->name), "Parked/%s",rchan->name); /* Make formats okay */ chan->readformat = rchan->readformat; chan->writeformat = rchan->writeformat; ast_channel_masquerade(chan, rchan); /* Setup the extensions and such */ strncpy(chan->context, rchan->context, sizeof(chan->context) - 1); strncpy(chan->exten, rchan->exten, sizeof(chan->exten) - 1); chan->priority = rchan->priority; /* Make the masq execute */ f = ast_read(chan); if (f) ast_frfree(f); ast_park_call(chan, peer); } else { ast_log(LOG_WARNING, "Unable to create parked channel\n"); return -1; } return 0; } int ast_bridge_call(struct ast_channel *chan, struct ast_channel *peer, int allowredirect) { /* Copy voice back and forth between the two channels. Give the peer the ability to transfer calls with '#state != AST_STATE_UP) { if (ast_answer(chan)) return -1; } peer->appl = "Bridged Call"; peer->data = chan->name; for (;;) { res = ast_channel_bridge(chan, peer, allowredirect ? AST_BRIDGE_DTMF_CHANNEL_1 : 0, &f, &who); if (res < 0) { ast_log(LOG_WARNING, "Bridge failed on channels %s and %s\n", chan->name, peer->name); return -1; } if (!f || ((f->frametype == AST_FRAME_CONTROL) && ((f->subclass == AST_CONTROL_HANGUP) || (f->subclass == AST_CONTROL_BUSY) || (f->subclass == AST_CONTROL_CONGESTION)))) { res = -1; break; } if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_RINGING)) { if (who == chan) ast_indicate(peer, AST_CONTROL_RINGING); else ast_indicate(chan, AST_CONTROL_RINGING); } if ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_OPTION)) { aoh = f->data; /* Forward option Requests */ if (aoh && (aoh->flag == AST_OPTION_FLAG_REQUEST)) { if (who == chan) ast_channel_setoption(peer, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0); else ast_channel_setoption(chan, ntohs(aoh->option), aoh->data, f->datalen - sizeof(struct ast_option_header), 0); } } if ((f->frametype == AST_FRAME_DTMF) && (who == peer) && allowredirect && (f->subclass == '#')) { memset(newext, 0, sizeof(newext)); ptr = newext; /* Transfer */ if ((res=ast_streamfile(peer, "pbx-transfer", chan->language))) break; if ((res=ast_waitstream(peer, AST_DIGIT_ANY)) < 0) break; ast_stopstream(peer); if (res > 0) { /* If they've typed a digit already, handle it */ newext[0] = res; ptr++; len --; } res = 0; while(strlen(newext) < sizeof(newext - 1)) { res = ast_waitfordigit(peer, 3000); if (res < 1) break; *(ptr++) = res; if (!ast_canmatch_extension(peer, peer->context, newext, 1, peer->callerid) || ast_exists_extension(peer, peer->context, newext, 1, peer->callerid)) { res = 0; break; } } if (res) break; if (!strcmp(newext, ast_parking_ext())) { if (!ast_park_call(chan, peer)) { /* We return non-zero, but tell the PBX not to hang the channel when the thread dies -- We have to be careful now though. We are responsible for hanging up the channel, else it will never be hung up! */ res=AST_PBX_KEEPALIVE; break; } else { ast_log(LOG_WARNING, "Unable to park call %s\n", chan->name); } /* XXX Maybe we should have another message here instead of invalid extension XXX */ } else if (ast_exists_extension(chan, peer->context, newext, 1, peer->callerid)) { /* Set the channel's new extension, since it exists, using peer context */ strncpy(chan->exten, newext, sizeof(chan->exten)-1); strncpy(chan->context, peer->context, sizeof(chan->context)-1); chan->priority = 0; ast_frfree(f); if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Transferring %s to '%s' (context %s) priority 1\n", chan->name, chan->exten, chan->context); res=0; break; } else { if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Unable to find extension '%s' in context %s\n", newext, peer->context); } res = ast_streamfile(peer, "pbx-invalid", chan->language); if (res) break; res = ast_waitstream(peer, AST_DIGIT_ANY); ast_stopstream(peer); res = 0; } else { if (f && (f->frametype == AST_FRAME_DTMF)) { if (who == peer) ast_write(chan, f); else ast_write(peer, f); } #if 1 ast_log(LOG_DEBUG, "Read from %s (%d,%d)\n", who->name, f->frametype, f->subclass); #endif } if (f) ast_frfree(f); } return res; } static void *do_parking_thread(void *ignore) { int ms, tms, max; struct parkeduser *pu, *pl, *pt = NULL; struct timeval tv; struct ast_frame *f; int x; fd_set rfds, efds; fd_set nrfds, nefds; FD_ZERO(&rfds); FD_ZERO(&efds); for (;;) { ms = -1; max = -1; ast_pthread_mutex_lock(&parking_lock); pl = NULL; pu = parkinglot; gettimeofday(&tv, NULL); FD_ZERO(&nrfds); FD_ZERO(&nefds); while(pu) { tms = (tv.tv_sec - pu->start.tv_sec) * 1000 + (tv.tv_usec - pu->start.tv_usec) / 1000; if (tms > parkingtime) { /* They've been waiting too long, send them back to where they came. Theoretically they should have their original extensions and such, but we copy to be on the safe side */ strncpy(pu->chan->exten, pu->exten, sizeof(pu->chan->exten)-1); strncpy(pu->chan->context, pu->context, sizeof(pu->chan->context)-1); pu->chan->priority = pu->priority; /* Start up the PBX, or hang them up */ if (ast_pbx_start(pu->chan)) { ast_log(LOG_WARNING, "Unable to restart the PBX for user on '%s', hanging them up...\n", pu->chan->name); ast_hangup(pu->chan); } /* And take them out of the parking lot */ if (pl) pl->next = pu->next; else parkinglot = pu->next; pt = pu; pu = pu->next; free(pt); } else { for (x=0;xchan->fds[x] > -1) && (FD_ISSET(pu->chan->fds[x], &rfds) || FD_ISSET(pu->chan->fds[x], &efds))) { if (FD_ISSET(pu->chan->fds[x], &efds)) pu->chan->exception = 1; pu->chan->fdno = x; /* See if they need servicing */ f = ast_read(pu->chan); if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) { /* There's a problem, hang them up*/ if (option_verbose > 1) ast_verbose(VERBOSE_PREFIX_2 "%s got tired of being parked\n", pu->chan->name); ast_hangup(pu->chan); /* And take them out of the parking lot */ if (pl) pl->next = pu->next; else parkinglot = pu->next; pt = pu; pu = pu->next; free(pt); break; } else { /* XXX Maybe we could do something with packets, like dial "0" for operator or something XXX */ ast_frfree(f); goto std; /* XXX Ick: jumping into an else statement??? XXX */ } } } if (x >= AST_MAX_FDS) { std: for (x=0;xchan->fds[x] > -1) { FD_SET(pu->chan->fds[x], &nrfds); FD_SET(pu->chan->fds[x], &nefds); if (pu->chan->fds[x] > max) max = pu->chan->fds[x]; } } /* Keep track of our longest wait */ if ((tms < ms) || (ms < 0)) ms = tms; pl = pu; pu = pu->next; } } } ast_pthread_mutex_unlock(&parking_lock); rfds = nrfds; efds = nefds; tv.tv_sec = ms / 1000; tv.tv_usec = (ms % 1000) * 1000; /* Wait for something to happen */ select(max + 1, &rfds, NULL, &efds, (ms > -1) ? &tv : NULL); pthread_testcancel(); } return NULL; /* Never reached */ } static int park_exec(struct ast_channel *chan, void *data) { int res=0; struct localuser *u; struct ast_channel *peer=NULL; struct parkeduser *pu, *pl=NULL; int park; if (!data) { ast_log(LOG_WARNING, "Park requires an argument (extension number)\n"); return -1; } LOCAL_USER_ADD(u); park = atoi((char *)data); ast_pthread_mutex_lock(&parking_lock); pu = parkinglot; while(pu) { if (pu->parkingnum == park) { if (pl) pl->next = pu->next; else parkinglot = pu->next; break; } pu = pu->next; } ast_pthread_mutex_unlock(&parking_lock); if (pu) { peer = pu->chan; free(pu); } if (peer) { res = ast_channel_make_compatible(chan, peer); if (res < 0) { ast_log(LOG_WARNING, "Could not make channels %s and %s compatible for bridge\n", chan->name, peer->name); ast_hangup(peer); return -1; } /* This runs sorta backwards, since we give the incoming channel control, as if it were the person called. */ if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Channel %s connected to parked call %d\n", chan->name, park); res = ast_bridge_call(peer, chan, 1); /* Simulate the PBX hanging up */ if (res != AST_PBX_KEEPALIVE) ast_hangup(peer); return -1; } else { /* XXX Play a message XXX */ if (option_verbose > 2) ast_verbose(VERBOSE_PREFIX_3 "Channel %s tried to talk to non-existant parked call %d\n", chan->name, park); res = -1; } LOCAL_USER_REMOVE(u); return res; } int load_module(void) { int res; int x; struct ast_context *con; char exten[AST_MAX_EXTENSION]; con = ast_context_find(parking_con); if (!con) { con = ast_context_create(parking_con, registrar); if (!con) { ast_log(LOG_ERROR, "Parking context '%s' does not exist and unable to create\n", parking_con); return -1; } } for(x=parking_start; x<=parking_stop;x++) { snprintf(exten, sizeof(exten), "%d", x); ast_add_extension2(con, 1, exten, 1, NULL, parkedcall, strdup(exten), free, registrar); } pthread_create(&parking_thread, NULL, do_parking_thread, NULL); res = ast_register_application(parkedcall, park_exec, synopsis, descrip); return res; } int unload_module(void) { STANDARD_HANGUP_LOCALUSERS; return ast_unregister_application(parkedcall); } char *description(void) { return "Call Parking Resource"; } int usecount(void) { /* Never allow parking to be unloaded because it will unresolve needed symbols in the dialer */ #if 0 int res; STANDARD_USECOUNT(res); return res; #else return 1; #endif } char *key() { return ASTERISK_GPL_KEY; }