7746502c7ebadfe86ebcddd408a606ffa6340345
[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 "asterisk.h"
29
30 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
31
32 #include <string.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <stdio.h>
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 #ifdef ODBC_STORAGE
49 #include <errno.h>
50 #include <sys/mman.h>
51 #include "asterisk/res_odbc.h"
52
53 static char odbc_database[80] = "asterisk";
54 static char odbc_table[80] = "voicemessages";
55 static char vmfmts[80] = "wav";
56 #endif
57
58 static char *app = "Directory";
59
60 static char *synopsis = "Provide directory of voicemail extensions";
61 static char *descrip =
62 "  Directory(vm-context[|dial-context[|options]]): This application will present\n"
63 "the calling channel with a directory of extensions from which they can search\n"
64 "by name. The list of names and corresponding extensions is retrieved from the\n"
65 "voicemail configuration file, voicemail.conf.\n"
66 "  This application will immediately exit if one of the following DTMF digits are\n"
67 "received and the extension to jump to exists:\n"
68 "    0 - Jump to the 'o' extension, if it exists.\n"
69 "    * - Jump to the 'a' extension, if it exists.\n\n"
70 "  Parameters:\n"
71 "    vm-context   - This is the context within voicemail.conf to use for the\n"
72 "                   Directory.\n"
73 "    dial-context - This is the dialplan context to use when looking for an\n"
74 "                   extension that the user has selected, or when jumping to the\n"
75 "                   'o' or 'a' extension.\n\n"
76 "  Options:\n"
77 "    e - In addition to the name, also read the extension number to the\n"
78 "        caller before presenting dialing options.\n"
79 "    f - Allow the caller to enter the first name of a user in the directory\n"
80 "        instead of using the last name.\n";
81
82 /* For simplicity, I'm keeping the format compatible with the voicemail config,
83    but i'm open to suggestions for isolating it */
84
85 #define VOICEMAIL_CONFIG "voicemail.conf"
86
87 /* How many digits to read in */
88 #define NUMDIGITS 3
89
90
91 #ifdef ODBC_STORAGE
92 static void retrieve_file(char *dir)
93 {
94         int x = 0;
95         int res;
96         int fd=-1;
97         size_t fdlen = 0;
98         void *fdm = MAP_FAILED;
99         SQLHSTMT stmt;
100         char sql[256];
101         char fmt[80]="", empty[10] = "";
102         char *c;
103         SQLLEN colsize;
104         char full_fn[256];
105         struct odbc_obj *obj;
106
107         obj = ast_odbc_request_obj(odbc_database, 1);
108         if (obj) {
109                 do {
110                         ast_copy_string(fmt, vmfmts, sizeof(fmt));
111                         c = strchr(fmt, '|');
112                         if (c)
113                                 *c = '\0';
114                         if (!strcasecmp(fmt, "wav49"))
115                                 strcpy(fmt, "WAV");
116                         snprintf(full_fn, sizeof(full_fn), "%s.%s", dir, fmt);
117                         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
118                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
119                                 ast_log(LOG_WARNING, "SQL Alloc Handle failed!\n");
120                                 break;
121                         }
122                         snprintf(sql, sizeof(sql), "SELECT recording FROM %s WHERE dir=? AND msgnum=-1", odbc_table);
123                         res = SQLPrepare(stmt, (unsigned char *)sql, SQL_NTS);
124                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
125                                 ast_log(LOG_WARNING, "SQL Prepare failed![%s]\n", sql);
126                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
127                                 break;
128                         }
129                         SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(dir), 0, (void *)dir, 0, NULL);
130                         res = ast_odbc_smart_execute(obj, stmt);
131                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
132                                 ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
133                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
134                                 break;
135                         }
136                         res = SQLFetch(stmt);
137                         if (res == SQL_NO_DATA) {
138                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
139                                 break;
140                         } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
141                                 ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
142                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
143                                 break;
144                         }
145                         fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, AST_FILE_MODE);
146                         if (fd < 0) {
147                                 ast_log(LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
148                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
149                                 break;
150                         }
151
152                         res = SQLGetData(stmt, 1, SQL_BINARY, empty, 0, &colsize);
153                         fdlen = colsize;
154                         if (fd > -1) {
155                                 char tmp[1]="";
156                                 lseek(fd, fdlen - 1, SEEK_SET);
157                                 if (write(fd, tmp, 1) != 1) {
158                                         close(fd);
159                                         fd = -1;
160                                         break;
161                                 }
162                                 if (fd > -1)
163                                         fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
164                         }
165                         if (fdm != MAP_FAILED) {
166                                 memset(fdm, 0, fdlen);
167                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, fdlen, &colsize);
168                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
169                                         ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
170                                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
171                                         break;
172                                 }
173                         }
174                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
175                 } while (0);
176                 ast_odbc_release_obj(obj);
177         } else
178                 ast_log(LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
179         if (fdm != MAP_FAILED)
180                 munmap(fdm, fdlen);
181         if (fd > -1)
182                 close(fd);
183         return;
184 }
185 #endif
186
187 static char *convert(const char *lastname)
188 {
189         char *tmp;
190         int lcount = 0;
191         tmp = ast_malloc(NUMDIGITS + 1);
192         if (tmp) {
193                 while((*lastname > 32) && lcount < NUMDIGITS) {
194                         switch(toupper(*lastname)) {
195                         case '1':
196                                 tmp[lcount++] = '1';
197                                 break;
198                         case '2':
199                         case 'A':
200                         case 'B':
201                         case 'C':
202                                 tmp[lcount++] = '2';
203                                 break;
204                         case '3':
205                         case 'D':
206                         case 'E':
207                         case 'F':
208                                 tmp[lcount++] = '3';
209                                 break;
210                         case '4':
211                         case 'G':
212                         case 'H':
213                         case 'I':
214                                 tmp[lcount++] = '4';
215                                 break;
216                         case '5':
217                         case 'J':
218                         case 'K':
219                         case 'L':
220                                 tmp[lcount++] = '5';
221                                 break;
222                         case '6':
223                         case 'M':
224                         case 'N':
225                         case 'O':
226                                 tmp[lcount++] = '6';
227                                 break;
228                         case '7':
229                         case 'P':
230                         case 'Q':
231                         case 'R':
232                         case 'S':
233                                 tmp[lcount++] = '7';
234                                 break;
235                         case '8':
236                         case 'T':
237                         case 'U':
238                         case 'V':
239                                 tmp[lcount++] = '8';
240                                 break;
241                         case '9':
242                         case 'W':
243                         case 'X':
244                         case 'Y':
245                         case 'Z':
246                                 tmp[lcount++] = '9';
247                                 break;
248                         }
249                         lastname++;
250                 }
251                 tmp[lcount] = '\0';
252         }
253         return tmp;
254 }
255
256 /* play name of mailbox owner.
257  * returns:  -1 for bad or missing extension
258  *           '1' for selected entry from directory
259  *           '*' for skipped entry from directory
260  */
261 static int play_mailbox_owner(struct ast_channel *chan, char *context,
262                 char *dialcontext, char *ext, char *name, int readext,
263                 int fromappvm)
264 {
265         int res = 0;
266         int loop;
267         char fn[256];
268
269         /* Check for the VoiceMail2 greeting first */
270         snprintf(fn, sizeof(fn), "%s/voicemail/%s/%s/greet",
271                 ast_config_AST_SPOOL_DIR, context, ext);
272 #ifdef ODBC_STORAGE
273         retrieve_file(fn);
274 #endif
275
276         if (ast_fileexists(fn, NULL, chan->language) <= 0) {
277                 /* no file, check for an old-style Voicemail greeting */
278                 snprintf(fn, sizeof(fn), "%s/vm/%s/greet",
279                         ast_config_AST_SPOOL_DIR, ext);
280         }
281 #ifdef ODBC_STORAGE
282         retrieve_file(fn);
283 #endif
284
285         if (ast_fileexists(fn, NULL, chan->language) > 0) {
286                 res = ast_stream_and_wait(chan, fn, AST_DIGIT_ANY);
287                 ast_stopstream(chan);
288                 /* If Option 'e' was specified, also read the extension number with the name */
289                 if (readext) {
290                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
291                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
292                 }
293         } else {
294                 res = ast_say_character_str(chan, S_OR(name, ext), AST_DIGIT_ANY, chan->language);
295                 if (!ast_strlen_zero(name) && readext) {
296                         ast_stream_and_wait(chan, "vm-extension", AST_DIGIT_ANY);
297                         res = ast_say_character_str(chan, ext, AST_DIGIT_ANY, chan->language);
298                 }
299         }
300 #ifdef ODBC_STORAGE
301         ast_filedelete(fn, NULL);       
302 #endif
303
304         for (loop = 3 ; loop > 0; loop--) {
305                 if (!res)
306                         res = ast_stream_and_wait(chan, "dir-instr", AST_DIGIT_ANY);
307                 if (!res)
308                         res = ast_waitfordigit(chan, 3000);
309                 ast_stopstream(chan);
310         
311                 if (res < 0) /* User hungup, so jump out now */
312                         break;
313                 if (res == '1') {       /* Name selected */
314                         if (fromappvm) {
315                                 /* We still want to set the exten though */
316                                 ast_copy_string(chan->exten, ext, sizeof(chan->exten));
317                         } else {
318                                 if (ast_goto_if_exists(chan, dialcontext, ext, 1)) {
319                                         ast_log(LOG_WARNING,
320                                                 "Can't find extension '%s' in context '%s'.  "
321                                                 "Did you pass the wrong context to Directory?\n",
322                                                 ext, dialcontext);
323                                         res = -1;
324                                 }
325                         }
326                         break;
327                 }
328                 if (res == '*') /* Skip to next match in list */
329                         break;
330
331                 /* Not '1', or '*', so decrement number of tries */
332                 res = 0;
333         }
334
335         return(res);
336 }
337
338 static struct ast_config *realtime_directory(char *context)
339 {
340         struct ast_config *cfg;
341         struct ast_config *rtdata;
342         struct ast_category *cat;
343         struct ast_variable *var;
344         char *mailbox;
345         const char *fullname;
346         const char *hidefromdir;
347         char tmp[100];
348
349         /* Load flat file config. */
350         cfg = ast_config_load(VOICEMAIL_CONFIG);
351
352         if (!cfg) {
353                 /* Loading config failed. */
354                 ast_log(LOG_WARNING, "Loading config failed.\n");
355                 return NULL;
356         }
357
358         /* Get realtime entries, categorized by their mailbox number
359            and present in the requested context */
360         rtdata = ast_load_realtime_multientry("voicemail", "mailbox LIKE", "%", "context", context, NULL);
361
362         /* if there are no results, just return the entries from the config file */
363         if (!rtdata)
364                 return cfg;
365
366         /* Does the context exist within the config file? If not, make one */
367         cat = ast_category_get(cfg, context);
368         if (!cat) {
369                 cat = ast_category_new(context);
370                 if (!cat) {
371                         ast_log(LOG_WARNING, "Out of memory\n");
372                         ast_config_destroy(cfg);
373                         return NULL;
374                 }
375                 ast_category_append(cfg, cat);
376         }
377
378         mailbox = NULL;
379         while ( (mailbox = ast_category_browse(rtdata, mailbox)) ) {
380                 fullname = ast_variable_retrieve(rtdata, mailbox, "fullname");
381                 hidefromdir = ast_variable_retrieve(rtdata, mailbox, "hidefromdir");
382                 snprintf(tmp, sizeof(tmp), "no-password,%s,hidefromdir=%s",
383                          fullname ? fullname : "",
384                          hidefromdir ? hidefromdir : "no");
385                 var = ast_variable_new(mailbox, tmp);
386                 if (var)
387                         ast_variable_append(cat, var);
388                 else
389                         ast_log(LOG_WARNING, "Out of memory adding mailbox '%s'\n", mailbox);
390         }
391         ast_config_destroy(rtdata);
392
393         return cfg;
394 }
395
396 static int do_directory(struct ast_channel *chan, struct ast_config *cfg, struct ast_config *ucfg, char *context, char *dialcontext, char digit, int last, int readext, int fromappvm)
397 {
398         /* Read in the first three digits..  "digit" is the first digit, already read */
399         char ext[NUMDIGITS + 1], *cat;
400         char name[80] = "";
401         struct ast_variable *v;
402         int res;
403         int found=0;
404         int lastuserchoice = 0;
405         char *start, *conv, *stringp = NULL;
406         const char *pos;
407
408         if (ast_strlen_zero(context)) {
409                 ast_log(LOG_WARNING,
410                         "Directory must be called with an argument "
411                         "(context in which to interpret extensions)\n");
412                 return -1;
413         }
414         if (digit == '0') {
415                 if (!ast_goto_if_exists(chan, dialcontext, "o", 1) ||
416                     (!ast_strlen_zero(chan->macrocontext) &&
417                      !ast_goto_if_exists(chan, chan->macrocontext, "o", 1))) {
418                         return 0;
419                 } else {
420                         ast_log(LOG_WARNING, "Can't find extension 'o' in current context.  "
421                                 "Not Exiting the Directory!\n");
422                         res = 0;
423                 }
424         }       
425         if (digit == '*') {
426                 if (!ast_goto_if_exists(chan, dialcontext, "a", 1) ||
427                     (!ast_strlen_zero(chan->macrocontext) &&
428                      !ast_goto_if_exists(chan, chan->macrocontext, "a", 1))) {
429                         return 0;
430                 } else {
431                         ast_log(LOG_WARNING, "Can't find extension 'a' in current context.  "
432                                 "Not Exiting the Directory!\n");
433                         res = 0;
434                 }
435         }       
436         memset(ext, 0, sizeof(ext));
437         ext[0] = digit;
438         res = 0;
439         if (ast_readstring(chan, ext + 1, NUMDIGITS - 1, 3000, 3000, "#") < 0) res = -1;
440         if (!res) {
441                 /* Search for all names which start with those digits */
442                 v = ast_variable_browse(cfg, context);
443                 while(v && !res) {
444                         /* Find all candidate extensions */
445                         while(v) {
446                                 /* Find a candidate extension */
447                                 start = ast_strdup(v->value);
448                                 if (start && !strcasestr(start, "hidefromdir=yes")) {
449                                         stringp=start;
450                                         strsep(&stringp, ",");
451                                         pos = strsep(&stringp, ",");
452                                         if (pos) {
453                                                 ast_copy_string(name, pos, sizeof(name));
454                                                 /* Grab the last name */
455                                                 if (last && strrchr(pos,' '))
456                                                         pos = strrchr(pos, ' ') + 1;
457                                                 conv = convert(pos);
458                                                 if (conv) {
459                                                         if (!strncmp(conv, ext, strlen(ext))) {
460                                                                 /* Match! */
461                                                                 found++;
462                                                                 ast_free(conv);
463                                                                 ast_free(start);
464                                                                 break;
465                                                         }
466                                                         ast_free(conv);
467                                                 }
468                                         }
469                                         ast_free(start);
470                                 }
471                                 v = v->next;
472                         }
473
474                         if (v) {
475                                 /* We have a match -- play a greeting if they have it */
476                                 res = play_mailbox_owner(chan, context, dialcontext, v->name, name, readext, fromappvm);
477                                 switch (res) {
478                                         case -1:
479                                                 /* user pressed '1' but extension does not exist, or
480                                                  * user hungup
481                                                  */
482                                                 lastuserchoice = 0;
483                                                 break;
484                                         case '1':
485                                                 /* user pressed '1' and extensions exists;
486                                                    play_mailbox_owner will already have done
487                                                    a goto() on the channel
488                                                  */
489                                                 lastuserchoice = res;
490                                                 break;
491                                         case '*':
492                                                 /* user pressed '*' to skip something found */
493                                                 lastuserchoice = res;
494                                                 res = 0;
495                                                 break;
496                                         default:
497                                                 break;
498                                 }
499                                 v = v->next;
500                         }
501                 }
502
503                 if (!res && ucfg) {
504                         /* Search users.conf for all names which start with those digits */
505                         for (cat = ast_category_browse(ucfg, NULL); cat && !res ; cat = ast_category_browse(ucfg, cat)) {
506                                 if (!strcasecmp(cat, "general"))
507                                         continue;
508                                 if (!ast_true(ast_config_option(ucfg, cat, "hasdirectory")))
509                                         continue;
510                                 
511                                 /* Find all candidate extensions */
512                                 if ((pos = ast_variable_retrieve(ucfg, cat, "fullname"))) {
513                                         ast_copy_string(name, pos, sizeof(name));
514                                         /* Grab the last name */
515                                         if (last && strrchr(pos,' '))
516                                                 pos = strrchr(pos, ' ') + 1;
517                                         conv = convert(pos);
518                                         if (conv) {
519                                                 if (!strcmp(conv, ext)) {
520                                                         /* Match! */
521                                                         found++;
522                                                         /* We have a match -- play a greeting if they have it */
523                                                         res = play_mailbox_owner(chan, context, dialcontext, cat, name, readext, fromappvm);
524                                                         switch (res) {
525                                                         case -1:
526                                                                 /* user pressed '1' but extension does not exist, or
527                                                                  * user hungup
528                                                                  */
529                                                                 lastuserchoice = 0;
530                                                                 break;
531                                                         case '1':
532                                                                 /* user pressed '1' and extensions exists;
533                                                                    play_mailbox_owner will already have done
534                                                                    a goto() on the channel
535                                                                  */
536                                                                 lastuserchoice = res;
537                                                                 break;
538                                                         case '*':
539                                                                 /* user pressed '*' to skip something found */
540                                                                 lastuserchoice = res;
541                                                                 res = 0;
542                                                                 break;
543                                                         default:
544                                                                 break;
545                                                         }
546                                                         ast_free(conv);
547                                                         break;
548                                                 }
549                                                 ast_free(conv);
550                                         }
551                                 }
552                         }
553                 }
554                         
555                 if (lastuserchoice != '1') {
556                         res = ast_streamfile(chan, found ? "dir-nomore" : "dir-nomatch", chan->language);
557                         if (!res)
558                                 res = 1;
559                         return res;
560                 }
561                 return 0;
562         }
563         return res;
564 }
565
566 static int directory_exec(struct ast_channel *chan, void *data)
567 {
568         int res = 0;
569         struct ast_module_user *u;
570         struct ast_config *cfg, *ucfg;
571         int last = 1;
572         int readext = 0;
573         int fromappvm = 0;
574         const char *dirintro;
575         char *parse;
576         AST_DECLARE_APP_ARGS(args,
577                 AST_APP_ARG(vmcontext);
578                 AST_APP_ARG(dialcontext);
579                 AST_APP_ARG(options);
580         );
581
582         if (ast_strlen_zero(data)) {
583                 ast_log(LOG_WARNING, "Directory requires an argument (context[,dialcontext])\n");
584                 return -1;
585         }
586
587         u = ast_module_user_add(chan);
588
589         parse = ast_strdupa(data);
590
591         AST_STANDARD_APP_ARGS(args, parse);
592                 
593         if (args.options) {
594                 if (strchr(args.options, 'f'))
595                         last = 0;
596                 if (strchr(args.options, 'e'))
597                         readext = 1;
598                 if (strchr(args.options, 'v'))
599                         fromappvm = 1;
600         }
601
602         if (ast_strlen_zero(args.dialcontext))  
603                 args.dialcontext = args.vmcontext;
604
605         cfg = realtime_directory(args.vmcontext);
606         if (!cfg) {
607                 ast_log(LOG_ERROR, "Unable to read the configuration data!\n");
608                 ast_module_user_remove(u);
609                 return -1;
610         }
611         
612         ucfg = ast_config_load("users.conf");
613
614         dirintro = ast_variable_retrieve(cfg, args.vmcontext, "directoryintro");
615         if (ast_strlen_zero(dirintro))
616                 dirintro = ast_variable_retrieve(cfg, "general", "directoryintro");
617         if (ast_strlen_zero(dirintro))
618                 dirintro = last ? "dir-intro" : "dir-intro-fn";
619
620         if (chan->_state != AST_STATE_UP) 
621                 res = ast_answer(chan);
622
623         for (;;) {
624                 if (!res)
625                         res = ast_stream_and_wait(chan, dirintro, AST_DIGIT_ANY);
626                 ast_stopstream(chan);
627                 if (!res)
628                         res = ast_waitfordigit(chan, 5000);
629                 if (res > 0) {
630                         res = do_directory(chan, cfg, ucfg, args.vmcontext, args.dialcontext, res, last, readext, fromappvm);
631                         if (res > 0) {
632                                 res = ast_waitstream(chan, AST_DIGIT_ANY);
633                                 ast_stopstream(chan);
634                                 if (res >= 0)
635                                         continue;
636                         }
637                 }
638                 break;
639         }
640         if (ucfg)
641                 ast_config_destroy(ucfg);
642         ast_config_destroy(cfg);
643         ast_module_user_remove(u);
644         return res;
645 }
646
647 static int unload_module(void)
648 {
649         int res;
650         res = ast_unregister_application(app);
651         return res;
652 }
653
654 static int load_module(void)
655 {
656 #ifdef ODBC_STORAGE
657         struct ast_config *cfg = ast_config_load(VOICEMAIL_CONFIG);
658         const char *tmp;
659
660         if (cfg) {
661                 if ((tmp = ast_variable_retrieve(cfg, "general", "odbcstorage"))) {
662                         ast_copy_string(odbc_database, tmp, sizeof(odbc_database));
663                 }
664                 if ((tmp = ast_variable_retrieve(cfg, "general", "odbctable"))) {
665                         ast_copy_string(odbc_table, tmp, sizeof(odbc_table));
666                 }
667                 if ((tmp = ast_variable_retrieve(cfg, "general", "format"))) {
668                         ast_copy_string(vmfmts, tmp, sizeof(vmfmts));
669                 }
670                 ast_config_destroy(cfg);
671         } else
672                 ast_log(LOG_WARNING, "Unable to load " VOICEMAIL_CONFIG " - ODBC defaults will be used\n");
673 #endif
674
675         return ast_register_application(app, directory_exec, synopsis, descrip);
676 }
677
678 AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Extension Directory");