more ast_copy_string conversions
[asterisk/asterisk.git] / cli.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Standard Command Line Interface
5  * 
6  * Copyright (C) 1999 - 2005, Digium, Inc.
7  *
8  * Mark Spencer <markster@digium.com>
9  *
10  * This program is free software, distributed under the terms of
11  * the GNU General Public License
12  */
13
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <sys/signal.h>
17 #include <stdio.h>
18 #include <signal.h>
19 #include <string.h>
20 #include <ctype.h>
21
22 #include "asterisk.h"
23
24 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
25
26 #include "asterisk/logger.h"
27 #include "asterisk/options.h"
28 #include "asterisk/cli.h"
29 #include "asterisk/module.h"
30 #include "asterisk/pbx.h"
31 #include "asterisk/channel.h"
32 #include "asterisk/manager.h"
33 #include "asterisk/utils.h"
34 #include "asterisk/lock.h"
35 /* For rl_filename_completion */
36 #include "editline/readline/readline.h"
37 /* For module directory */
38 #include "asterisk/version.h"
39 #include "asterisk/build.h"
40
41 #define VERSION_INFO "Asterisk " ASTERISK_VERSION " built by " BUILD_USER "@" BUILD_HOSTNAME \
42         " on a " BUILD_MACHINE " running " BUILD_OS " on " BUILD_DATE
43         
44 extern unsigned long global_fin, global_fout;
45         
46 void ast_cli(int fd, char *fmt, ...)
47 {
48         char *stuff;
49         int res = 0;
50
51         va_list ap;
52         va_start(ap, fmt);
53 #ifdef SOLARIS
54         stuff = (char *)malloc(10240);
55         vsnprintf(stuff, 10240, fmt, ap);
56 #else
57         res = vasprintf(&stuff, fmt, ap);
58 #endif
59         va_end(ap);
60         if (res == -1) {
61                 ast_log(LOG_ERROR, "Out of memory\n");
62         } else {
63                 ast_carefulwrite(fd, stuff, strlen(stuff), 100);
64                 free(stuff);
65         }
66 }
67
68 AST_MUTEX_DEFINE_STATIC(clilock);
69
70 struct ast_cli_entry *helpers = NULL;
71
72 static char load_help[] = 
73 "Usage: load <module name>\n"
74 "       Loads the specified module into Asterisk.\n";
75
76 static char unload_help[] = 
77 "Usage: unload [-f|-h] <module name>\n"
78 "       Unloads the specified module from Asterisk.  The -f\n"
79 "       option causes the module to be unloaded even if it is\n"
80 "       in use (may cause a crash) and the -h module causes the\n"
81 "       module to be unloaded even if the module says it cannot, \n"
82 "       which almost always will cause a crash.\n";
83
84 static char help_help[] =
85 "Usage: help [topic]\n"
86 "       When called with a topic as an argument, displays usage\n"
87 "       information on the given command.  If called without a\n"
88 "       topic, it provides a list of commands.\n";
89
90 static char chanlist_help[] = 
91 "Usage: show channels [concise]\n"
92 "       Lists currently defined channels and some information about\n"
93 "       them.  If 'concise' is specified, format is abridged and in\n"
94 "       a more easily machine parsable format\n";
95
96 static char reload_help[] = 
97 "Usage: reload [module ...]\n"
98 "       Reloads configuration files for all listed modules which support\n"
99 "       reloading, or for all supported modules if none are listed.\n";
100
101 static char set_verbose_help[] = 
102 "Usage: set verbose <level>\n"
103 "       Sets level of verbose messages to be displayed.  0 means\n"
104 "       no messages should be displayed. Equivalent to -v[v[v...]]\n"
105 "       on startup\n";
106
107 static char set_debug_help[] = 
108 "Usage: set debug <level>\n"
109 "       Sets level of core debug messages to be displayed.  0 means\n"
110 "       no messages should be displayed. Equivalent to -d[d[d...]]\n"
111 "       on startup.\n";
112
113 static char softhangup_help[] =
114 "Usage: soft hangup <channel>\n"
115 "       Request that a channel be hung up.  The hangup takes effect\n"
116 "       the next time the driver reads or writes from the channel\n";
117
118 static int handle_load(int fd, int argc, char *argv[])
119 {
120         if (argc != 2)
121                 return RESULT_SHOWUSAGE;
122         if (ast_load_resource(argv[1])) {
123                 ast_cli(fd, "Unable to load module %s\n", argv[1]);
124                 return RESULT_FAILURE;
125         }
126         return RESULT_SUCCESS;
127 }
128
129 static int handle_reload(int fd, int argc, char *argv[])
130 {
131         int x;
132         int res;
133         if (argc < 1)
134                 return RESULT_SHOWUSAGE;
135         if (argc > 1) { 
136                 for (x=1;x<argc;x++) {
137                         res = ast_module_reload(argv[x]);
138                         switch(res) {
139                         case 0:
140                                 ast_cli(fd, "No such module '%s'\n", argv[x]);
141                                 break;
142                         case 1:
143                                 ast_cli(fd, "Module '%s' does not support reload\n", argv[x]);
144                                 break;
145                         }
146                 }
147         } else
148                 ast_module_reload(NULL);
149         return RESULT_SUCCESS;
150 }
151
152 static int handle_set_verbose(int fd, int argc, char *argv[])
153 {
154         int val = 0;
155         int oldval = 0;
156
157         /* Has a hidden 'at least' argument */
158         if ((argc != 3) && (argc != 4))
159                 return RESULT_SHOWUSAGE;
160         if ((argc == 4) && strcasecmp(argv[2], "atleast"))
161                 return RESULT_SHOWUSAGE;
162         oldval = option_verbose;
163         if (argc == 3)
164                 option_verbose = atoi(argv[2]);
165         else {
166                 val = atoi(argv[3]);
167                 if (val > option_verbose)
168                         option_verbose = val;
169         }
170         if (oldval != option_verbose && option_verbose > 0)
171                 ast_cli(fd, "Verbosity was %d and is now %d\n", oldval, option_verbose);
172         else if (oldval > 0 && option_verbose > 0)
173                 ast_cli(fd, "Verbosity is at least %d\n", option_verbose);
174         else if (oldval > 0 && option_verbose == 0)
175                 ast_cli(fd, "Verbosity is now OFF\n");
176         return RESULT_SUCCESS;
177 }
178
179 static int handle_set_debug(int fd, int argc, char *argv[])
180 {
181         int val = 0;
182         int oldval = 0;
183         /* Has a hidden 'at least' argument */
184         if ((argc != 3) && (argc != 4))
185                 return RESULT_SHOWUSAGE;
186         if ((argc == 4) && strcasecmp(argv[2], "atleast"))
187                 return RESULT_SHOWUSAGE;
188         oldval = option_debug;
189         if (argc == 3)
190                 option_debug = atoi(argv[2]);
191         else {
192                 val = atoi(argv[3]);
193                 if (val > option_debug)
194                         option_debug = val;
195         }
196         if (oldval != option_debug && option_debug > 0)
197                 ast_cli(fd, "Core debug was %d and is now %d\n", oldval, option_debug);
198         else if (oldval > 0 && option_debug > 0)
199                 ast_cli(fd, "Core debug is at least %d\n", option_debug);
200         else if (oldval > 0 && option_debug == 0)
201                 ast_cli(fd, "Core debug is now OFF\n");
202         return RESULT_SUCCESS;
203 }
204
205 static int handle_unload(int fd, int argc, char *argv[])
206 {
207         int x;
208         int force=AST_FORCE_SOFT;
209         if (argc < 2)
210                 return RESULT_SHOWUSAGE;
211         for (x=1;x<argc;x++) {
212                 if (argv[x][0] == '-') {
213                         switch(argv[x][1]) {
214                         case 'f':
215                                 force = AST_FORCE_FIRM;
216                                 break;
217                         case 'h':
218                                 force = AST_FORCE_HARD;
219                                 break;
220                         default:
221                                 return RESULT_SHOWUSAGE;
222                         }
223                 } else if (x !=  argc - 1) 
224                         return RESULT_SHOWUSAGE;
225                 else if (ast_unload_resource(argv[x], force)) {
226                         ast_cli(fd, "Unable to unload resource %s\n", argv[x]);
227                         return RESULT_FAILURE;
228                 }
229         }
230         return RESULT_SUCCESS;
231 }
232
233 #define MODLIST_FORMAT  "%-30s %-40.40s %-10d\n"
234 #define MODLIST_FORMAT2 "%-30s %-40.40s %-10s\n"
235
236 AST_MUTEX_DEFINE_STATIC(climodentrylock);
237 static int climodentryfd = -1;
238
239 static int modlist_modentry(const char *module, const char *description, int usecnt, const char *like)
240 {
241         /* Comparing the like with the module */
242         if (strstr(module, like) != NULL) {
243                 ast_cli(climodentryfd, MODLIST_FORMAT, module, description, usecnt);
244                 return 1;
245         } 
246         return 0;
247 }
248
249 static char modlist_help[] =
250 "Usage: show modules [like keyword]\n"
251 "       Shows Asterisk modules currently in use, and usage statistics.\n";
252
253 static char version_help[] =
254 "Usage: show version\n"
255 "       Shows Asterisk version information.\n";
256
257 static char uptime_help[] =
258 "Usage: show uptime [seconds]\n"
259 "       Shows Asterisk uptime information.\n"
260 "       The seconds word returns the uptime in seconds only.\n";
261
262 static char *format_uptimestr(time_t timeval)
263 {
264         int years = 0, weeks = 0, days = 0, hours = 0, mins = 0, secs = 0;
265         char timestr[256]="";
266         int bytes = 0;
267         int maxbytes = 0;
268         int offset = 0;
269 #define SECOND (1)
270 #define MINUTE (SECOND*60)
271 #define HOUR (MINUTE*60)
272 #define DAY (HOUR*24)
273 #define WEEK (DAY*7)
274 #define YEAR (DAY*365)
275 #define ESS(x) ((x == 1) ? "" : "s")
276
277         maxbytes = sizeof(timestr);
278         if (timeval < 0)
279                 return NULL;
280         if (timeval > YEAR) {
281                 years = (timeval / YEAR);
282                 timeval -= (years * YEAR);
283                 if (years > 0) {
284                         snprintf(timestr + offset, maxbytes, "%d year%s, ", years, ESS(years));
285                         bytes = strlen(timestr + offset);
286                         offset += bytes;
287                         maxbytes -= bytes;
288                 }
289         }
290         if (timeval > WEEK) {
291                 weeks = (timeval / WEEK);
292                 timeval -= (weeks * WEEK);
293                 if (weeks > 0) {
294                         snprintf(timestr + offset, maxbytes, "%d week%s, ", weeks, ESS(weeks));
295                         bytes = strlen(timestr + offset);
296                         offset += bytes;
297                         maxbytes -= bytes;
298                 }
299         }
300         if (timeval > DAY) {
301                 days = (timeval / DAY);
302                 timeval -= (days * DAY);
303                 if (days > 0) {
304                         snprintf(timestr + offset, maxbytes, "%d day%s, ", days, ESS(days));
305                         bytes = strlen(timestr + offset);
306                         offset += bytes;
307                         maxbytes -= bytes;
308                 }
309         }
310         if (timeval > HOUR) {
311                 hours = (timeval / HOUR);
312                 timeval -= (hours * HOUR);
313                 if (hours > 0) {
314                         snprintf(timestr + offset, maxbytes, "%d hour%s, ", hours, ESS(hours));
315                         bytes = strlen(timestr + offset);
316                         offset += bytes;
317                         maxbytes -= bytes;
318                 }
319         }
320         if (timeval > MINUTE) {
321                 mins = (timeval / MINUTE);
322                 timeval -= (mins * MINUTE);
323                 if (mins > 0) {
324                         snprintf(timestr + offset, maxbytes, "%d minute%s, ", mins, ESS(mins));
325                         bytes = strlen(timestr + offset);
326                         offset += bytes;
327                         maxbytes -= bytes;
328                 }
329         }
330         secs = timeval;
331
332         if (secs > 0) {
333                 snprintf(timestr + offset, maxbytes, "%d second%s", secs, ESS(secs));
334         }
335
336         return timestr ? strdup(timestr) : NULL;
337 }
338
339 static int handle_showuptime(int fd, int argc, char *argv[])
340 {
341         time_t curtime, tmptime;
342         char *timestr;
343         int printsec;
344
345         printsec = ((argc == 3) && (!strcasecmp(argv[2],"seconds")));
346         if ((argc != 2) && (!printsec))
347                 return RESULT_SHOWUSAGE;
348
349         time(&curtime);
350         if (ast_startuptime) {
351                 tmptime = curtime - ast_startuptime;
352                 if (printsec) {
353                         ast_cli(fd, "System uptime: %lu\n",tmptime);
354                 } else {
355                         timestr = format_uptimestr(tmptime);
356                         if (timestr) {
357                                 ast_cli(fd, "System uptime: %s\n", timestr);
358                                 free(timestr);
359                         }
360                 }
361         }               
362         if (ast_lastreloadtime) {
363                 tmptime = curtime - ast_lastreloadtime;
364                 if (printsec) {
365                         ast_cli(fd, "Last reload: %lu\n", tmptime);
366                 } else {
367                         timestr = format_uptimestr(tmptime);
368                         if ((timestr) && (!printsec)) {
369                                 ast_cli(fd, "Last reload: %s\n", timestr);
370                                 free(timestr);
371                         } 
372                 }
373         }
374         return RESULT_SUCCESS;
375 }
376
377 static int handle_modlist(int fd, int argc, char *argv[])
378 {
379         char *like = "";
380         if (argc == 3)
381                 return RESULT_SHOWUSAGE;
382         else if (argc >= 4) {
383                 if (strcmp(argv[2],"like")) 
384                         return RESULT_SHOWUSAGE;
385                 like = argv[3];
386         }
387                 
388         ast_mutex_lock(&climodentrylock);
389         climodentryfd = fd;
390         ast_cli(fd, MODLIST_FORMAT2, "Module", "Description", "Use Count");
391         ast_cli(fd,"%d modules loaded\n", ast_update_module_list(modlist_modentry, like));
392         climodentryfd = -1;
393         ast_mutex_unlock(&climodentrylock);
394         return RESULT_SUCCESS;
395 }
396
397 static int handle_version(int fd, int argc, char *argv[])
398 {
399         if (argc != 2)
400                 return RESULT_SHOWUSAGE;
401         ast_cli(fd, "%s\n", VERSION_INFO);
402         return RESULT_SUCCESS;
403 }
404 static int handle_chanlist(int fd, int argc, char *argv[])
405 {
406 #define FORMAT_STRING  "%15s  (%-10s %-12s %-4d) %7s %-12s  %-15s\n"
407 #define FORMAT_STRING2 "%15s  (%-10s %-12s %-4s) %7s %-12s  %-15s\n"
408 #define CONCISE_FORMAT_STRING  "%s:%s:%s:%d:%s:%s:%s:%s:%s:%d\n"
409
410         struct ast_channel *c=NULL;
411         int numchans = 0;
412         int concise = 0;
413         if (argc < 2 || argc > 3)
414                 return RESULT_SHOWUSAGE;
415         
416         concise = (argc == 3 && (!strcasecmp(argv[2],"concise")));
417         if(!concise)
418                 ast_cli(fd, FORMAT_STRING2, "Channel", "Context", "Extension", "Pri", "State", "Appl.", "Data");
419         while ( (c = ast_channel_walk_locked(c)) != NULL) {
420                 if(concise)
421                         ast_cli(fd, CONCISE_FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
422                                         c->appl ? c->appl : "(None)", c->data ? ( !ast_strlen_zero(c->data) ? c->data : "" ): "",
423                                         (c->cid.cid_num && !ast_strlen_zero(c->cid.cid_num)) ? c->cid.cid_num : "",
424                                         (c->accountcode && !ast_strlen_zero(c->accountcode)) ? c->accountcode : "",c->amaflags);
425                 else
426                         ast_cli(fd, FORMAT_STRING, c->name, c->context, c->exten, c->priority, ast_state2str(c->_state),
427                                         c->appl ? c->appl : "(None)", c->data ? ( !ast_strlen_zero(c->data) ? c->data : "(Empty)" ): "(None)");
428
429                 numchans++;
430                 ast_mutex_unlock(&c->lock);
431         }
432         if(!concise) {
433                 ast_cli(fd, "%d active channel%s\n", numchans, (numchans!=1) ? "s" : "");
434                 if (option_maxcalls)
435                         ast_cli(fd, "%d of %d max active call%s (%5.2f%% of capacity)\n", ast_active_calls(), option_maxcalls, (ast_active_calls()!=1) ? "s" : "", ((float)ast_active_calls() / (float)option_maxcalls) * 100.0);
436                 else
437                         ast_cli(fd, "%d active call%s\n", ast_active_calls(), (ast_active_calls()!=1) ? "s" : "");
438         }
439         return RESULT_SUCCESS;
440 }
441
442 static char showchan_help[] = 
443 "Usage: show channel <channel>\n"
444 "       Shows lots of information about the specified channel.\n";
445
446 static char debugchan_help[] = 
447 "Usage: debug channel <channel>\n"
448 "       Enables debugging on a specific channel.\n";
449
450 static char debuglevel_help[] = 
451 "Usage: debug level <level> [filename]\n"
452 "       Set debug to specified level (0 to disable).  If filename\n"
453 "is specified, debugging will be limited to just that file.\n";
454
455 static char nodebugchan_help[] = 
456 "Usage: no debug channel <channel>\n"
457 "       Disables debugging on a specific channel.\n";
458
459 static char commandcomplete_help[] = 
460 "Usage: _command complete \"<line>\" text state\n"
461 "       This function is used internally to help with command completion and should.\n"
462 "       never be called by the user directly.\n";
463
464 static char commandnummatches_help[] = 
465 "Usage: _command nummatches \"<line>\" text \n"
466 "       This function is used internally to help with command completion and should.\n"
467 "       never be called by the user directly.\n";
468
469 static char commandmatchesarray_help[] = 
470 "Usage: _command matchesarray \"<line>\" text \n"
471 "       This function is used internally to help with command completion and should.\n"
472 "       never be called by the user directly.\n";
473
474 static int handle_softhangup(int fd, int argc, char *argv[])
475 {
476         struct ast_channel *c=NULL;
477         if (argc != 3)
478                 return RESULT_SHOWUSAGE;
479         c = ast_get_channel_by_name_locked(argv[2]);
480         if (c) {
481                 ast_cli(fd, "Requested Hangup on channel '%s'\n", c->name);
482                 ast_softhangup(c, AST_SOFTHANGUP_EXPLICIT);
483                 ast_mutex_unlock(&c->lock);
484         } else
485                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
486         return RESULT_SUCCESS;
487 }
488
489 static char *__ast_cli_generator(char *text, char *word, int state, int lock);
490
491 static int handle_commandmatchesarray(int fd, int argc, char *argv[])
492 {
493         char *buf, *obuf;
494         int buflen = 2048;
495         int len = 0;
496         char **matches;
497         int x, matchlen;
498
499         if (argc != 4)
500                 return RESULT_SHOWUSAGE;
501         buf = malloc(buflen);
502         if (!buf)
503                 return RESULT_FAILURE;
504         buf[len] = '\0';
505         matches = ast_cli_completion_matches(argv[2], argv[3]);
506         if (matches) {
507                 for (x=0; matches[x]; x++) {
508 #if 0
509                         printf("command matchesarray for '%s' %s got '%s'\n", argv[2], argv[3], matches[x]);
510 #endif
511                         matchlen = strlen(matches[x]) + 1;
512                         if (len + matchlen >= buflen) {
513                                 buflen += matchlen * 3;
514                                 obuf = buf;
515                                 buf = realloc(obuf, buflen);
516                                 if (!buf) 
517                                         /* Out of memory...  Just free old buffer and be done */
518                                         free(obuf);
519                         }
520                         if (buf)
521                                 len += sprintf( buf + len, "%s ", matches[x]);
522                         free(matches[x]);
523                         matches[x] = NULL;
524                 }
525                 free(matches);
526         }
527 #if 0
528         printf("array for '%s' %s got '%s'\n", argv[2], argv[3], buf);
529 #endif
530         
531         if (buf) {
532                 ast_cli(fd, "%s%s",buf, AST_CLI_COMPLETE_EOF);
533                 free(buf);
534         } else
535                 ast_cli(fd, "NULL\n");
536
537         return RESULT_SUCCESS;
538 }
539
540
541
542 static int handle_commandnummatches(int fd, int argc, char *argv[])
543 {
544         int matches = 0;
545
546         if (argc != 4)
547                 return RESULT_SHOWUSAGE;
548
549         matches = ast_cli_generatornummatches(argv[2], argv[3]);
550
551 #if 0
552         printf("Search for '%s' %s got '%d'\n", argv[2], argv[3], matches);
553 #endif
554         ast_cli(fd, "%d", matches);
555
556         return RESULT_SUCCESS;
557 }
558
559 static int handle_commandcomplete(int fd, int argc, char *argv[])
560 {
561         char *buf;
562 #if 0
563         printf("Search for %d args: '%s', '%s', '%s', '%s'\n", argc, argv[0], argv[1], argv[2], argv[3]);
564 #endif  
565         if (argc != 5)
566                 return RESULT_SHOWUSAGE;
567         buf = __ast_cli_generator(argv[2], argv[3], atoi(argv[4]), 0);
568 #if 0
569         printf("Search for '%s' %s %d got '%s'\n", argv[2], argv[3], atoi(argv[4]), buf);
570 #endif  
571         if (buf) {
572                 ast_cli(fd, buf);
573                 free(buf);
574         } else
575                 ast_cli(fd, "NULL\n");
576         return RESULT_SUCCESS;
577 }
578
579 static int handle_debuglevel(int fd, int argc, char *argv[])
580 {
581         int newlevel;
582         char *filename = "<any>";
583         if ((argc < 3) || (argc > 4))
584                 return RESULT_SHOWUSAGE;
585         if (sscanf(argv[2], "%d", &newlevel) != 1)
586                 return RESULT_SHOWUSAGE;
587         option_debug = newlevel;
588         if (argc == 4) {
589                 filename = argv[3];
590                 ast_copy_string(debug_filename, filename, sizeof(debug_filename));
591         } else {
592                 debug_filename[0] = '\0';
593         }
594         ast_cli(fd, "Debugging level set to %d, file '%s'\n", newlevel, filename);
595         return RESULT_SUCCESS;
596 }
597
598 #define DEBUGCHAN_FLAG  0x80000000
599 /* XXX todo: merge next two functions!!! */
600 static int handle_debugchan(int fd, int argc, char *argv[])
601 {
602         struct ast_channel *c=NULL;
603         int is_all;
604         if (argc != 3)
605                 return RESULT_SHOWUSAGE;
606
607         is_all = !strcasecmp("all", argv[2]);
608         if (is_all) {
609                 global_fin |= DEBUGCHAN_FLAG;
610                 global_fout |= DEBUGCHAN_FLAG;
611                 c = ast_channel_walk_locked(NULL);
612         } else {
613                 c = ast_get_channel_by_name_locked(argv[2]);
614                 if (c == NULL)
615                         ast_cli(fd, "No such channel %s\n", argv[2]);
616         }
617         while(c) {
618                 if (!(c->fin & DEBUGCHAN_FLAG) || !(c->fout & DEBUGCHAN_FLAG)) {
619                         c->fin |= DEBUGCHAN_FLAG;
620                         c->fout |= DEBUGCHAN_FLAG;
621                         ast_cli(fd, "Debugging enabled on channel %s\n", c->name);
622                 }
623                 ast_mutex_unlock(&c->lock);
624                 if (!is_all)
625                         break;
626                 c = ast_channel_walk_locked(c);
627         }
628         ast_cli(fd, "Debugging on new channels is enabled\n");
629         return RESULT_SUCCESS;
630 }
631
632 static int handle_nodebugchan(int fd, int argc, char *argv[])
633 {
634         struct ast_channel *c=NULL;
635         int is_all;
636         if (argc != 4)
637                 return RESULT_SHOWUSAGE;
638         is_all = !strcasecmp("all", argv[3]);
639         if (is_all) {
640                 global_fin &= ~DEBUGCHAN_FLAG;
641                 global_fout &= ~DEBUGCHAN_FLAG;
642                 c = ast_channel_walk_locked(NULL);
643         } else {
644                 c = ast_get_channel_by_name_locked(argv[3]);
645                 if (c == NULL)
646                         ast_cli(fd, "No such channel %s\n", argv[3]);
647     }
648         while(c) {
649                 if ((c->fin & DEBUGCHAN_FLAG) || (c->fout & DEBUGCHAN_FLAG)) {
650                         c->fin &= ~DEBUGCHAN_FLAG;
651                         c->fout &= ~DEBUGCHAN_FLAG;
652                         ast_cli(fd, "Debugging disabled on channel %s\n", c->name);
653                 }
654                 ast_mutex_unlock(&c->lock);
655                 if (!is_all)
656                         break;
657                 c = ast_channel_walk_locked(c);
658         }
659         ast_cli(fd, "Debugging on new channels is disabled\n");
660         return RESULT_SUCCESS;
661 }
662                 
663         
664
665 static int handle_showchan(int fd, int argc, char *argv[])
666 {
667         struct ast_channel *c=NULL;
668         struct timeval now;
669         char buf[2048];
670         char cdrtime[256];
671         long elapsed_seconds=0;
672         int hour=0, min=0, sec=0;
673         
674         if (argc != 3)
675                 return RESULT_SHOWUSAGE;
676         gettimeofday(&now, NULL);
677         c = ast_get_channel_by_name_locked(argv[2]);
678         if (!c) {
679                 ast_cli(fd, "%s is not a known channel\n", argv[2]);
680                 return RESULT_SUCCESS;
681         }
682         if(c->cdr) {
683                 elapsed_seconds = now.tv_sec - c->cdr->start.tv_sec;
684                 hour = elapsed_seconds / 3600;
685                 min = (elapsed_seconds % 3600) / 60;
686                 sec = elapsed_seconds % 60;
687                 snprintf(cdrtime, sizeof(cdrtime), "%dh%dm%ds", hour, min, sec);
688         } else
689                 strcpy(cdrtime, "N/A");
690         ast_cli(fd, 
691                 " -- General --\n"
692                 "           Name: %s\n"
693                 "           Type: %s\n"
694                 "       UniqueID: %s\n"
695                 "      Caller ID: %s\n"
696                 " Caller ID Name: %s\n"
697                 "    DNID Digits: %s\n"
698                 "          State: %s (%d)\n"
699                 "          Rings: %d\n"
700                 "   NativeFormat: %d\n"
701                 "    WriteFormat: %d\n"
702                 "     ReadFormat: %d\n"
703                 "1st File Descriptor: %d\n"
704                 "      Frames in: %d%s\n"
705                 "     Frames out: %d%s\n"
706                 " Time to Hangup: %ld\n"
707                 "   Elapsed Time: %s\n"
708                 "  Direct Bridge: %s\n"
709                 "Indirect Bridge: %s\n"
710                 " --   PBX   --\n"
711                 "        Context: %s\n"
712                 "      Extension: %s\n"
713                 "       Priority: %d\n"
714                 "     Call Group: %d\n"
715                 "   Pickup Group: %d\n"
716                 "    Application: %s\n"
717                 "           Data: %s\n"
718                 "    Blocking in: %s\n",
719                 c->name, c->type, c->uniqueid,
720                 (c->cid.cid_num ? c->cid.cid_num : "(N/A)"),
721                 (c->cid.cid_name ? c->cid.cid_name : "(N/A)"),
722                 (c->cid.cid_dnid ? c->cid.cid_dnid : "(N/A)" ), ast_state2str(c->_state), c->_state, c->rings, c->nativeformats, c->writeformat, c->readformat,
723                 c->fds[0], c->fin & 0x7fffffff, (c->fin & 0x80000000) ? " (DEBUGGED)" : "",
724                 c->fout & 0x7fffffff, (c->fout & 0x80000000) ? " (DEBUGGED)" : "", (long)c->whentohangup,
725                 cdrtime, c->_bridge ? c->_bridge->name : "<none>", ast_bridged_channel(c) ? ast_bridged_channel(c)->name : "<none>", 
726                 c->context, c->exten, c->priority, c->callgroup, c->pickupgroup, ( c->appl ? c->appl : "(N/A)" ),
727                 ( c-> data ? (!ast_strlen_zero(c->data) ? c->data : "(Empty)") : "(None)"),
728                 (ast_test_flag(c, AST_FLAG_BLOCKING) ? c->blockproc : "(Not Blocking)"));
729         
730         if(pbx_builtin_serialize_variables(c,buf,sizeof(buf)))
731                 ast_cli(fd,"      Variables:\n%s\n",buf);
732         if(c->cdr && ast_cdr_serialize_variables(c->cdr,buf, sizeof(buf), '=', '\n', 1))
733                 ast_cli(fd,"  CDR Variables:\n%s\n",buf);
734         
735         ast_mutex_unlock(&c->lock);
736         return RESULT_SUCCESS;
737 }
738
739 static char *complete_ch_helper(char *line, char *word, int pos, int state, int rpos)
740 {
741         struct ast_channel *c = NULL;
742         int which=0;
743         char *ret = NULL;
744
745         if (pos != rpos)
746                 return NULL;
747         while ( (c = ast_channel_walk_locked(c)) != NULL) {
748                 if (!strncasecmp(word, c->name, strlen(word))) {
749                         if (++which > state) {
750                                 ret = strdup(c->name);
751                                 ast_mutex_unlock(&c->lock);
752                                 break;
753                         }
754                 }
755                 ast_mutex_unlock(&c->lock);
756         }
757         return ret;
758 }
759
760 static char *complete_ch_3(char *line, char *word, int pos, int state)
761 {
762         return complete_ch_helper(line, word, pos, state, 2);
763 }
764
765 static char *complete_ch_4(char *line, char *word, int pos, int state)
766 {
767         return complete_ch_helper(line, word, pos, state, 3);
768 }
769
770 static char *complete_mod_2(char *line, char *word, int pos, int state)
771 {
772         return ast_module_helper(line, word, pos, state, 1, 1);
773 }
774
775 static char *complete_mod_4(char *line, char *word, int pos, int state)
776 {
777         return ast_module_helper(line, word, pos, state, 3, 0);
778 }
779
780 static char *complete_fn(char *line, char *word, int pos, int state)
781 {
782         char *c;
783         char filename[256];
784         if (pos != 1)
785                 return NULL;
786         if (word[0] == '/')
787                 ast_copy_string(filename, word, sizeof(filename));
788         else
789                 snprintf(filename, sizeof(filename), "%s/%s", (char *)ast_config_AST_MODULE_DIR, word);
790         c = (char*)filename_completion_function(filename, state);
791         if (c && word[0] != '/')
792                 c += (strlen((char*)ast_config_AST_MODULE_DIR) + 1);
793         return c ? strdup(c) : c;
794 }
795
796 static int handle_help(int fd, int argc, char *argv[]);
797
798 static struct ast_cli_entry builtins[] = {
799         /* Keep alphabetized, with longer matches first (example: abcd before abc) */
800         { { "_command", "complete", NULL }, handle_commandcomplete, "Command complete", commandcomplete_help },
801         { { "_command", "nummatches", NULL }, handle_commandnummatches, "Returns number of command matches", commandnummatches_help },
802         { { "_command", "matchesarray", NULL }, handle_commandmatchesarray, "Returns command matches array", commandmatchesarray_help },
803         { { "debug", "channel", NULL }, handle_debugchan, "Enable debugging on a channel", debugchan_help, complete_ch_3 },
804         { { "debug", "level", NULL }, handle_debuglevel, "Set global debug level", debuglevel_help },
805         { { "help", NULL }, handle_help, "Display help list, or specific help on a command", help_help },
806         { { "load", NULL }, handle_load, "Load a dynamic module by name", load_help, complete_fn },
807         { { "no", "debug", "channel", NULL }, handle_nodebugchan, "Disable debugging on a channel", nodebugchan_help, complete_ch_4 },
808         { { "reload", NULL }, handle_reload, "Reload configuration", reload_help, complete_mod_2 },
809         { { "set", "debug", NULL }, handle_set_debug, "Set level of debug chattiness", set_debug_help },
810         { { "set", "verbose", NULL }, handle_set_verbose, "Set level of verboseness", set_verbose_help },
811         { { "show", "channel", NULL }, handle_showchan, "Display information on a specific channel", showchan_help, complete_ch_3 },
812         { { "show", "channels", NULL }, handle_chanlist, "Display information on channels", chanlist_help },
813         { { "show", "modules", NULL }, handle_modlist, "List modules and info", modlist_help },
814         { { "show", "modules", "like", NULL }, handle_modlist, "List modules and info", modlist_help, complete_mod_4 },
815         { { "show", "uptime", NULL }, handle_showuptime, "Show uptime information", uptime_help },
816         { { "show", "version", NULL }, handle_version, "Display version info", version_help },
817         { { "soft", "hangup", NULL }, handle_softhangup, "Request a hangup on a given channel", softhangup_help, complete_ch_3 },
818         { { "unload", NULL }, handle_unload, "Unload a dynamic module by name", unload_help, complete_fn },
819         { { NULL }, NULL, NULL, NULL }
820 };
821
822 static struct ast_cli_entry *find_cli(char *cmds[], int exact)
823 {
824         int x;
825         int y;
826         int match;
827         struct ast_cli_entry *e=NULL;
828
829         for (e=helpers;e;e=e->next) {
830                 match = 1;
831                 for (y=0;match && cmds[y]; y++) {
832                         if (!e->cmda[y] && !exact)
833                                 break;
834                         if (!e->cmda[y] || strcasecmp(e->cmda[y], cmds[y]))
835                                 match = 0;
836                 }
837                 if ((exact > -1) && e->cmda[y])
838                         match = 0;
839                 if (match)
840                         break;
841         }
842         if (e)
843                 return e;
844         for (x=0;builtins[x].cmda[0];x++) {
845                 /* start optimistic */
846                 match = 1;
847                 for (y=0;match && cmds[y]; y++) {
848                         /* If there are no more words in the candidate command, then we're
849                            there.  */
850                         if (!builtins[x].cmda[y] && !exact)
851                                 break;
852                         /* If there are no more words in the command (and we're looking for
853                            an exact match) or there is a difference between the two words,
854                            then this is not a match */
855                         if (!builtins[x].cmda[y] || strcasecmp(builtins[x].cmda[y], cmds[y]))
856                                 match = 0;
857                 }
858                 /* If more words are needed to complete the command then this is not
859                    a candidate (unless we're looking for a really inexact answer  */
860                 if ((exact > -1) && builtins[x].cmda[y])
861                         match = 0;
862                 if (match)
863                         return &builtins[x];
864         }
865         return NULL;
866 }
867
868 static void join(char *dest, size_t destsize, char *w[])
869 {
870         int x;
871         /* Join words into a string */
872         if (!dest || destsize < 1) {
873                 return;
874         }
875         dest[0] = '\0';
876         for (x=0;w[x];x++) {
877                 if (x)
878                         strncat(dest, " ", destsize - strlen(dest) - 1);
879                 strncat(dest, w[x], destsize - strlen(dest) - 1);
880         }
881 }
882
883 static void join2(char *dest, size_t destsize, char *w[])
884 {
885         int x;
886         /* Join words into a string */
887         if (!dest || destsize < 1) {
888                 return;
889         }
890         dest[0] = '\0';
891         for (x=0;w[x];x++) {
892                 strncat(dest, w[x], destsize - strlen(dest) - 1);
893         }
894 }
895
896 static char *find_best(char *argv[])
897 {
898         static char cmdline[80];
899         int x;
900         /* See how close we get, then print the  */
901         char *myargv[AST_MAX_CMD_LEN];
902         for (x=0;x<AST_MAX_CMD_LEN;x++)
903                 myargv[x]=NULL;
904         for (x=0;argv[x];x++) {
905                 myargv[x] = argv[x];
906                 if (!find_cli(myargv, -1))
907                         break;
908         }
909         join(cmdline, sizeof(cmdline), myargv);
910         return cmdline;
911 }
912
913 int ast_cli_unregister(struct ast_cli_entry *e)
914 {
915         struct ast_cli_entry *cur, *l=NULL;
916         ast_mutex_lock(&clilock);
917         cur = helpers;
918         while(cur) {
919                 if (e == cur) {
920                         if (e->inuse) {
921                                 ast_log(LOG_WARNING, "Can't remove command that is in use\n");
922                         } else {
923                                 /* Rewrite */
924                                 if (l)
925                                         l->next = e->next;
926                                 else
927                                         helpers = e->next;
928                                 e->next = NULL;
929                                 break;
930                         }
931                 }
932                 l = cur;
933                 cur = cur->next;
934         }
935         ast_mutex_unlock(&clilock);
936         return 0;
937 }
938
939 int ast_cli_register(struct ast_cli_entry *e)
940 {
941         struct ast_cli_entry *cur, *l=NULL;
942         char fulle[80] ="", fulltst[80] ="";
943         static int len;
944         ast_mutex_lock(&clilock);
945         join2(fulle, sizeof(fulle), e->cmda);
946         if (find_cli(e->cmda, -1)) {
947                 ast_mutex_unlock(&clilock);
948                 ast_log(LOG_WARNING, "Command '%s' already registered (or something close enough)\n", fulle);
949                 return -1;
950         }
951         cur = helpers;
952         while(cur) {
953                 join2(fulltst, sizeof(fulltst), cur->cmda);
954                 len = strlen(fulltst);
955                 if (strlen(fulle) < len)
956                         len = strlen(fulle);
957                 if (strncasecmp(fulle, fulltst, len) < 0) {
958                         if (l) {
959                                 e->next = l->next;
960                                 l->next = e;
961                         } else {
962                                 e->next = helpers;
963                                 helpers = e;
964                         }
965                         break;
966                 }
967                 l = cur;
968                 cur = cur->next;
969         }
970         if (!cur) {
971                 if (l)
972                         l->next = e;
973                 else
974                         helpers = e;
975                 e->next = NULL;
976         }
977         ast_mutex_unlock(&clilock);
978         return 0;
979 }
980
981 /*
982  * register/unregister an array of entries.
983  */
984 void ast_cli_register_multiple(struct ast_cli_entry *e, int len)
985 {
986         int i;
987
988         for (i=0; i < len; i++)
989                 ast_cli_register(e + i);
990 }
991
992 void ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
993 {
994         int i;
995
996         for (i=0; i < len; i++)
997                 ast_cli_unregister(e + i);
998 }
999
1000 static int help_workhorse(int fd, char *match[])
1001 {
1002         char fullcmd1[80] = "";
1003         char fullcmd2[80] = "";
1004         char matchstr[80];
1005         char *fullcmd = NULL;
1006         struct ast_cli_entry *e, *e1, *e2;
1007         e1 = builtins;
1008         e2 = helpers;
1009         if (match)
1010                 join(matchstr, sizeof(matchstr), match);
1011         while(e1->cmda[0] || e2) {
1012                 if (e2)
1013                         join(fullcmd2, sizeof(fullcmd2), e2->cmda);
1014                 if (e1->cmda[0])
1015                         join(fullcmd1, sizeof(fullcmd1), e1->cmda);
1016                 if (!e1->cmda[0] || 
1017                                 (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
1018                         /* Use e2 */
1019                         e = e2;
1020                         fullcmd = fullcmd2;
1021                         /* Increment by going to next */
1022                         e2 = e2->next;
1023                 } else {
1024                         /* Use e1 */
1025                         e = e1;
1026                         fullcmd = fullcmd1;
1027                         e1++;
1028                 }
1029                 /* Hide commands that start with '_' */
1030                 if (fullcmd[0] == '_')
1031                         continue;
1032                 if (match) {
1033                         if (strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
1034                                 continue;
1035                         }
1036                 }
1037                 ast_cli(fd, "%25.25s  %s\n", fullcmd, e->summary);
1038         }
1039         return 0;
1040 }
1041
1042 static int handle_help(int fd, int argc, char *argv[]) {
1043         struct ast_cli_entry *e;
1044         char fullcmd[80];
1045         if ((argc < 1))
1046                 return RESULT_SHOWUSAGE;
1047         if (argc > 1) {
1048                 e = find_cli(argv + 1, 1);
1049                 if (e) {
1050                         if (e->usage)
1051                                 ast_cli(fd, e->usage);
1052                         else {
1053                                 join(fullcmd, sizeof(fullcmd), argv+1);
1054                                 ast_cli(fd, "No help text available for '%s'.\n", fullcmd);
1055                         }
1056                 } else {
1057                         if (find_cli(argv + 1, -1)) {
1058                                 return help_workhorse(fd, argv + 1);
1059                         } else {
1060                                 join(fullcmd, sizeof(fullcmd), argv+1);
1061                                 ast_cli(fd, "No such command '%s'.\n", fullcmd);
1062                         }
1063                 }
1064         } else {
1065                 return help_workhorse(fd, NULL);
1066         }
1067         return RESULT_SUCCESS;
1068 }
1069
1070 static char *parse_args(char *s, int *argc, char *argv[], int max)
1071 {
1072         char *dup, *cur;
1073         int x = 0;
1074         int quoted = 0;
1075         int escaped = 0;
1076         int whitespace = 1;
1077
1078         if (!(dup = strdup(s)))
1079                 return NULL;
1080
1081         cur = dup;
1082         while (*s) {
1083                 if ((*s == '"') && !escaped) {
1084                         quoted = !quoted;
1085                         if (quoted & whitespace) {
1086                                 /* If we're starting a quoted string, coming off white space, start a new argument */
1087                                 if (x >= (max - 1)) {
1088                                         ast_log(LOG_WARNING, "Too many arguments, truncating\n");
1089                                         break;
1090                                 }
1091                                 argv[x++] = cur;
1092                                 whitespace = 0;
1093                         }
1094                         escaped = 0;
1095                 } else if (((*s == ' ') || (*s == '\t')) && !(quoted || escaped)) {
1096                         /* If we are not already in whitespace, and not in a quoted string or
1097                            processing an escape sequence, and just entered whitespace, then
1098                            finalize the previous argument and remember that we are in whitespace
1099                         */
1100                         if (!whitespace) {
1101                                 *(cur++) = '\0';
1102                                 whitespace = 1;
1103                         }
1104                 } else if ((*s == '\\') && !escaped) {
1105                         escaped = 1;
1106                 } else {
1107                         if (whitespace) {
1108                                 /* If we are coming out of whitespace, start a new argument */
1109                                 if (x >= (max - 1)) {
1110                                         ast_log(LOG_WARNING, "Too many arguments, truncating\n");
1111                                         break;
1112                                 }
1113                                 argv[x++] = cur;
1114                                 whitespace = 0;
1115                         }
1116                         *(cur++) = *s;
1117                         escaped = 0;
1118                 }
1119                 s++;
1120         }
1121         /* Null terminate */
1122         *(cur++) = '\0';
1123         argv[x] = NULL;
1124         *argc = x;
1125
1126         return dup;
1127 }
1128
1129 /* This returns the number of unique matches for the generator */
1130 int ast_cli_generatornummatches(char *text, char *word)
1131 {
1132         int matches = 0, i = 0;
1133         char *buf = NULL, *oldbuf = NULL;
1134
1135         while ( (buf = ast_cli_generator(text, word, i)) ) {
1136                 if (++i > 1 && strcmp(buf,oldbuf) == 0)  {
1137                                 continue;
1138                 }
1139                 oldbuf = buf;
1140                 matches++;
1141         }
1142
1143         return matches;
1144 }
1145
1146 char **ast_cli_completion_matches(char *text, char *word)
1147 {
1148         char **match_list = NULL, *retstr, *prevstr;
1149         size_t match_list_len, max_equal, which, i;
1150         int matches = 0;
1151
1152         match_list_len = 1;
1153         while ((retstr = ast_cli_generator(text, word, matches)) != NULL) {
1154                 if (matches + 1 >= match_list_len) {
1155                         match_list_len <<= 1;
1156                         match_list = realloc(match_list, match_list_len * sizeof(char *));
1157                 }
1158                 match_list[++matches] = retstr;
1159         }
1160
1161         if (!match_list)
1162                 return (char **) NULL;
1163
1164         which = 2;
1165         prevstr = match_list[1];
1166         max_equal = strlen(prevstr);
1167         for (; which <= matches; which++) {
1168                 for (i = 0; i < max_equal && toupper(prevstr[i]) == toupper(match_list[which][i]); i++)
1169                         continue;
1170                 max_equal = i;
1171         }
1172
1173         retstr = malloc(max_equal + 1);
1174         (void) strncpy(retstr, match_list[1], max_equal);
1175         retstr[max_equal] = '\0';
1176         match_list[0] = retstr;
1177
1178         if (matches + 1 >= match_list_len)
1179                 match_list = realloc(match_list, (match_list_len + 1) * sizeof(char *));
1180         match_list[matches + 1] = (char *) NULL;
1181
1182         return (match_list);
1183 }
1184
1185 static char *__ast_cli_generator(char *text, char *word, int state, int lock)
1186 {
1187         char *argv[AST_MAX_ARGS];
1188         struct ast_cli_entry *e, *e1, *e2;
1189         int x;
1190         int matchnum=0;
1191         char *dup, *res;
1192         char fullcmd1[80] = "";
1193         char fullcmd2[80] = "";
1194         char matchstr[80];
1195         char *fullcmd = NULL;
1196
1197         if ((dup = parse_args(text, &x, argv, sizeof(argv) / sizeof(argv[0])))) {
1198                 join(matchstr, sizeof(matchstr), argv);
1199                 if (lock)
1200                         ast_mutex_lock(&clilock);
1201                 e1 = builtins;
1202                 e2 = helpers;
1203                 while(e1->cmda[0] || e2) {
1204                         if (e2)
1205                                 join(fullcmd2, sizeof(fullcmd2), e2->cmda);
1206                         if (e1->cmda[0])
1207                                 join(fullcmd1, sizeof(fullcmd1), e1->cmda);
1208                         if (!e1->cmda[0] || 
1209                                         (e2 && (strcmp(fullcmd2, fullcmd1) < 0))) {
1210                                 /* Use e2 */
1211                                 e = e2;
1212                                 fullcmd = fullcmd2;
1213                                 /* Increment by going to next */
1214                                 e2 = e2->next;
1215                         } else {
1216                                 /* Use e1 */
1217                                 e = e1;
1218                                 fullcmd = fullcmd1;
1219                                 e1++;
1220                         }
1221                         if ((fullcmd[0] != '_') && !strncasecmp(matchstr, fullcmd, strlen(matchstr))) {
1222                                 /* We contain the first part of one or more commands */
1223                                 /* Now, what we're supposed to return is the next word... */
1224                                 if (!ast_strlen_zero(word) && x>0) {
1225                                         res = e->cmda[x-1];
1226                                 } else {
1227                                         res = e->cmda[x];
1228                                 }
1229                                 if (res) {
1230                                         matchnum++;
1231                                         if (matchnum > state) {
1232                                                 if (lock)
1233                                                         ast_mutex_unlock(&clilock);
1234                                                 free(dup);
1235                                                 return strdup(res);
1236                                         }
1237                                 }
1238                         }
1239                         if (e->generator && !strncasecmp(matchstr, fullcmd, strlen(fullcmd))) {
1240                                 /* We have a command in its entirity within us -- theoretically only one
1241                                    command can have this occur */
1242                                 fullcmd = e->generator(matchstr, word, (!ast_strlen_zero(word) ? (x - 1) : (x)), state);
1243                                 if (fullcmd) {
1244                                         if (lock)
1245                                                 ast_mutex_unlock(&clilock);
1246                                         free(dup);
1247                                         return fullcmd;
1248                                 }
1249                         }
1250                         
1251                 }
1252                 if (lock)
1253                         ast_mutex_unlock(&clilock);
1254                 free(dup);
1255         }
1256         return NULL;
1257 }
1258
1259 char *ast_cli_generator(char *text, char *word, int state)
1260 {
1261         return __ast_cli_generator(text, word, state, 1);
1262 }
1263
1264 int ast_cli_command(int fd, char *s)
1265 {
1266         char *argv[AST_MAX_ARGS];
1267         struct ast_cli_entry *e;
1268         int x;
1269         char *dup;
1270
1271         if ((dup = parse_args(s, &x, argv, sizeof(argv) / sizeof(argv[0])))) {
1272                 /* We need at least one entry, or ignore */
1273                 if (x > 0) {
1274                         ast_mutex_lock(&clilock);
1275                         e = find_cli(argv, 0);
1276                         if (e)
1277                                 e->inuse++;
1278                         ast_mutex_unlock(&clilock);
1279                         if (e) {
1280                                 switch(e->handler(fd, x, argv)) {
1281                                 case RESULT_SHOWUSAGE:
1282                                         ast_cli(fd, e->usage);
1283                                         break;
1284                                 }
1285                         } else 
1286                                 ast_cli(fd, "No such command '%s' (type 'help' for help)\n", find_best(argv));
1287                         if (e) {
1288                                 ast_mutex_lock(&clilock);
1289                                 e->inuse--;
1290                                 ast_mutex_unlock(&clilock);
1291                         }
1292                 }
1293                 free(dup);
1294         } else {
1295                 ast_log(LOG_WARNING, "Out of memory\n");        
1296                 return -1;
1297         }
1298         return 0;
1299 }