8404d526916e94dfb8cbd1f15b6bac77c902bdf4
[asterisk/asterisk.git] / apps / app_directory.c
1 /*
2  * Asterisk -- A telephony toolkit for Linux.
3  *
4  * Provide a directory of extensions
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 <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/module.h>
20 #include <asterisk/config.h>
21 #include <asterisk/say.h>
22 #include <asterisk/utils.h>
23 #include <string.h>
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <stdio.h>
27 #include "../asterisk.h"
28 #include "../astconf.h"
29
30 static char *tdesc = "Extension Directory";
31 static char *app = "Directory";
32
33 static char *synopsis = "Provide directory of voicemail extensions";
34 static char *descrip =
35 "  Directory(vm-context[|dial-context[|options]]): Presents the user with a directory\n"
36 "of extensions from which they  may  select  by name. The  list  of  names \n"
37 "and  extensions  is discovered from  voicemail.conf. The  vm-context  argument\n"
38 "is required, and specifies  the  context  of voicemail.conf to use.  The\n"
39 "dial-context is the context to use for dialing the users, and defaults to\n"
40 "the vm-context if unspecified. The 'f' option causes the directory to match\n"
41 "based on the first name in voicemail.conf instead of the last name.\n"
42 "Returns 0 unless the user hangs up. It  also sets up the channel on exit\n"
43 "to enter the extension the user selected.  If the user enters '0' and there\n"
44 "exists an extension 'o' in the current context, the directory will exit with 0\n"
45 "and call control will resume at that extension.  Entering '*' will exit similarly,\n"
46 "but to the 'a' extension, much like app_voicemail's behavior.\n";
47
48 /* For simplicity, I'm keeping the format compatible with the voicemail config,
49    but i'm open to suggestions for isolating it */
50
51 #define DIRECTORY_CONFIG "voicemail.conf"
52
53 /* How many digits to read in */
54 #define NUMDIGITS 3
55
56 STANDARD_LOCAL_USER;
57
58 LOCAL_USER_DECL;
59
60 static char *convert(char *lastname)
61 {
62         char *tmp;
63         int lcount = 0;
64         tmp = malloc(NUMDIGITS + 1);
65         if (tmp) {
66                 while((*lastname > 32) && lcount < NUMDIGITS) {
67                         switch(toupper(*lastname)) {
68                         case '1':
69                                 tmp[lcount++] = '1';
70                                 break;
71                         case '2':
72                         case 'A':
73                         case 'B':
74                         case 'C':
75                                 tmp[lcount++] = '2';
76                                 break;
77                         case '3':
78                         case 'D':
79                         case 'E':
80                         case 'F':
81                                 tmp[lcount++] = '3';
82                                 break;
83                         case '4':
84                         case 'G':
85                         case 'H':
86                         case 'I':
87                                 tmp[lcount++] = '4';
88                                 break;
89                         case '5':
90                         case 'J':
91                         case 'K':
92                         case 'L':
93                                 tmp[lcount++] = '5';
94                                 break;
95                         case '6':
96                         case 'M':
97                         case 'N':
98                         case 'O':
99                                 tmp[lcount++] = '6';
100                                 break;
101                         case '7':
102                         case 'P':
103                         case 'Q':
104                         case 'R':
105                         case 'S':
106                                 tmp[lcount++] = '7';
107                                 break;
108                         case '8':
109                         case 'T':
110                         case 'U':
111                         case 'V':
112                                 tmp[lcount++] = '8';
113                                 break;
114                         case '9':
115                         case 'W':
116                         case 'X':
117                         case 'Y':
118                         case 'Z':
119                                 tmp[lcount++] = '9';
120                                 break;
121                         }
122                         lastname++;
123                 }
124                 tmp[lcount] = '\0';
125         }
126         return tmp;
127 }
128
129 /* play name of mailbox owner.
130  * returns:  -1 for bad or missing extension
131  *           '1' for selected entry from directory
132  *           '*' for skipped entry from directory
133  */
134 static int play_mailbox_owner(struct ast_channel *chan, char *context, char *dialcontext, char *ext, char *name) {
135         int res = 0;
136         int loop = 3;
137         char fn[256];
138         char fn2[256];
139
140         /* Check for the VoiceMail2 greeting first */
141         snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
142                 (char *)ast_config_AST_SPOOL_DIR, context, ext);
143
144         /* Otherwise, check for an old-style Voicemail greeting */
145         snprintf(fn2, sizeof(fn2), "%s/vm/%s/greet",
146                 (char *)ast_config_AST_SPOOL_DIR, ext);
147
148         if (ast_fileexists(fn, NULL, chan->language) > 0) {
149                 res = ast_streamfile(chan, fn, chan->language);
150                 if (!res) {
151                         res = ast_waitstream(chan, AST_DIGIT_ANY);
152                 }
153                 ast_stopstream(chan);
154         } else if (ast_fileexists(fn2, NULL, chan->language) > 0) {
155                 res = ast_streamfile(chan, fn2, chan->language);
156                 if (!res) {
157                         res = ast_waitstream(chan, AST_DIGIT_ANY);
158                 }
159                 ast_stopstream(chan);
160         } else {
161                 res = ast_say_character_str(chan, !ast_strlen_zero(name) ? name : ext,
162                                         AST_DIGIT_ANY, chan->language);
163         }
164
165         while (loop) {
166                 if (!res) {
167                         res = ast_streamfile(chan, "dir-instr", chan->language);
168                 }
169                 if (!res) {
170                         res = ast_waitstream(chan, AST_DIGIT_ANY);
171                 }
172                 if (!res) {
173                         res = ast_waitfordigit(chan, 3000);
174                 }
175                 ast_stopstream(chan);
176         
177                 if (res > -1) {
178                         switch (res) {
179                                 case '1':
180                                         /* Name selected */
181                                         loop = 0;
182                                         if (ast_exists_extension(chan,dialcontext,ext,1,chan->cid.cid_num)) {
183                                                 strncpy(chan->exten, ext, sizeof(chan->exten)-1);
184                                                 chan->priority = 0;
185                                                 strncpy(chan->context, dialcontext, sizeof(chan->context)-1);
186                                         } else {
187                                                 ast_log(LOG_WARNING,
188                                                         "Can't find extension '%s' in context '%s'.  "
189                                                         "Did you pass the wrong context to Directory?\n",
190                                                         ext, dialcontext);
191                                                 res = -1;
192                                         }
193                                         break;
194         
195                                 case '*':   
196                                         /* Skip to next match in list */
197                                         loop = 0;
198                                         break;
199         
200                                 default:
201                                         /* Not '1', or '*', so decrement number of tries */
202                                         res = 0;
203                                         loop--;
204                                         break;
205                         } /* end switch */
206                 } /* end if */
207                 else {
208                         /* User hungup, so jump out now */
209                         loop = 0;
210                 }
211         } /* end while */
212
213         return(res);
214 }
215
216 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last)
217 {
218         /* Read in the first three digits..  "digit" is the first digit, already read */
219         char ext[NUMDIGITS + 1];
220         char name[80] = "";
221         struct ast_variable *v;
222         int res;
223         int found=0;
224         int lastuserchoice = 0;
225         char *start, *pos, *conv,*stringp=NULL;
226
227         if (!context || ast_strlen_zero(context)) {
228                 ast_log(LOG_WARNING,
229                         "Directory must be called with an argument "
230                         "(context in which to interpret extensions)\n");
231                 return -1;
232         }
233         if (digit == '0') {
234                 if (ast_exists_extension(chan,chan->context,"o",1,chan->cid.cid_num) || 
235                         (!ast_strlen_zero(chan->macrocontext) &&
236                      ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num))) {
237                         strncpy(chan->exten, "o", sizeof(chan->exten)-1);
238                         chan->priority = 0;
239                         return 0;
240                 } else {
241
242                         ast_log(LOG_WARNING, "Can't find extension 'o' in current context.  "
243                                 "Not Exiting the Directory!\n");
244                         res = 0;
245                 }
246         }       
247         if (digit == '*') {
248                 if (ast_exists_extension(chan,chan->context,"a",1,chan->cid.cid_num) || 
249                         (!ast_strlen_zero(chan->macrocontext) &&
250                      ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num))) {
251                         strncpy(chan->exten, "a", sizeof(chan->exten)-1);
252                         chan->priority = 0;
253                         return 0;
254                 } else {
255
256                         ast_log(LOG_WARNING, "Can't find extension 'a' in current context.  "
257                                 "Not Exiting the Directory!\n");
258                         res = 0;
259                 }
260         }       
261         memset(ext, 0, sizeof(ext));
262         ext[0] = digit;
263         res = 0;
264         if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
265         if (!res) {
266                 /* Search for all names which start with those digits */
267                 v = ast_variable_browse(cfg, context);
268                 while(v && !res) {
269                         /* Find all candidate extensions */
270                         while(v) {
271                                 /* Find a candidate extension */
272                                 start = strdup(v->value);
273                                 if (start) {
274                                         stringp=start;
275                                         strsep(&stringp, ",");
276                                         pos = strsep(&stringp, ",");
277                                         if (pos) {
278                                                 strncpy(name, pos, sizeof(name) - 1);
279                                                 /* Grab the last name */
280                                                 if (last && strrchr(pos,' '))
281                                                         pos = strrchr(pos, ' ') + 1;
282                                                 conv = convert(pos);
283                                                 if (conv) {
284                                                         if (!strcmp(conv, ext)) {
285                                                                 /* Match! */
286                                                                 found++;
287                                                                 free(conv);
288                                                                 free(start);
289                                                                 break;
290                                                         }
291                                                         free(conv);
292                                                 }
293                                         }
294                                         free(start);
295                                 }
296                                 v = v->next;
297                         }
298
299                         if (v) {
300                                 /* We have a match -- play a greeting if they have it */
301                                 res = play_mailbox_owner(chan, context, dialcontext, v->name, name);
302                                 switch (res) {
303                                         case -1:
304                                                 /* user pressed '1' but extension does not exist, or
305                                                  * user hungup
306                                                  */
307                                                 lastuserchoice = 0;
308                                                 break;
309                                         case '1':
310                                                 /* user pressed '1' and extensions exists */
311                                                 lastuserchoice = res;
312                                                 strncpy(chan->context, dialcontext, sizeof(chan->context) - 1);
313                                                 strncpy(chan->exten, v->name, sizeof(chan->exten) - 1);
314                                                 chan->priority = 0;
315                                                 break;
316                                         case '*':
317                                                 /* user pressed '*' to skip something found */
318                                                 lastuserchoice = res;
319                                                 res = 0;
320                                                 break;
321                                         default:
322                                                 break;
323                                 }
324                                 v = v->next;
325                         }
326                 }
327
328                 if (lastuserchoice != '1') {
329                         if (found) 
330                                 res = ast_streamfile(chan, "dir-nomore", chan->language);
331                         else
332                                 res = ast_streamfile(chan, "dir-nomatch", chan->language);
333                         if (!res)
334                                 res = 1;
335                         return res;
336                 }
337                 return 0;
338         }
339         return res;
340 }
341
342 static int directory_exec(struct ast_channel *chan, void *data)
343 {
344         int res = 0;
345         struct localuser *u;
346         struct ast_config *cfg;
347         int last = 1;
348         char *context, *dialcontext, *dirintro, *options;
349
350         if (!data) {
351                 ast_log(LOG_WARNING, "directory requires an argument (context[,dialcontext])\n");
352                 return -1;
353         }
354         cfg = ast_config_load(DIRECTORY_CONFIG);
355         if (!cfg) {
356                 ast_log(LOG_WARNING, "Unable to open directory configuration %s\n", DIRECTORY_CONFIG);
357                 return -1;
358         }
359         LOCAL_USER_ADD(u);
360 top:
361         context = ast_strdupa(data);
362         dialcontext = strchr(context, '|');
363         if (dialcontext) {
364                 *dialcontext = '\0';
365                 dialcontext++;
366                 options = strchr(dialcontext, '|');
367                 if (options) {
368                         *options = '\0';
369                         options++; 
370                         if (strchr(options, 'f'))
371                                 last = 0;
372                 }
373         } else  
374                 dialcontext = context;
375         dirintro = ast_variable_retrieve(cfg, context, "directoryintro");
376         if (!dirintro || ast_strlen_zero(dirintro))
377                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
378         if (!dirintro || ast_strlen_zero(dirintro)) {
379                 if (last)
380                         dirintro = "dir-intro"; 
381                 else
382                         dirintro = "dir-intro-fn";
383         }
384         if (chan->_state != AST_STATE_UP) 
385                 res = ast_answer(chan);
386         if (!res)
387                 res = ast_streamfile(chan, dirintro, chan->language);
388         if (!res)
389                 res = ast_waitstream(chan, AST_DIGIT_ANY);
390         ast_stopstream(chan);
391         if (!res)
392                 res = ast_waitfordigit(chan, 5000);
393         if (res > 0) {
394                 res = do_directory(chan, cfg, context, dialcontext, res, last);
395                 if (res > 0) {
396                         res = ast_waitstream(chan, AST_DIGIT_ANY);
397                         ast_stopstream(chan);
398                         if (res >= 0) {
399                                 goto top;
400                         }
401                 }
402         }
403         ast_config_destroy(cfg);
404         LOCAL_USER_REMOVE(u);
405         return res;
406 }
407
408 int unload_module(void)
409 {
410         STANDARD_HANGUP_LOCALUSERS;
411         return ast_unregister_application(app);
412 }
413
414 int load_module(void)
415 {
416         return ast_register_application(app, directory_exec, synopsis, descrip);
417 }
418
419 char *description(void)
420 {
421         return tdesc;
422 }
423
424 int usecount(void)
425 {
426         int res;
427         STANDARD_USECOUNT(res);
428         return res;
429 }
430
431 char *key()
432 {
433         return ASTERISK_GPL_KEY;
434 }