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