(closes issue #10271)
[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 <unistd.h>
32 #include <string.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <stdio.h>
36 #include <errno.h>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39
40 #include "asterisk/lock.h"
41 #include "asterisk/file.h"
42 #include "asterisk/cli.h"
43 #include "asterisk/logger.h"
44 #include "asterisk/config.h"
45 #include "asterisk/channel.h"
46 #include "asterisk/pbx.h"
47 #include "asterisk/module.h"
48 #include "asterisk/translate.h"
49 #include "asterisk/indications.h"
50 #include "asterisk/utils.h"
51
52 /* Globals */
53 static const char config[] = "indications.conf";
54
55 /*
56  * Help for commands provided by this module ...
57  */
58 static char help_add_indication[] =
59 "Usage: indication add <country> <indication> \"<tonelist>\"\n"
60 "       Add the given indication to the country.\n";
61
62 static char help_remove_indication[] =
63 "Usage: indication remove <country> <indication>\n"
64 "       Remove the given indication from the country.\n";
65
66 static char help_show_indications[] =
67 "Usage: indication show [<country> ...]\n"
68 "       Display either a condensed for of all country/indications, or the\n"
69 "       indications for the specified countries.\n";
70
71 char *playtones_desc=
72 "PlayTones(arg): Plays a tone list. Execution will continue with the next step immediately,\n"
73 "while the tones continue to play.\n"
74 "Arg is either the tone name defined in the indications.conf configuration file, or a directly\n"
75 "specified list of frequencies and durations.\n"
76 "See the sample indications.conf for a description of the specification of a tonelist.\n\n"
77 "Use the StopPlayTones application to stop the tones playing. \n";
78
79 /*
80  * Implementation of functions provided by this module
81  */
82
83 /*!
84  * \brief Add a country to indication
85  * \param fd file descriptor of CLI
86  * \param argc no of args
87  * \param argv arguements
88  */
89 static int handle_add_indication(int fd, int argc, char *argv[])
90 {
91         struct ind_tone_zone *tz;
92         int created_country = 0;
93         if (argc != 5) return RESULT_SHOWUSAGE;
94
95         tz = ast_get_indication_zone(argv[2]);
96         if (!tz) {
97                 /* country does not exist, create it */
98                 ast_log(LOG_NOTICE, "Country '%s' does not exist, creating it.\n",argv[2]);
99                 
100                 if (!(tz = ast_calloc(1, sizeof(*tz)))) {
101                         return -1;
102                 }
103                 ast_copy_string(tz->country,argv[2],sizeof(tz->country));
104                 if (ast_register_indication_country(tz)) {
105                         ast_log(LOG_WARNING, "Unable to register new country\n");
106                         ast_free(tz);
107                         return -1;
108                 }
109                 created_country = 1;
110         }
111         if (ast_register_indication(tz,argv[3],argv[4])) {
112                 ast_log(LOG_WARNING, "Unable to register indication %s/%s\n",argv[2],argv[3]);
113                 if (created_country)
114                         ast_unregister_indication_country(argv[2]);
115                 return -1;
116         }
117         return 0;
118 }
119
120 /*!
121  * \brief Remove a country from indication
122  * \param fd file descriptor of CLI
123  * \param argc no of args
124  * \param argv arguements
125  */
126 static int handle_remove_indication(int fd, int argc, char *argv[])
127 {
128         struct ind_tone_zone *tz;
129         if (argc != 3 && argc != 4) return RESULT_SHOWUSAGE;
130
131         if (argc == 3) {
132                 /* remove entiry country */
133                 if (ast_unregister_indication_country(argv[2])) {
134                         ast_log(LOG_WARNING, "Unable to unregister indication country %s\n",argv[2]);
135                         return -1;
136                 }
137                 return 0;
138         }
139
140         tz = ast_get_indication_zone(argv[2]);
141         if (!tz) {
142                 ast_log(LOG_WARNING, "Unable to unregister indication %s/%s, country does not exists\n",argv[2],argv[3]);
143                 return -1;
144         }
145         if (ast_unregister_indication(tz,argv[3])) {
146                 ast_log(LOG_WARNING, "Unable to unregister indication %s/%s\n",argv[2],argv[3]);
147                 return -1;
148         }
149         return 0;
150 }
151
152 /*!
153  * \brief Show the current indications
154  * \param fd file descriptor of CLI
155  * \param argc no of args
156  * \param argv arguements
157  */
158 static int handle_show_indications(int fd, int argc, char *argv[])
159 {
160         struct ind_tone_zone *tz = NULL;
161         char buf[256];
162         int found_country = 0;
163
164         if (argc == 2) {
165                 /* no arguments, show a list of countries */
166                 ast_cli(fd,"Country Alias   Description\n"
167                            "===========================\n");
168                 while ( (tz = ast_walk_indications(tz) ) )
169                         ast_cli(fd,"%-7.7s %-7.7s %s\n", tz->country, tz->alias, tz->description);
170                 return 0;
171         }
172         /* there was a request for specific country(ies), lets humor them */
173         while ( (tz = ast_walk_indications(tz) ) ) {
174                 int i,j;
175                 for (i=2; i<argc; i++) {
176                         if (strcasecmp(tz->country,argv[i])==0 &&
177                             !tz->alias[0]) {
178                                 struct ind_tone_zone_sound* ts;
179                                 if (!found_country) {
180                                         found_country = 1;
181                                         ast_cli(fd,"Country Indication      PlayList\n"
182                                                    "=====================================\n");
183                                 }
184                                 j = snprintf(buf,sizeof(buf),"%-7.7s %-15.15s ",tz->country,"<ringcadence>");
185                                 for (i=0; i<tz->nrringcadence; i++) {
186                                         j += snprintf(buf+j,sizeof(buf)-j,"%d,",tz->ringcadence[i]);
187                                 }
188                                 if (tz->nrringcadence)
189                                         j--;
190                                 ast_copy_string(buf+j,"\n",sizeof(buf)-j);
191                                 ast_cli(fd,buf);
192                                 for (ts=tz->tones; ts; ts=ts->next)
193                                         ast_cli(fd,"%-7.7s %-15.15s %s\n",tz->country,ts->name,ts->data);
194                                 break;
195                         }
196                 }
197         }
198         if (!found_country)
199                 ast_cli(fd,"No countries matched your criteria.\n");
200         return -1;
201 }
202
203 /*!
204  * \brief play tone for indication country
205  * \param chan ast_channel to play the sounds back to
206  * \param data contains tone to play
207  */
208 static int handle_playtones(struct ast_channel *chan, void *data)
209 {
210         struct ind_tone_zone_sound *ts;
211         int res;
212
213         if (!data || !((char*)data)[0]) {
214                 ast_log(LOG_NOTICE,"Nothing to play\n");
215                 return -1;
216         }
217         ts = ast_get_indication_tone(chan->zone, (const char*)data);
218         if (ts && ts->data[0])
219                 res = ast_playtones_start(chan, 0, ts->data, 0);
220         else
221                 res = ast_playtones_start(chan, 0, (const char*)data, 0);
222         if (res)
223                 ast_log(LOG_NOTICE,"Unable to start playtones\n");
224         return res;
225 }
226
227 /*!
228  * \brief Stop tones playing
229  * \param chan 
230  * \param data 
231  */
232 static int handle_stopplaytones(struct ast_channel *chan, void *data)
233 {
234         ast_playtones_stop(chan);
235         return 0;
236 }
237
238 /*! \brief load indications module */
239 static int ind_load_module(void)
240 {
241         struct ast_config *cfg;
242         struct ast_variable *v;
243         char *cxt;
244         char *c;
245         struct ind_tone_zone *tones;
246         const char *country = NULL;
247
248         /* that the following cast is needed, is yuk! */
249         /* yup, checked it out. It is NOT written to. */
250         cfg = ast_config_load((char *)config);
251         if (!cfg)
252                 return -1;
253
254         /* Use existing config to populate the Indication table */
255         cxt = ast_category_browse(cfg, NULL);
256         while(cxt) {
257                 /* All categories but "general" are considered countries */
258                 if (!strcasecmp(cxt, "general")) {
259                         cxt = ast_category_browse(cfg, cxt);
260                         continue;
261                 }               
262                 if (!(tones = ast_calloc(1, sizeof(*tones)))) {
263                         ast_config_destroy(cfg);
264                         return -1;
265                 }
266                 ast_copy_string(tones->country,cxt,sizeof(tones->country));
267
268                 v = ast_variable_browse(cfg, cxt);
269                 while(v) {
270                         if (!strcasecmp(v->name, "description")) {
271                                 ast_copy_string(tones->description, v->value, sizeof(tones->description));
272                         } else if ((!strcasecmp(v->name,"ringcadence"))||(!strcasecmp(v->name,"ringcadance"))) {
273                                 char *ring,*rings = ast_strdupa(v->value);
274                                 c = rings;
275                                 ring = strsep(&c,",");
276                                 while (ring) {
277                                         int *tmp, val;
278                                         if (!isdigit(ring[0]) || (val=atoi(ring))==-1) {
279                                                 ast_log(LOG_WARNING,"Invalid ringcadence given '%s' at line %d.\n",ring,v->lineno);
280                                                 ring = strsep(&c,",");
281                                                 continue;
282                                         }                                       
283                                         if (!(tmp = ast_realloc(tones->ringcadence, (tones->nrringcadence + 1) * sizeof(int)))) {
284                                                 ast_config_destroy(cfg);
285                                                 return -1;
286                                         }
287                                         tones->ringcadence = tmp;
288                                         tmp[tones->nrringcadence] = val;
289                                         tones->nrringcadence++;
290                                         /* next item */
291                                         ring = strsep(&c,",");
292                                 }
293                         } else if (!strcasecmp(v->name,"alias")) {
294                                 char *countries = ast_strdupa(v->value);
295                                 c = countries;
296                                 country = strsep(&c,",");
297                                 while (country) {
298                                         struct ind_tone_zone* azone;
299                                         if (!(azone = ast_calloc(1, sizeof(*azone)))) {
300                                                 ast_config_destroy(cfg);
301                                                 return -1;
302                                         }
303                                         ast_copy_string(azone->country, country, sizeof(azone->country));
304                                         ast_copy_string(azone->alias, cxt, sizeof(azone->alias));
305                                         if (ast_register_indication_country(azone)) {
306                                                 ast_log(LOG_WARNING, "Unable to register indication alias at line %d.\n",v->lineno);
307                                                 ast_free(tones);
308                                         }
309                                         /* next item */
310                                         country = strsep(&c,",");
311                                 }
312                         } else {
313                                 /* add tone to country */
314                                 struct ind_tone_zone_sound *ps,*ts;
315                                 for (ps=NULL,ts=tones->tones; ts; ps=ts, ts=ts->next) {
316                                         if (strcasecmp(v->name,ts->name)==0) {
317                                                 /* already there */
318                                                 ast_log(LOG_NOTICE,"Duplicate entry '%s', skipped.\n",v->name);
319                                                 goto out;
320                                         }
321                                 }
322                                 /* not there, add it to the back */                             
323                                 if (!(ts = ast_malloc(sizeof(*ts)))) {
324                                         ast_config_destroy(cfg);
325                                         return -1;
326                                 }
327                                 ts->next = NULL;
328                                 ts->name = ast_strdup(v->name);
329                                 ts->data = ast_strdup(v->value);
330                                 if (ps)
331                                         ps->next = ts;
332                                 else
333                                         tones->tones = ts;
334                         }
335 out:                    v = v->next;
336                 }
337                 if (tones->description[0] || tones->alias[0] || tones->tones) {
338                         if (ast_register_indication_country(tones)) {
339                                 ast_log(LOG_WARNING, "Unable to register indication at line %d.\n",v->lineno);
340                                 ast_free(tones);
341                         }
342                 } else ast_free(tones);
343
344                 cxt = ast_category_browse(cfg, cxt);
345         }
346
347         /* determine which country is the default */
348         country = ast_variable_retrieve(cfg,"general","country");
349         if (!country || !*country || ast_set_indication_country(country))
350                 ast_log(LOG_WARNING,"Unable to set the default country (for indication tones)\n");
351
352         ast_config_destroy(cfg);
353         return 0;
354 }
355
356 /*! \brief CLI entries for commands provided by this module */
357 static struct ast_cli_entry cli_indications[] = {
358         { { "indication", "add", NULL },
359         handle_add_indication, "Add the given indication to the country",
360         help_add_indication, NULL },
361
362         { { "indication", "remove", NULL },
363         handle_remove_indication, "Remove the given indication from the country",
364         help_remove_indication, NULL },
365
366         { { "indication", "show", NULL },
367         handle_show_indications, "Display a list of all countries/indications",
368         help_show_indications },
369 };
370
371 /*! \brief Unload indicators module */
372 static int unload_module(void)
373 {
374         /* remove the registed indications... */
375         ast_unregister_indication_country(NULL);
376
377         /* and the functions */
378         ast_cli_unregister_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
379         ast_unregister_application("PlayTones");
380         ast_unregister_application("StopPlayTones");
381         return 0;
382 }
383
384
385 /*! \brief Load indications module */
386 static int load_module(void)
387 {
388         if (ind_load_module())
389                 return AST_MODULE_LOAD_DECLINE; 
390         ast_cli_register_multiple(cli_indications, sizeof(cli_indications) / sizeof(struct ast_cli_entry));
391         ast_register_application("PlayTones", handle_playtones, "Play a tone list", playtones_desc);
392         ast_register_application("StopPlayTones", handle_stopplaytones, "Stop playing a tone list","Stop playing a tone list");
393
394         return 0;
395 }
396
397 /*! \brief Reload indications module */
398 static int reload(void)
399 {
400         /* remove the registed indications... */
401         ast_unregister_indication_country(NULL);
402
403         return ind_load_module();
404 }
405
406 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS, "Region-specific tones",
407                 .load = load_module,
408                 .unload = unload_module,
409                 .reload = reload,
410                );