Remove access to free'd memory fro dude's code
[asterisk/asterisk.git] / apps / app_dial.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Trivial application to dial a channel and send an URL on answer
5  * 
6  * Copyright (C) 1999, Mark Spencer
7  *
8  * Mark Spencer <markster@linux-support.net>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <asterisk/lock.h>
15 #include <asterisk/file.h>
16 #include <asterisk/logger.h>
17 #include <asterisk/channel.h>
18 #include <asterisk/pbx.h>
19 #include <asterisk/options.h>
20 #include <asterisk/module.h>
21 #include <asterisk/translate.h>
22 #include <asterisk/say.h>
23 #include <asterisk/parking.h>
24 #include <asterisk/musiconhold.h>
25 #include <asterisk/callerid.h>
26 #include <stdlib.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32 #include <sys/time.h>
33 #include <sys/signal.h>
34 #include <netinet/in.h>
35
36 #include <pthread.h>
37
38 static char *tdesc = "Dialing Application";
39
40 static char *app = "Dial";
41
42 static char *synopsis = "Place an call and connect to the current channel";
43
44 static char *descrip =
45 "  Dial(Technology/resource[&Technology2/resource2...][|timeout][|options][|URL]):\n"
46 "Requests  one  or more channels and places specified outgoing calls on them.\n"
47 "As soon as a  channel  answers, the  Dial  app  will  answer the originating\n"
48 "channel (if it needs to be answered) and will bridge a call with the channel\n"
49 "which first answered. All other calls placed by the Dial app will be hunp up\n"
50 "If a timeout is not specified, the Dial  application  will wait indefinitely\n"
51 "until either one of the  called channels  answers, the user hangs up, or all\n"
52 "channels return busy or  error. In general,  the dialler will return 0 if it\n"
53 "was  unable  to  place  the  call, or the timeout expired.  However, if  all\n"
54 "channels were busy, and there exists an extension with priority n+101 (where\n"
55 "n is the priority of  the  dialler  instance), then  it  will  be  the  next\n"
56 "executed extension (this allows you to setup different behavior on busy from\n"
57 "no-answer).\n"
58 "  This application returns -1 if the originating channel hangs up, or if the\n"
59 "call is bridged and  either of the parties in the bridge terminate the call.\n"
60 "The option string may contain zero or more of the following characters:\n"
61 "      't' -- allow the called user transfer the calling user\n"
62 "      'T' -- to allow the calling user to transfer the call.\n"
63 "      'r' -- indicate ringing to the calling party, pass no audio until answered.\n"
64 "      'm' -- provide hold music to the calling party until answered.\n"
65 "      'd' -- data-quality (modem) call (minimum delay).\n"
66 "      'c' -- clear-channel data call (PRI-PRI only).\n"
67 "      'H' -- allow caller to hang up by hitting *.\n"
68 "      'C' -- reset call detail record for this call.\n"
69 "      'P[(x)]' -- privacy mode, using 'x' as database if provided.\n"
70 "  In addition to transferring the call, a call may be parked and then picked\n"
71 "up by another user.\n"
72 "  The optionnal URL will be sent to the called party if the channel supports\n"
73 "it.\n";
74
75 /* We define a customer "local user" structure because we
76    use it not only for keeping track of what is in use but
77    also for keeping track of who we're dialing. */
78
79 struct localuser {
80         struct ast_channel *chan;
81         int stillgoing;
82         int allowredirect;
83         int ringbackonly;
84         int musiconhold;
85         int dataquality;
86         int allowdisconnect;
87         struct localuser *next;
88 };
89
90 LOCAL_USER_DECL;
91
92 static void hanguptree(struct localuser *outgoing, struct ast_channel *exception)
93 {
94         /* Hang up a tree of stuff */
95         struct localuser *oo;
96         while(outgoing) {
97                 /* Hangup any existing lines we have open */
98                 if (outgoing->chan != exception)
99                         ast_hangup(outgoing->chan);
100                 oo = outgoing;
101                 outgoing=outgoing->next;
102                 free(oo);
103         }
104 }
105
106 #define MAX 256
107
108 static struct ast_channel *wait_for_answer(struct ast_channel *in, struct localuser *outgoing, int *to, int *allowredir, int *allowdisconnect)
109 {
110         struct localuser *o;
111         int found;
112         int numlines;
113         int sentringing = 0;
114         int numbusies = 0;
115         int orig = *to;
116         struct ast_frame *f;
117         struct ast_channel *peer = NULL;
118         struct ast_channel *watchers[MAX];
119         int pos;
120         int single;
121         int moh=0;
122         int ringind=0;
123         struct ast_channel *winner;
124         
125         single = (outgoing && !outgoing->next && !outgoing->musiconhold && !outgoing->ringbackonly);
126         
127         if (single) {
128                 /* If we are calling a single channel, make them compatible for in-band tone purpose */
129                 ast_channel_make_compatible(outgoing->chan, in);
130         }
131         
132         if (outgoing) {
133                 moh = outgoing->musiconhold;
134                 ringind = outgoing->ringbackonly;
135                 if (outgoing->musiconhold) {
136                         ast_moh_start(in, NULL);
137                 } else if (outgoing->ringbackonly) {
138                         ast_indicate(in, AST_CONTROL_RINGING);
139                 }
140         }
141         
142         while(*to && !peer) {
143                 o = outgoing;
144                 found = -1;
145                 pos = 1;
146                 numlines = 0;
147                 watchers[0] = in;
148                 while(o) {
149                         /* Keep track of important channels */
150                         if (o->stillgoing) {
151                                 watchers[pos++] = o->chan;
152                                 found = 1;
153                         }
154                         o = o->next;
155                         numlines++;
156                 }
157                 if (found < 0) {
158                         if (numlines == numbusies) {
159                                 if (option_verbose > 2)
160                                         ast_verbose( VERBOSE_PREFIX_2 "Everyone is busy at this time\n");
161                                 /* See if there is a special busy message */
162                                 if (ast_exists_extension(in, in->context, in->exten, in->priority + 101, in->callerid)) 
163                                         in->priority+=100;
164                         } else {
165                                 if (option_verbose > 2)
166                                         ast_verbose( VERBOSE_PREFIX_2 "No one is available to answer at this time\n");
167                         }
168                         *to = 0;
169                         /* if no one available we'd better stop MOH/ringing to */
170                         if (moh) {
171                                 ast_moh_stop(in);
172                         } else if (ringind) {
173                                 ast_indicate(in, -1);
174                         }
175                         return NULL;
176                 }
177                 winner = ast_waitfor_n(watchers, pos, to);
178                 o = outgoing;
179                 while(o) {
180                         if (o->stillgoing && (o->chan->_state == AST_STATE_UP)) {
181                                 if (!peer) {
182                                         if (option_verbose > 2)
183                                                 ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
184                                         peer = o->chan;
185                                         *allowredir = o->allowredirect;
186                                         *allowdisconnect = o->allowdisconnect;
187                                 }
188                         } else if (o->chan == winner) {
189                                 f = ast_read(winner);
190                                 if (f) {
191                                         if (f->frametype == AST_FRAME_CONTROL) {
192                                                 switch(f->subclass) {
193                                             case AST_CONTROL_ANSWER:
194                                                         /* This is our guy if someone answered. */
195                                                         if (!peer) {
196                                                                 if (option_verbose > 2)
197                                                                         ast_verbose( VERBOSE_PREFIX_3 "%s answered %s\n", o->chan->name, in->name);
198                                                                 peer = o->chan;
199                                                                 *allowredir = o->allowredirect;
200                                                                 *allowdisconnect = o->allowdisconnect;
201                                                         }
202                                                         break;
203                                                 case AST_CONTROL_BUSY:
204                                                         if (option_verbose > 2)
205                                                                 ast_verbose( VERBOSE_PREFIX_3 "%s is busy\n", o->chan->name);
206                                                         o->stillgoing = 0;
207                                                         if (in->cdr)
208                                                                 ast_cdr_busy(in->cdr);
209                                                         numbusies++;
210                                                         break;
211                                                 case AST_CONTROL_CONGESTION:
212                                                         if (option_verbose > 2)
213                                                                 ast_verbose( VERBOSE_PREFIX_3 "%s is circuit-busy\n", o->chan->name);
214                                                         o->stillgoing = 0;
215                                                         if (in->cdr)
216                                                                 ast_cdr_busy(in->cdr);
217                                                         numbusies++;
218                                                         break;
219                                                 case AST_CONTROL_RINGING:
220                                                         if (option_verbose > 2)
221                                                                 ast_verbose( VERBOSE_PREFIX_3 "%s is ringing\n", o->chan->name);
222                                                         if (!sentringing) {
223                                                                 ast_indicate(in, AST_CONTROL_RINGING);
224                                                                 sentringing++;
225                                                                 ringind++;
226                                                         }
227                                                         break;
228                                                 case AST_CONTROL_OFFHOOK:
229                                                         /* Ignore going off hook */
230                                                         break;
231                                                 default:
232                                                         ast_log(LOG_DEBUG, "Dunno what to do with control type %d\n", f->subclass);
233                                                 }
234                                         } else if (single && (f->frametype == AST_FRAME_VOICE) && 
235                                                                 !(outgoing->ringbackonly || outgoing->musiconhold)) {
236                                                 if (ast_write(in, f)) 
237                                                         ast_log(LOG_WARNING, "Unable to forward frame\n");
238                                         } else if (single && (f->frametype == AST_FRAME_IMAGE) && 
239                                                                 !(outgoing->ringbackonly || outgoing->musiconhold)) {
240                                                 if (ast_write(in, f))
241                                                         ast_log(LOG_WARNING, "Unable to forward image\n");
242                                         }
243                                         ast_frfree(f);
244                                 } else {
245                                         o->stillgoing = 0;
246                                 }
247                         }
248                         o = o->next;
249                 }
250                 if (winner == in) {
251                         f = ast_read(in);
252 #if 0
253                         if (f && (f->frametype != AST_FRAME_VOICE))
254                                         printf("Frame type: %d, %d\n", f->frametype, f->subclass);
255                         else if (!f || (f->frametype != AST_FRAME_VOICE))
256                                 printf("Hangup received on %s\n", in->name);
257 #endif
258                         if (!f || ((f->frametype == AST_FRAME_CONTROL) && (f->subclass == AST_CONTROL_HANGUP))) {
259                                 /* Got hung up */
260                                 *to=-1;
261                                 return NULL;
262                         }
263                         if (f && (f->frametype == AST_FRAME_DTMF) && allowdisconnect &&
264                                 (f->subclass == '*')) {
265                             if (option_verbose > 3)
266                                 ast_verbose(VERBOSE_PREFIX_3 "User hit %c to disconnect call.\n", f->subclass);
267                                 *to=0;
268                                 return NULL;
269                         }
270                         if (single && ((f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_DTMF)))  {
271                                 if (ast_write(outgoing->chan, f))
272                                         ast_log(LOG_WARNING, "Unable to forward voice\n");
273                         }
274                 }
275                 if (!*to && (option_verbose > 2))
276                         ast_verbose( VERBOSE_PREFIX_3 "Nobody picked up in %d ms\n", orig);
277         }
278         if (moh) {
279                 ast_moh_stop(in);
280         } else if (ringind) {
281                 ast_indicate(in, -1);
282         }
283
284         return peer;
285         
286 }
287
288 static int dial_exec(struct ast_channel *chan, void *data)
289 {
290         int res=-1;
291         struct localuser *u;
292         char info[256], *peers, *timeout, *tech, *number, *rest, *cur;
293         char  privdb[256] = "", *s;
294         struct localuser *outgoing=NULL, *tmp;
295         struct ast_channel *peer;
296         int to;
297         int allowredir=0;
298         int allowdisconnect=0;
299         int privacy=0;
300         int resetcdr=0;
301         int clearchannel=0;
302         char numsubst[AST_MAX_EXTENSION];
303         char restofit[AST_MAX_EXTENSION];
304         char *transfer = NULL;
305         char *newnum;
306         char callerid[256], *l, *n;
307         char *url=NULL; /* JDG */
308         struct ast_var_t *current;
309         struct varshead *headp, *newheadp;
310         struct ast_var_t *newvar;
311         
312         if (!data) {
313                 ast_log(LOG_WARNING, "Dial requires an argument (technology1/number1&technology2/number2...|optional timeout)\n");
314                 return -1;
315         }
316         
317         LOCAL_USER_ADD(u);
318         
319         /* Parse our arguments XXX Check for failure XXX */
320         strncpy(info, (char *)data, strlen((char *)data) + AST_MAX_EXTENSION-1);
321         peers = info;
322         if (peers) {
323                 timeout = strchr(info, '|');
324                 if (timeout) {
325                         *timeout = '\0';
326                         timeout++;
327                         transfer = strchr(timeout, '|');
328                         if (transfer) {
329                                 *transfer = '\0';
330                                 transfer++;
331                                 /* JDG */
332                                 url = strchr(transfer, '|');
333                                 if (url) {
334                                         *url = '\0';
335                                         url++;
336                                         ast_log(LOG_DEBUG, "DIAL WITH URL=%s_\n", url);
337                                 } else 
338                                         ast_log(LOG_DEBUG, "SIMPLE DIAL (NO URL)\n");
339                                 /* /JDG */
340                         }
341                 }
342         } else
343                 timeout = NULL;
344         if (!peers || !strlen(peers)) {
345                 ast_log(LOG_WARNING, "Dial argument takes format (technology1/number1&technology2/number2...|optional timeout)\n");
346                 goto out;
347         }
348         
349
350         if (transfer) {
351                 /* Extract privacy info from transfer */
352                 if ((s = strstr(transfer, "P("))) {
353                         privacy = 1;
354                         strncpy(privdb, s + 2, sizeof(privdb) - 1);
355                         /* Overwrite with X's what was the privacy info */
356                         while(*s && (*s != ')')) 
357                                 *(s++) = 'X';
358                         if (*s)
359                                 *s = 'X';
360                         /* Now find the end of the privdb */
361                         s = strchr(privdb, ')');
362                         if (s)
363                                 *s = '\0';
364                         else {
365                                 ast_log(LOG_WARNING, "Transfer with privacy lacking trailing '('\n");
366                                 privacy = 0;
367                         }
368                 } else if (strchr(transfer, 'P')) {
369                         /* No specified privdb */
370                         privacy = 1;
371                 } else if (strchr(transfer, 'C')) {
372                         resetcdr = 1;
373                 }
374         }
375         if (resetcdr && chan->cdr)
376                 ast_cdr_reset(chan->cdr, 0);
377         if (!strlen(privdb) && privacy) {
378                 /* If privdb is not specified and we are using privacy, copy from extension */
379                 strncpy(privdb, chan->exten, sizeof(privdb) - 1);
380         }
381         if (privacy) {
382                 if (chan->callerid)
383                         strncpy(callerid, chan->callerid, sizeof(callerid));
384                 else
385                         strcpy(callerid, "");
386                 ast_callerid_parse(callerid, &n, &l);
387                 if (l) {
388                         ast_shrink_phone_number(l);
389                 } else
390                         l = "";
391                 ast_log(LOG_NOTICE, "Privacy DB is '%s', privacy is %d, clid is '%s'\n", privdb, privacy, l);
392         }
393         cur = peers;
394         do {
395                 /* Remember where to start next time */
396                 rest = strchr(cur, '&');
397                 if (rest) {
398                         *rest = 0;
399                         rest++;
400                 }
401                 /* Get a technology/[device:]number pair */
402                 tech = cur;
403                 number = strchr(tech, '/');
404                 if (!number) {
405                         ast_log(LOG_WARNING, "Dial argument takes format (technology1/[device:]number1&technology2/[device:]number2...|optional timeout)\n");
406                         goto out;
407                 }
408                 *number = '\0';
409                 number++;
410                 tmp = malloc(sizeof(struct localuser));
411                 if (!tmp) {
412                         ast_log(LOG_WARNING, "Out of memory\n");
413                         goto out;
414                 }
415                 memset(tmp, 0, sizeof(struct localuser));
416                 if (transfer) {
417                         if (strchr(transfer, 't'))
418                                 tmp->allowredirect = 1;
419                         else    tmp->allowredirect = 0;
420                         if (strchr(transfer, 'r'))
421                                 tmp->ringbackonly = 1;
422                         else    tmp->ringbackonly = 0;
423                         if (strchr(transfer, 'm'))
424                                 tmp->musiconhold = 1;
425                         else    tmp->musiconhold = 0;
426                         if (strchr(transfer, 'd'))
427                                 tmp->dataquality = 1;
428                         else    tmp->dataquality = 0;
429                         if (strchr(transfer, 'H'))
430                                 tmp->allowdisconnect = 1;
431                         else    tmp->allowdisconnect = 0;
432                         if (strchr(transfer, 'c'))
433                                 clearchannel = 1;
434             else    
435                                 clearchannel = 0;
436                 }
437                 strncpy(numsubst, number, sizeof(numsubst)-1);
438                 /* If we're dialing by extension, look at the extension to know what to dial */
439                 if ((newnum = strstr(numsubst, "BYEXTENSION"))) {
440                         strncpy(restofit, newnum + strlen("BYEXTENSION"), sizeof(restofit)-1);
441                         snprintf(newnum, sizeof(numsubst) - (newnum - numsubst), "%s%s", chan->exten,restofit);
442                         if (option_debug)
443                                 ast_log(LOG_DEBUG, "Dialing by extension %s\n", numsubst);
444                 }
445                 /* Request the peer */
446                 tmp->chan = ast_request(tech, chan->nativeformats, numsubst);
447                 if (!tmp->chan) {
448                         /* If we can't, just go on to the next call */
449                         ast_log(LOG_NOTICE, "Unable to create channel of type '%s'\n", tech);
450                         if (chan->cdr)
451                                 ast_cdr_busy(chan->cdr);
452                         free(tmp);
453                         cur = rest;
454                         continue;
455                 }
456                 if (strlen(tmp->chan->call_forward)) {
457                         if (option_verbose > 2)
458                                 ast_verbose(VERBOSE_PREFIX_3 "Forwarding call to '%s@%s'\n", tmp->chan->call_forward, tmp->chan->context);
459                         /* Setup parameters */
460                         strncpy(chan->exten, tmp->chan->call_forward, sizeof(chan->exten));
461                         strncpy(chan->context, tmp->chan->context, sizeof(chan->context));
462                         chan->priority = 0;
463                         to = 0;
464                         ast_hangup(tmp->chan);
465                         free(tmp);
466                         cur = rest;
467                         break;
468                 }
469                 /* If creating a SIP channel, look for a variable called */
470                 /* VXML_URL in the calling channel and copy it to the    */
471                 /* new channel.                                          */
472                 if (strcasecmp(tech,"SIP")==0)
473                 {
474                         headp=&chan->varshead;
475                         AST_LIST_TRAVERSE(headp,current,entries) {
476                                 if (strcasecmp(ast_var_name(current),"VXML_URL")==0)
477                                 {
478                                         newvar=ast_var_assign(ast_var_name(current),ast_var_value(current));
479                                         newheadp=&tmp->chan->varshead;
480                                         AST_LIST_INSERT_HEAD(newheadp,newvar,entries);
481                                         break;
482                                 }
483                         }
484                 }
485                 
486                 tmp->chan->appl = "AppDial";
487                 tmp->chan->data = "(Outgoing Line)";
488                 tmp->chan->whentohangup = 0;
489                 if (tmp->chan->callerid)
490                         free(tmp->chan->callerid);
491                 if (tmp->chan->ani)
492                         free(tmp->chan->ani);
493                 if (chan->callerid)
494                         tmp->chan->callerid = strdup(chan->callerid);
495                 else
496                         tmp->chan->callerid = NULL;
497                 if (chan->ani)
498                         tmp->chan->ani = strdup(chan->ani);
499                 else
500                         tmp->chan->ani = NULL;
501                 /* Presense of ADSI CPE on outgoing channel follows ours */
502                 tmp->chan->adsicpe = chan->adsicpe;
503                 /* Place the call, but don't wait on the answer */
504                 res = ast_call(tmp->chan, numsubst, 0);
505                 if (res) {
506                         /* Again, keep going even if there's an error */
507                         if (option_debug)
508                                 ast_log(LOG_DEBUG, "ast call on peer returned %d\n", res);
509                         else if (option_verbose > 2)
510                                 ast_verbose(VERBOSE_PREFIX_3 "Couldn't call %s\n", numsubst);
511                         ast_hangup(tmp->chan);
512                         free(tmp);
513                         cur = rest;
514                         continue;
515                 } else
516                         if (option_verbose > 2)
517                                 ast_verbose(VERBOSE_PREFIX_3 "Called %s\n", numsubst);
518                 /* Put them in the list of outgoing thingies...  We're ready now. 
519                    XXX If we're forcibly removed, these outgoing calls won't get
520                    hung up XXX */
521                 tmp->stillgoing = -1;
522                 tmp->next = outgoing;
523                 outgoing = tmp;
524                 /* If this line is up, don't try anybody else */
525                 if (outgoing->chan->_state == AST_STATE_UP)
526                         break;
527                 cur = rest;
528         } while(cur);
529         
530         if (timeout && strlen(timeout))
531                 to = atoi(timeout) * 1000;
532         else
533                 to = -1;
534         peer = wait_for_answer(chan, outgoing, &to, &allowredir, &allowdisconnect);
535         if (!peer) {
536                 if (to) 
537                         /* Musta gotten hung up */
538                         res = -1;
539                  else 
540                         /* Nobody answered, next please? */
541                         res=0;
542                 
543                 goto out;
544         }
545         if (peer) {
546                 /* Ah ha!  Someone answered within the desired timeframe.  Of course after this
547                    we will always return with -1 so that it is hung up properly after the 
548                    conversation.  */
549                 if (!strcmp(chan->type,"Zap"))
550                 {
551                         int x = 2;
552                         if (tmp->dataquality || clearchannel) x = 0;
553                         ast_channel_setoption(chan,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
554                 }                       
555                 if (!strcmp(peer->type,"Zap"))
556                 {
557                         int x = 2;
558                         if (tmp->dataquality || clearchannel) x = 0;
559                         ast_channel_setoption(peer,AST_OPTION_TONE_VERIFY,&x,sizeof(char),0);
560                 }                       
561                 hanguptree(outgoing, peer);
562                 outgoing = NULL;
563                 /* If appropriate, log that we have a destination channel */
564                 if (chan->cdr)
565                         ast_cdr_setdestchan(chan->cdr, peer->name);
566                 if (peer->name)
567                         pbx_builtin_setvar_helper(chan, "DIALEDPEERNAME", peer->name);
568                 if (numsubst)
569                         pbx_builtin_setvar_helper(chan, "DIALEDPEERNUMBER", numsubst);
570                 /* Make sure channels are compatible */
571                 res = ast_channel_make_compatible(chan, peer);
572                 if (res < 0) {
573                         ast_log(LOG_WARNING, "Had to drop call because I couldn't make %s compatible with %s\n", chan->name, peer->name);
574                         ast_hangup(peer);
575                         return -1;
576                 }
577                 /* JDG: sendurl */
578                 if( url && strlen(url) && ast_channel_supports_html(peer) ) {
579                         ast_log(LOG_DEBUG, "app_dial: sendurl=%s.\n", url);
580                         ast_channel_sendurl( peer, url );
581                 } /* /JDG */
582                 if (clearchannel)
583                 {
584                         int x = 0;
585                         ast_channel_setoption(chan,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
586                         ast_channel_setoption(peer,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
587                 }
588                 res = ast_bridge_call(chan, peer, allowredir, allowdisconnect | tmp->clearchannel);
589                 if (clearchannel)
590                 {
591                         int x = 1;
592                         ast_channel_setoption(chan,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
593                         ast_channel_setoption(peer,AST_OPTION_AUDIO_MODE,&x,sizeof(char),0);
594                 }
595                 ast_hangup(peer);
596         }       
597 out:
598         hanguptree(outgoing, NULL);
599         LOCAL_USER_REMOVE(u);
600         return res;
601 }
602
603 int unload_module(void)
604 {
605         STANDARD_HANGUP_LOCALUSERS;
606         return ast_unregister_application(app);
607 }
608
609 int load_module(void)
610 {
611         int res;
612         res = ast_register_application(app, dial_exec, synopsis, descrip);
613         return res;
614 }
615
616 char *description(void)
617 {
618         return tdesc;
619 }
620
621 int usecount(void)
622 {
623         int res;
624         STANDARD_USECOUNT(res);
625         return res;
626 }
627
628 char *key()
629 {
630         return ASTERISK_GPL_KEY;
631 }