fix various typos and other bits (from Ian Kinner)
[asterisk/asterisk.git] / apps / app_directory.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Provide a directory of extensions
22  *
23  * \author Mark Spencer <markster@digium.com>
24  * 
25  * \ingroup applications
26  */
27  
28 #include <string.h>
29 #include <ctype.h>
30 #include <stdlib.h>
31 #include <stdio.h>
32
33 #include "asterisk.h"
34
35 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
36
37 #include "asterisk/lock.h"
38 #include "asterisk/file.h"
39 #include "asterisk/logger.h"
40 #include "asterisk/channel.h"
41 #include "asterisk/pbx.h"
42 #include "asterisk/module.h"
43 #include "asterisk/config.h"
44 #include "asterisk/say.h"
45 #include "asterisk/utils.h"
46 #include "asterisk/app.h"
47
48 static char *app = "Directory";
49
50 static char *synopsis = "Provide directory of voicemail extensions";
51 static char *descrip =
52 "  Directory(vm-context[|dial-context[|options]]): This application will present\n"
53 "the calling channel with a directory of extensions from which they can search\n"
54 "by name. The list of names and corresponding extensions is retrieved from the\n"
55 "voicemail configuration file, voicemail.conf.\n"
56 "  This application will immediately exit if one of the following DTMF digits are\n"
57 "received and the extension to jump to exists:\n"
58 "    0 - Jump to the 'o' extension, if it exists.\n"
59 "    * - Jump to the 'a' extension, if it exists.\n\n"
60 "  Parameters:\n"
61 "    vm-context   - This is the context within voicemail.conf to use for the\n"
62 "                   Directory.\n"
63 "    dial-context - This is the dialplan context to use when looking for an\n"
64 "                   extension that the user has selected, or when jumping to the\n"
65 "                   'o' or 'a' extension.\n\n"
66 "  Options:\n"
67 "    e - In addition to the name, also read the extension number to the\n"
68 "        caller before presenting dialing options.\n"
69 "    f - Allow the caller to enter the first name of a user in the directory\n"
70 "        instead of using the last name.\n";
71
72 /* For simplicity, I'm keeping the format compatible with the voicemail config,
73    but i'm open to suggestions for isolating it */
74
75 #define VOICEMAIL_CONFIG "voicemail.conf"
76
77 /* How many digits to read in */
78 #define NUMDIGITS 3
79
80 LOCAL_USER_DECL;
81
82 static char *convert(char *lastname)
83 {
84         char *tmp;
85         int lcount = 0;
86         tmp = ast_malloc(NUMDIGITS + 1);
87         if (tmp) {
88                 while((*lastname > 32) && lcount < NUMDIGITS) {
89                         switch(toupper(*lastname)) {
90                         case '1':
91                                 tmp[lcount++] = '1';
92                                 break;
93                         case '2':
94                         case 'A':
95                         case 'B':
96                         case 'C':
97                                 tmp[lcount++] = '2';
98                                 break;
99                         case '3':
100                         case 'D':
101                         case 'E':
102                         case 'F':
103                                 tmp[lcount++] = '3';
104                                 break;
105                         case '4':
106                         case 'G':
107                         case 'H':
108                         case 'I':
109                                 tmp[lcount++] = '4';
110                                 break;
111                         case '5':
112                         case 'J':
113                         case 'K':
114                         case 'L':
115                                 tmp[lcount++] = '5';
116                                 break;
117                         case '6':
118                         case 'M':
119                         case 'N':
120                         case 'O':
121                                 tmp[lcount++] = '6';
122                                 break;
123                         case '7':
124                         case 'P':
125                         case 'Q':
126                         case 'R':
127                         case 'S':
128                                 tmp[lcount++] = '7';
129                                 break;
130                         case '8':
131                         case 'T':
132                         case 'U':
133                         case 'V':
134                                 tmp[lcount++] = '8';
135                                 break;
136                         case '9':
137                         case 'W':
138                         case 'X':
139                         case 'Y':
140                         case 'Z':
141                                 tmp[lcount++] = '9';
142                                 break;
143                         }
144                         lastname++;
145                 }
146                 tmp[lcount] = '\0';
147         }
148         return tmp;
149 }
150
151 /* play name of mailbox owner.
152  * returns:  -1 for bad or missing extension
153  *           '1' for selected entry from directory
154  *           '*' for skipped entry from directory
155  */
156 static int play_mailbox_owner(struct ast_channel *chan, char *context,
157                 char *dialcontext, char *ext, char *name, int readext)
158 {
159         int res = 0;
160         int loop;
161         char fn[256];
162
163         /* Check for the VoiceMail2 greeting first */
164         snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
165                 ast_config_AST_SPOOL_DIR, context, ext);
166
167         if (ast_fileexists(fn, NULL, chan->language) <= 0) {
168                 /* no file, check for an old-style Voicemail greeting */
169                 snprintf(fn, sizeof(fn), "%s/vm/%s/greet",
170                         ast_config_AST_SPOOL_DIR, ext);
171         }
172
173         if (ast_fileexists(fn, NULL, chan->language) > 0) {
174                 res = ast_stream_and_wait(chan, fn, chan->language, AST_DIGIT_ANY);
175                 ast_stopstream(chan);
176                 /* If Option 'e' was specified, also read the extension number with the name */
177                 if (readext) {
178                         ast_stream_and_wait(chan, "vm-extension", chan->language, AST_DIGIT_ANY);
179                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
180                 }
181         } else {
182                 res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
183                 if (!ast_strlen_zero(name) && readext) {
184                         ast_stream_and_wait(chan, "vm-extension", chan->language, AST_DIGIT_ANY);
185                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
186                 }
187         }
188
189         for (loop = 3 ; loop > 0; loop--) {
190                 if (!res)
191                         res = ast_stream_and_wait(chan, "dir-instr", chan->language, AST_DIGIT_ANY);
192                 if (!res)
193                         res = ast_waitfordigit(chan, 3000);
194                 ast_stopstream(chan);
195         
196                 if (res < 0) /* User hungup, so jump out now */
197                         break;
198                 if (res == '1') {       /* Name selected */
199                         if (ast_goto_if_exists(chan, dialcontext, ext, 1)) {
200                                 ast_log(LOG_WARNING,
201                                         "Can't find extension '%s' in context '%s'.  "
202                                         "Did you pass the wrong context to Directory?\n",
203                                         ext, dialcontext);
204                                 res = -1;
205                         }
206                         break;
207                 }
208                 if (res == '*') /* Skip to next match in list */
209                         break;
210
211                 /* Not '1', or '*', so decrement number of tries */
212                 res = 0;
213         }
214
215         return(res);
216 }
217
218 static struct ast_config *realtime_directory(char *context)
219 {
220         struct ast_config *cfg;
221         struct ast_config *rtdata;
222         struct ast_category *cat;
223         struct ast_variable *var;
224         char *mailbox;
225         char *fullname;
226         char *hidefromdir;
227         char tmp[100];
228
229         /* Load flat file config. */
230         cfg = ast_config_load(VOICEMAIL_CONFIG);
231
232         if (!cfg) {
233                 /* Loading config failed. */
234                 ast_log(LOG_WARNING, "Loading config failed.\n");
235                 return NULL;
236         }
237
238         /* Get realtime entries, categorized by their mailbox number
239            and present in the requested context */
240         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
241
242         /* if there are no results, just return the entries from the config file */
243         if (!rtdata)
244                 return cfg;
245
246         /* Does the context exist within the config file? If not, make one */
247         cat = ast_category_get(cfg, context);
248         if (!cat) {
249                 cat = ast_category_new(context);
250                 if (!cat) {
251                         ast_log(LOG_WARNING, "Out of memory\n");
252                         ast_config_destroy(cfg);
253                         return NULL;
254                 }
255                 ast_category_append(cfg, cat);
256         }
257
258         mailbox = NULL;
259         while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
260                 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
261                 hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
262                 snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
263                          fullname ? fullname : "",
264                          hidefromdir ? hidefromdir : "no");
265                 var = ast_variable_new(mailbox, tmp);
266                 if (var)
267                         ast_variable_append(cat, var);
268                 else
269                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
270         }
271         ast_config_destroy(rtdata);
272
273         return cfg;
274 }
275
276 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, char *context, char *dialcontext, char digit, int last, int readext)
277 {
278         /* Read in the first three digits..  "digit" is the first digit, already read */
279         char ext[NUMDIGITS + 1];
280         char name[80] = "";
281         struct ast_variable *v;
282         int res;
283         int found=0;
284         int lastuserchoice = 0;
285         char *start, *pos, *conv,*stringp=NULL;
286
287         if (ast_strlen_zero(context)) {
288                 ast_log(LOG_WARNING,
289                         "Directory must be called with an argument "
290                         "(context in which to interpret extensions)\n");
291                 return -1;
292         }
293         if (digit == '0') {
294                 if (!ast_goto_if_exists(chan, chan->context, "o", 1) ||
295                     (!ast_strlen_zero(chan->macrocontext) &&
296                      !ast_goto_if_exists(chan, chan->macrocontext, "o", 1))) {
297                         return 0;
298                 } else {
299                         ast_log(LOG_WARNING, "Can't find extension 'o' in current context.  "
300                                 "Not Exiting the Directory!\n");
301                         res = 0;
302                 }
303         }       
304         if (digit == '*') {
305                 if (!ast_goto_if_exists(chan, chan->context, "a", 1) ||
306                     (!ast_strlen_zero(chan->macrocontext) &&
307                      !ast_goto_if_exists(chan, chan->macrocontext, "a", 1))) {
308                         return 0;
309                 } else {
310                         ast_log(LOG_WARNING, "Can't find extension 'a' in current context.  "
311                                 "Not Exiting the Directory!\n");
312                         res = 0;
313                 }
314         }       
315         memset(ext, 0, sizeof(ext));
316         ext[0] = digit;
317         res = 0;
318         if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
319         if (!res) {
320                 /* Search for all names which start with those digits */
321                 v = ast_variable_browse(cfg, context);
322                 while(v && !res) {
323                         /* Find all candidate extensions */
324                         while(v) {
325                                 /* Find a candidate extension */
326                                 start = strdup(v->value);
327                                 if (start && !strcasestr(start, "hidefromdir=yes")) {
328                                         stringp=start;
329                                         strsep(&stringp, ",");
330                                         pos = strsep(&stringp, ",");
331                                         if (pos) {
332                                                 ast_copy_string(name, pos, sizeof(name));
333                                                 /* Grab the last name */
334                                                 if (last && strrchr(pos,' '))
335                                                         pos = strrchr(pos, ' ') + 1;
336                                                 conv = convert(pos);
337                                                 if (conv) {
338                                                         if (!strcmp(conv, ext)) {
339                                                                 /* Match! */
340                                                                 found++;
341                                                                 free(conv);
342                                                                 free(start);
343                                                                 break;
344                                                         }
345                                                         free(conv);
346                                                 }
347                                         }
348                                         free(start);
349                                 }
350                                 v = v->next;
351                         }
352
353                         if (v) {
354                                 /* We have a match -- play a greeting if they have it */
355                                 res = play_mailbox_owner(chan, context, dialcontext, v->name, name, readext);
356                                 switch (res) {
357                                         case -1:
358                                                 /* user pressed '1' but extension does not exist, or
359                                                  * user hungup
360                                                  */
361                                                 lastuserchoice = 0;
362                                                 break;
363                                         case '1':
364                                                 /* user pressed '1' and extensions exists;
365                                                    play_mailbox_owner will already have done
366                                                    a goto() on the channel
367                                                  */
368                                                 lastuserchoice = res;
369                                                 break;
370                                         case '*':
371                                                 /* user pressed '*' to skip something found */
372                                                 lastuserchoice = res;
373                                                 res = 0;
374                                                 break;
375                                         default:
376                                                 break;
377                                 }
378                                 v = v->next;
379                         }
380                 }
381
382                 if (lastuserchoice != '1') {
383                         res = ast_streamfile(chan, found ? "dir-nomore" : "dir-nomatch", chan->language);
384                         if (!res)
385                                 res = 1;
386                         return res;
387                 }
388                 return 0;
389         }
390         return res;
391 }
392
393 static int directory_exec(struct ast_channel *chan, void *data)
394 {
395         int res = 0;
396         struct localuser *u;
397         struct ast_config *cfg;
398         int last = 1;
399         int readext = 0;
400         char *dirintro, *parse;
401         AST_DECLARE_APP_ARGS(args,
402                 AST_APP_ARG(vmcontext);
403                 AST_APP_ARG(dialcontext);
404                 AST_APP_ARG(options);
405         );
406
407         if (ast_strlen_zero(data)) {
408                 ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
409                 return -1;
410         }
411
412         LOCAL_USER_ADD(u);
413
414         parse = ast_strdupa(data);
415
416         AST_STANDARD_APP_ARGS(args, parse);
417                 
418         if (args.options) {
419                 if (strchr(args.options, 'f'))
420                         last = 0;
421                 if (strchr(args.options, 'e'))
422                         readext = 1;
423         }
424
425         if (ast_strlen_zero(args.dialcontext))  
426                 args.dialcontext = args.vmcontext;
427
428         cfg = realtime_directory(args.vmcontext);
429         if (!cfg) {
430                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
431                 LOCAL_USER_REMOVE(u);
432                 return -1;
433         }
434
435         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
436         if (ast_strlen_zero(dirintro))
437                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
438         if (ast_strlen_zero(dirintro))
439                 dirintro = last ? "dir-intro" : "dir-intro-fn";
440
441         if (chan->_state != AST_STATE_UP) 
442                 res = ast_answer(chan);
443
444         for (;;) {
445                 if (!res)
446                         res = ast_stream_and_wait(chan, dirintro, chan->language, AST_DIGIT_ANY);
447                 ast_stopstream(chan);
448                 if (!res)
449                         res = ast_waitfordigit(chan, 5000);
450                 if (res > 0) {
451                         res = do_directory(chan, cfg, args.vmcontext, args.dialcontext, res, last, readext);
452                         if (res > 0) {
453                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
454                                 ast_stopstream(chan);
455                                 if (res >= 0)
456                                         continue;
457                         }
458                 }
459                 break;
460         }
461         ast_config_destroy(cfg);
462         LOCAL_USER_REMOVE(u);
463         return res;
464 }
465
466 static int unload_module(void *mod)
467 {
468         int res;
469         res = ast_unregister_application(app);
470         return res;
471 }
472
473 static int load_module(void *mod)
474 {
475         __mod_desc = mod;
476         return ast_register_application(app, directory_exec, synopsis, descrip);
477 }
478
479 static const char *description(void)
480 {
481         return "Extension Directory";
482 }
483
484 static const char *key(void)
485 {
486         return ASTERISK_GPL_KEY;
487 }
488
489 STD_MOD1;