Add additional memory debugging to several core APIs, and fix several memory
[asterisk/asterisk.git] / res / res_indications.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2002, Pauline Middelink
5  *
6  *
7  * See http://www.asterisk.org for more information about
8  * the Asterisk project. Please do not directly contact
9  * any of the maintainers of this project for assistance;
10  * the project provides a web site, mailing lists and IRC
11  * channels for your use.
12  *
13  * This program is free software, distributed under the terms of
14  * the GNU General Public License Version 2. See the LICENSE file
15  * at the top of the source tree.
16  */
17
18 /*! \file res_indications.c 
19  *
20  * \brief Load the indications
21  * 
22  * \author Pauline Middelink <middelink@polyware.nl>
23  *
24  * Load the country specific dialtones into the asterisk PBX.
25  */
26  
27 #include "asterisk.h"
28
29 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
30
31 #include <ctype.h>
32 #include <sys/stat.h>
33
34 #include "asterisk/lock.h"
35 #include "asterisk/file.h"
36 #include "asterisk/cli.h"
37 #include "asterisk/config.h"
38 #include "asterisk/channel.h"
39 #include "asterisk/pbx.h"
40 #include "asterisk/module.h"
41 #include "asterisk/translate.h"
42 #include "asterisk/indications.h"
43 #include "asterisk/utils.h"
44
45 /* Globals */
46 static const char config[] = "indications.conf";
47
48 char *playtones_desc=
49 "  PlayTones(arg): Plays a tone list. Execution will continue with the next step immediately,\n"
50 "while the tones continue to play.\n"
51 "Arg is either the tone name defined in the indications.conf configuration file, or a directly\n"
52 "specified list of frequencies and durations.\n"
53 "See the sample indications.conf for a description of the specification of a tonelist.\n\n"
54 "Use the StopPlayTones application to stop the tones playing. \n";
55
56 /*
57  * Implementation of functions provided by this module
58  */
59
60 /*!
61  * \brief Add a country to indication
62  * \param e the ast_cli_entry for this CLI command
63  * \param cmd the reason we are being called
64  * \param a the arguments being passed to us
65  */
66 static char *handle_cli_indication_add(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
67 {
68         struct ind_tone_zone *tz;
69         int created_country = 0;
70
71         switch (cmd) {
72         case CLI_INIT:
73                 e->command = "indication add";
74                 e->usage =
75                         "Usage: indication add <country> <indication> \"<tonelist>\"\n"
76                         "       Add the given indication to the country.\n";
77                 return NULL;
78         case CLI_GENERATE:
79                 return NULL;
80         }
81
82         if (a->argc != 5)
83                 return CLI_SHOWUSAGE;
84
85         tz = ast_get_indication_zone(a->argv[2]);
86         if (!tz) {
87                 /* country does not exist, create it */
88                 ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n", a->argv[2]);
89                 
90                 if (!(tz = ast_calloc(1, sizeof(*tz)))) {
91                         return CLI_FAILURE;
92                 }
93                 ast_copy_string(tz->country, a->argv[2], sizeof(tz->country));
94                 if (ast_register_indication_country(tz)) {
95                         ast_log(LOG_WARNING, "Unable to register new country\n");
96                         ast_free(tz);
97                         return CLI_FAILURE;
98                 }
99                 created_country = 1;
100         }
101         if (ast_register_indication(tz, a->argv[3], a->argv[4])) {
102                 ast_log(LOG_WARNING, "Unable to register indication %s/%s\n", a->argv[2], a->argv[3]);
103                 if (created_country)
104                         ast_unregister_indication_country(a->argv[2]);
105                 return CLI_FAILURE;
106         }
107         return CLI_SUCCESS;
108 }
109
110 /*!
111  * \brief Remove a country from indication
112  * \param e the ast_cli_entry for this CLI command
113  * \param cmd the reason we are being called
114  * \param a the arguments being passed to us
115  */
116 static char *handle_cli_indication_remove(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
117 {
118         struct ind_tone_zone *tz;
119
120         switch (cmd) {
121         case CLI_INIT:
122                 e->command = "indication remove";
123                 e->usage =
124                         "Usage: indication remove <country> <indication>\n"
125                         "       Remove the given indication from the country.\n";
126                 return NULL;
127         case CLI_GENERATE:
128                 return NULL;
129         }
130
131         if (a->argc != 3 && a->argc != 4)
132                 return CLI_SHOWUSAGE;
133
134         if (a->argc == 3) {
135                 /* remove entiry country */
136                 if (ast_unregister_indication_country(a->argv[2])) {
137                         ast_log(LOG_WARNING, "Unable to unregister indication country %s\n", a->argv[2]);
138                         return CLI_FAILURE;
139                 }
140                 return CLI_SUCCESS;
141         }
142
143         tz = ast_get_indication_zone(a->argv[2]);
144         if (!tz) {
145                 ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n", a->argv[2], a->argv[3]);
146                 return CLI_FAILURE;
147         }
148         if (ast_unregister_indication(tz, a->argv[3])) {
149                 ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n", a->argv[2], a->argv[3]);
150                 return CLI_FAILURE;
151         }
152         return CLI_SUCCESS;
153 }
154
155 /*!
156  * \brief Show the current indications
157  * \param e the ast_cli_entry for this CLI command
158  * \param cmd the reason we are being called
159  * \param a the arguments being passed to us
160  */
161 static char *handle_cli_indication_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
162 {
163         struct ind_tone_zone *tz = NULL;
164         char buf[256];
165         int found_country = 0;
166
167         switch (cmd) {
168         case CLI_INIT:
169                 e->command = "indication show";
170                 e->usage =
171                         "Usage: indication show [<country> ...]\n"
172                         "       Display either a condensed for of all country/indications, or the\n"
173                         "       indications for the specified countries.\n";
174                 return NULL;
175         case CLI_GENERATE:
176                 return NULL;
177         }
178
179         if (a->argc == 2) {
180                 /* no arguments, show a list of countries */
181                 ast_cli(a->fd, "Country Alias   Description\n");
182                 ast_cli(a->fd, "===========================\n");
183                 while ((tz = ast_walk_indications(tz)))
184                         ast_cli(a->fd, "%-7.7s %-7.7s %s\n", tz->country, tz->alias, tz->description);
185                 return CLI_SUCCESS;
186         }
187         /* there was a request for specific country(ies), lets humor them */
188         while ((tz = ast_walk_indications(tz))) {
189                 int i, j;
190                 for (i = 2; i < a->argc; i++) {
191                         if (strcasecmp(tz->country, a->argv[i]) == 0 && !tz->alias[0]) {
192                                 struct ind_tone_zone_sound* ts;
193                                 if (!found_country) {
194                                         found_country = 1;
195                                         ast_cli(a->fd, "Country Indication      PlayList\n");
196                                         ast_cli(a->fd, "=====================================\n");
197                                 }
198                                 j = snprintf(buf, sizeof(buf), "%-7.7s %-15.15s ", tz->country, "<ringcadence>");
199                                 for (i = 0; i < tz->nrringcadence; i++) {
200                                         j += snprintf(buf + j, sizeof(buf) - j, "%d,", tz->ringcadence[i]);
201                                 }
202                                 if (tz->nrringcadence)
203                                         j--;
204                                 ast_copy_string(buf + j, "\n", sizeof(buf) - j);
205                                 ast_cli(a->fd, "%s", buf);
206                                 for (ts = tz->tones; ts; ts = ts->next)
207                                         ast_cli(a->fd, "%-7.7s %-15.15s %s\n", tz->country, ts->name, ts->data);
208                                 break;
209                         }
210                 }
211         }
212         if (!found_country)
213                 ast_cli(a->fd, "No countries matched your criteria.\n");
214         return CLI_SUCCESS;
215 }
216
217 /*!
218  * \brief play tone for indication country
219  * \param chan ast_channel to play the sounds back to
220  * \param data contains tone to play
221  */
222 static int handle_playtones(struct ast_channel *chan, void *data)
223 {
224         struct ind_tone_zone_sound *ts;
225         int res;
226
227         if (!data || !((char*)data)[0]) {
228                 ast_log(LOG_NOTICE,"Nothing to play\n");
229                 return -1;
230         }
231         ts = ast_get_indication_tone(chan->zone, (const char*)data);
232         if (ts && ts->data[0])
233                 res = ast_playtones_start(chan, 0, ts->data, 0);
234         else
235                 res = ast_playtones_start(chan, 0, (const char*)data, 0);
236         if (res)
237                 ast_log(LOG_NOTICE,"Unable to start playtones\n");
238         return res;
239 }
240
241 /*!
242  * \brief Stop tones playing
243  * \param chan 
244  * \param data 
245  */
246 static int handle_stopplaytones(struct ast_channel *chan, void *data)
247 {
248         ast_playtones_stop(chan);
249         return 0;
250 }
251
252 /* helper function to delete a tone_zone in its entirety */
253 static inline void free_zone(struct ind_tone_zone* zone)
254 {
255         while (zone->tones) {
256                 struct ind_tone_zone_sound *tmp = zone->tones->next;
257                 ast_free((void *)zone->tones->name);
258                 ast_free((void *)zone->tones->data);
259                 ast_free(zone->tones);
260                 zone->tones = tmp;
261         }
262
263         if (zone->ringcadence)
264                 ast_free(zone->ringcadence);
265
266         ast_free(zone);
267 }
268
269 /*! \brief load indications module */
270 static int ind_load_module(int reload)
271 {
272         struct ast_config *cfg;
273         struct ast_variable *v;
274         char *cxt;
275         char *c;
276         struct ind_tone_zone *tones;
277         const char *country = NULL;
278         struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
279
280         /* that the following cast is needed, is yuk! */
281         /* yup, checked it out. It is NOT written to. */
282         cfg = ast_config_load((char *)config, config_flags);
283         if (cfg == CONFIG_STATUS_FILEMISSING || cfg == CONFIG_STATUS_FILEINVALID) {
284                 return -1;
285         } else if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
286                 return 0;
287         }
288
289         if (reload)
290                 ast_unregister_indication_country(NULL);
291
292         /* Use existing config to populate the Indication table */
293         cxt = ast_category_browse(cfg, NULL);
294         while(cxt) {
295                 /* All categories but "general" are considered countries */
296                 if (!strcasecmp(cxt, "general")) {
297                         cxt = ast_category_browse(cfg, cxt);
298                         continue;
299                 }               
300                 if (!(tones = ast_calloc(1, sizeof(*tones)))) {
301                         ast_config_destroy(cfg);
302                         return -1;
303                 }
304                 ast_copy_string(tones->country,cxt,sizeof(tones->country));
305
306                 v = ast_variable_browse(cfg, cxt);
307                 while(v) {
308                         if (!strcasecmp(v->name, "description")) {
309                                 ast_copy_string(tones->description, v->value, sizeof(tones->description));
310                         } else if ((!strcasecmp(v->name,"ringcadence"))||(!strcasecmp(v->name,"ringcadance"))) {
311                                 char *ring,*rings = ast_strdupa(v->value);
312                                 c = rings;
313                                 ring = strsep(&c,",");
314                                 while (ring) {
315                                         int *tmp, val;
316                                         if (!isdigit(ring[0]) || (val=atoi(ring))==-1) {
317                                                 ast_log(LOG_WARNING,"Invalid ringcadence given '%s' at line %d.\n",ring,v->lineno);
318                                                 ring = strsep(&c,",");
319                                                 continue;
320                                         }                                       
321                                         if (!(tmp = ast_realloc(tones->ringcadence, (tones->nrringcadence + 1) * sizeof(int)))) {
322                                                 ast_config_destroy(cfg);
323                                                 free_zone(tones);
324                                                 return -1;
325                                         }
326                                         tones->ringcadence = tmp;
327                                         tmp[tones->nrringcadence] = val;
328                                         tones->nrringcadence++;
329                                         /* next item */
330                                         ring = strsep(&c,",");
331                                 }
332                         } else if (!strcasecmp(v->name,"alias")) {
333                                 char *countries = ast_strdupa(v->value);
334                                 c = countries;
335                                 country = strsep(&c,",");
336                                 while (country) {
337                                         struct ind_tone_zone* azone;
338                                         if (!(azone = ast_calloc(1, sizeof(*azone)))) {
339                                                 ast_config_destroy(cfg);
340                                                 free_zone(tones);
341                                                 return -1;
342                                         }
343                                         ast_copy_string(azone->country, country, sizeof(azone->country));
344                                         ast_copy_string(azone->alias, cxt, sizeof(azone->alias));
345                                         if (ast_register_indication_country(azone)) {
346                                                 ast_log(LOG_WARNING, "Unable to register indication alias at line %d.\n",v->lineno);
347                                                 free_zone(tones);
348                                         }
349                                         /* next item */
350                                         country = strsep(&c,",");
351                                 }
352                         } else {
353                                 /* add tone to country */
354                                 struct ind_tone_zone_sound *ps,*ts;
355                                 for (ps=NULL,ts=tones->tones; ts; ps=ts, ts=ts->next) {
356                                         if (strcasecmp(v->name,ts->name)==0) {
357                                                 /* already there */
358                                                 ast_log(LOG_NOTICE,"Duplicate entry '%s', skipped.\n",v->name);
359                                                 goto out;
360                                         }
361                                 }
362                                 /* not there, add it to the back */                             
363                                 if (!(ts = ast_malloc(sizeof(*ts)))) {
364                                         ast_config_destroy(cfg);
365                                         return -1;
366                                 }
367                                 ts->next = NULL;
368                                 ts->name = ast_strdup(v->name);
369                                 ts->data = ast_strdup(v->value);
370                                 if (ps)
371                                         ps->next = ts;
372                                 else
373                                         tones->tones = ts;
374                         }
375 out:                    v = v->next;
376                 }
377                 if (tones->description[0] || tones->alias[0] || tones->tones) {
378                         if (ast_register_indication_country(tones)) {
379                                 ast_log(LOG_WARNING, "Unable to register indication at line %d.\n",v->lineno);
380                                 free_zone(tones);
381                         }
382                 } else {
383                         free_zone(tones);
384                 }
385
386                 cxt = ast_category_browse(cfg, cxt);
387         }
388
389         /* determine which country is the default */
390         country = ast_variable_retrieve(cfg,"general","country");
391         if (ast_strlen_zero(country) || ast_set_indication_country(country)) {
392                 ast_log(LOG_WARNING,"Unable to set the default country (for indication tones)\n");
393         }
394
395         ast_config_destroy(cfg);
396         return 0;
397 }
398
399 /*! \brief CLI entries for commands provided by this module */
400 static struct ast_cli_entry cli_indications[] = {
401         AST_CLI_DEFINE(handle_cli_indication_add,    "Add the given indication to the country"),
402         AST_CLI_DEFINE(handle_cli_indication_remove, "Remove the given indication from the country"),
403         AST_CLI_DEFINE(handle_cli_indication_show,   "Display a list of all countries/indications")
404 };
405
406 /*! \brief Unload indicators module */
407 static int unload_module(void)
408 {
409         /* remove the registed indications... */
410         ast_unregister_indication_country(NULL);
411
412         /* and the functions */
413         ast_cli_unregister_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
414         ast_unregister_application("PlayTones");
415         ast_unregister_application("StopPlayTones");
416         return 0;
417 }
418
419
420 /*! \brief Load indications module */
421 static int load_module(void)
422 {
423         if (ind_load_module(0))
424                 return AST_MODULE_LOAD_DECLINE; 
425         ast_cli_register_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
426         ast_register_application("PlayTones", handle_playtones, "Play a tone list", playtones_desc);
427         ast_register_application("StopPlayTones", handle_stopplaytones, "Stop playing a tone list","  StopPlayTones(): Stop playing a tone list");
428
429         return AST_MODULE_LOAD_SUCCESS;
430 }
431
432 /*! \brief Reload indications module */
433 static int reload(void)
434 {
435         return ind_load_module(1);
436 }
437
438 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Region-specific tones",
439                 .load = load_module,
440                 .unload = unload_module,
441                 .reload = reload,
442                );