2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2005, Digium, Inc.
5 * and Edvina AB, Sollentuna, Sweden
7 * Mark Spencer <markster@digium.com> (Comedian Mail)
8 * and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
10 * See http://www.asterisk.org for more information about
11 * the Asterisk project. Please do not directly contact
12 * any of the maintainers of this project for assistance;
13 * the project provides a web site, mailing lists and IRC
14 * channels for your use.
16 * This program is free software, distributed under the terms of
17 * the GNU General Public License Version 2. See the LICENSE file
18 * at the top of the source tree.
23 * \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
25 * A voicemail system in small building blocks, working together
26 * based on the Comedian Mail voicemail system (app_voicemail.c).
29 * \arg \ref Config_minivm
30 * \arg \ref Config_minivm_examples
31 * \arg \ref App_minivm
33 * \ingroup applications
35 * \page App_minivm Asterisk Mini-voicemail - A minimal voicemail system
37 * This is a minimal voicemail system, building blocks for something
38 * else. It is built for multi-language systems.
39 * The current version is focused on accounts where voicemail is
40 * forwarded to users in e-mail. It's work in progress, with loosed ends hanging
41 * around from the old voicemail system and it's configuration.
43 * Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
46 * Dialplan applications
47 * - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
48 * - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
49 * - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
50 * - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
51 * - minivmAccMess - Record personal messages (busy | unavailable | temporary)
54 * - MINIVMACCOUNT() - A dialplan function
55 * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
58 * - minivm list accounts
60 * - minivm list templates
62 * - minivm show settings
65 * - General configuration in minivm.conf
66 * - Users in realtime or configuration file
67 * - Or configured on the command line with just the e-mail address
69 * Voicemail accounts are identified by userid and domain
71 * Language codes are like setlocale - langcode_countrycode
72 * \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
73 * language_country like setlocale().
76 * - Swedish, Sweden sv_se
77 * - Swedish, Finland sv_fi
78 * - English, USA en_us
82 * \arg \ref Config_minivm
83 * \arg \ref Config_minivm_examples
84 * \arg \ref Minivm_directories
85 * \arg \ref app_minivm.c
86 * \arg Comedian mail: app_voicemail.c
87 * \arg \ref descrip_minivm_accmess
88 * \arg \ref descrip_minivm_greet
89 * \arg \ref descrip_minivm_record
90 * \arg \ref descrip_minivm_delete
91 * \arg \ref descrip_minivm_notify
93 * \arg \ref App_minivm_todo
95 /*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
97 * The directory structure for storing voicemail
98 * - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
99 * - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
100 * - Domain MVM_SPOOL_DIR/domain
101 * - Username MVM_SPOOL_DIR/domain/username
102 * - /greet : Recording of account owner's name
103 * - /busy : Busy message
104 * - /unavailable : Unavailable message
105 * - /temp : Temporary message
107 * For account anita@localdomain.xx the account directory would as a default be
108 * \b /var/spool/asterisk/voicemail/localdomain.xx/anita
110 * To avoid transcoding, these sound files should be converted into several formats
111 * They are recorded in the format closest to the incoming streams
114 * Back: \ref App_minivm
117 /*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
118 * \section Example dialplan scripts for Mini-Voicemail
119 * \verbinclude extensions_minivm.conf.sample
121 * Back: \ref App_minivm
124 /*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
125 * - configure accounts from AMI?
126 * - test, test, test, test
127 * - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
128 * "The extension you are calling"
129 * - For trunk, consider using channel storage for information passing between small applications
130 * - Set default directory for voicemail
131 * - New app for creating directory for account if it does not exist
132 * - Re-insert code for IMAP storage at some point
133 * - Jabber integration for notifications
134 * - Figure out how to handle video in voicemail
135 * - Integration with the HTTP server
136 * - New app for moving messages between mailboxes, and optionally mark it as "new"
138 * For Asterisk 1.4/trunk
139 * - Use string fields for minivm_account
141 * Back: \ref App_minivm
151 #include <sys/time.h>
152 #include <sys/stat.h>
153 #include <sys/types.h>
154 #include <sys/mman.h>
159 #include "asterisk.h"
161 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
163 #include "asterisk/astobj.h"
164 #include "asterisk/lock.h"
165 #include "asterisk/file.h"
166 #include "asterisk/logger.h"
167 #include "asterisk/channel.h"
168 #include "asterisk/pbx.h"
169 #include "asterisk/options.h"
170 #include "asterisk/config.h"
171 #include "asterisk/say.h"
172 #include "asterisk/module.h"
173 #include "asterisk/app.h"
174 #include "asterisk/manager.h"
175 #include "asterisk/dsp.h"
176 #include "asterisk/localtime.h"
177 #include "asterisk/cli.h"
178 #include "asterisk/utils.h"
179 #include "asterisk/linkedlists.h"
180 #include "asterisk/callerid.h"
190 #define MVM_REVIEW (1 << 0) /*!< Review message */
191 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
192 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
193 #define MVM_SVMAIL (1 << 3)
194 #define MVM_ENVELOPE (1 << 4)
195 #define MVM_PBXSKIP (1 << 9)
196 #define MVM_ALLOCED (1 << 13)
198 /*! \brief Default mail command to mail voicemail. Change it with the
199 mailcmd= command in voicemail.conf */
200 #define SENDMAIL "/usr/sbin/sendmail -t"
202 #define SOUND_INTRO "vm-intro"
203 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
204 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
207 #define MAX_DATETIME_FORMAT 512
208 #define MAX_NUM_CID_CONTEXTS 10
210 #define ERROR_LOCK_PATH -100
211 #define VOICEMAIL_DIR_MODE 0700
213 #define VOICEMAIL_CONFIG "minivm.conf"
214 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
216 /*! \brief Message types for notification */
217 enum mvm_messagetype {
220 /* For trunk: MVM_MESSAGE_JABBER, */
223 static char MVM_SPOOL_DIR[PATH_MAX];
225 /* Module declarations */
226 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
227 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
228 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
229 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
230 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
232 static char *synopsis_minivm_record = "Receive Mini-Voicemail and forward via e-mail";
233 static char *descrip_minivm_record =
234 "Syntax: MinivmRecord(username@domain[,options])\n"
235 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
236 "MiniVM records audio file in configured format and forwards message to e-mail and pager.\n"
237 "If there's no user account for that address, a temporary account will\n"
238 "be used with default options.\n"
239 "The recorded file name and path will be stored in MINIVM_FILENAME and the \n"
240 "duration of the message will be stored in MINIVM_DURATION\n"
241 "\nNote: If the caller hangs up after the recording, the only way to send\n"
242 "the message and clean up is to execute in the \"h\" extension.\n"
243 "\nThe application will exit if any of the following DTMF digits are \n"
244 "received and the requested extension exist in the current context.\n"
245 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
246 " * - Jump to the 'a' extension in the current dialplan context.\n"
248 "Result is given in channel variable MINIVM_RECORD_STATUS\n"
249 " The possible values are: SUCCESS | USEREXIT | FAILED\n\n"
251 " g(#) - Use the specified amount of gain when recording the voicemail\n"
252 " message. The units are whole-number decibels (dB).\n"
255 static char *synopsis_minivm_greet = "Play Mini-Voicemail prompts";
256 static char *descrip_minivm_greet =
257 "Syntax: MinivmGreet(username@domain[,options])\n"
258 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
259 "MinivmGreet() plays default prompts or user specific prompts for an account.\n"
260 "Busy and unavailable messages can be choosen, but will be overridden if a temporary\n"
261 "message exists for the account.\n"
263 "Result is given in channel variable MINIVM_GREET_STATUS\n"
264 " The possible values are: SUCCESS | USEREXIT | FAILED\n\n"
266 " b - Play the 'busy' greeting to the calling party.\n"
267 " s - Skip the playback of instructions for leaving a message to the\n"
269 " u - Play the 'unavailable greeting.\n"
272 static char *synopsis_minivm_notify = "Notify voicemail owner about new messages.";
273 static char *descrip_minivm_notify =
274 "Syntax: MinivmNotify(username@domain[,template])\n"
275 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
276 "MiniVMnotify forwards messages about new voicemail to e-mail and pager.\n"
277 "If there's no user account for that address, a temporary account will\n"
278 "be used with default options (set in minivm.conf).\n"
279 "The recorded file name and path will be read from MVM_FILENAME and the \n"
280 "duration of the message will be accessed from MVM_DURATION (set by MinivmRecord() )\n"
281 "If the channel variable MVM_COUNTER is set, this will be used in the\n"
282 "message file name and available in the template for the message.\n"
283 "If not template is given, the default email template will be used to send email and\n"
284 "default pager template to send paging message (if the user account is configured with\n"
285 "a paging address.\n"
287 "Result is given in channel variable MINIVM_NOTIFY_STATUS\n"
288 " The possible values are: SUCCESS | FAILED\n"
291 static char *synopsis_minivm_delete = "Delete Mini-Voicemail voicemail messages";
292 static char *descrip_minivm_delete =
293 "Syntax: MinivmDelete(filename)\n"
294 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
295 "It deletes voicemail file set in MVM_FILENAME or given filename.\n"
297 "Result is given in channel variable MINIVM_DELETE_STATUS\n"
298 " The possible values are: SUCCESS | FAILED\n"
299 " FAILED is set if the file does not exist or can't be deleted.\n"
302 static char *synopsis_minivm_accmess = "Record account specific messages";
303 static char *descrip_minivm_accmess =
304 "Syntax: MinivmAccmess(username@domain,option)\n"
305 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
306 "Use this application to record account specific audio/video messages for\n"
307 "busy, unavailable and temporary messages.\n"
308 "Account specific directories will be created if they do not exist.\n"
309 "\nThe option selects message to be recorded:\n"
312 " t Temporary (overrides busy and unavailable)\n"
315 "Result is given in channel variable MINIVM_ACCMESS_STATUS\n"
316 " The possible values are: SUCCESS | FAILED\n"
317 " FAILED is set if the file can't be created.\n"
321 OPT_SILENT = (1 << 0),
322 OPT_BUSY_GREETING = (1 << 1),
323 OPT_UNAVAIL_GREETING = (1 << 2),
324 OPT_TEMP_GREETING = (1 << 3),
325 OPT_NAME_GREETING = (1 << 4),
326 OPT_RECORDGAIN = (1 << 5),
327 } minivm_option_flags;
330 OPT_ARG_RECORDGAIN = 0,
331 OPT_ARG_ARRAY_SIZE = 1,
332 } minivm_option_args;
334 AST_APP_OPTIONS(minivm_app_options, {
335 AST_APP_OPTION('s', OPT_SILENT),
336 AST_APP_OPTION('b', OPT_BUSY_GREETING),
337 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
338 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
341 AST_APP_OPTIONS(minivm_accmess_options, {
342 AST_APP_OPTION('b', OPT_BUSY_GREETING),
343 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
344 AST_APP_OPTION('t', OPT_TEMP_GREETING),
345 AST_APP_OPTION('n', OPT_NAME_GREETING),
348 /*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
349 struct minivm_account {
350 char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
351 char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
353 char pincode[10]; /*!< Secret pin code, numbers only */
354 char fullname[120]; /*!< Full name, for directory app */
355 char email[80]; /*!< E-mail address - override */
356 char pager[80]; /*!< E-mail address to pager (no attachment) */
357 char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
358 char serveremail[80]; /*!< From: Mail address */
359 char externnotify[160]; /*!< Configurable notification command */
360 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
361 char zonetag[80]; /*!< Time zone */
362 char uniqueid[20]; /*!< Unique integer identifier */
363 char exit[80]; /*!< Options for exiting from voicemail() */
364 char attachfmt[80]; /*!< Format for voicemail audio file attachment */
365 char etemplate[80]; /*!< Pager template */
366 char ptemplate[80]; /*!< Voicemail format */
367 uint64_t flags; /*!< MVM_ flags */
368 struct ast_variable *chanvars; /*!< Variables for e-mail template */
369 double volgain; /*!< Volume gain for voicemails sent via e-mail */
370 AST_LIST_ENTRY(minivm_account) list;
373 /*! \brief The list of e-mail accounts */
374 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
376 /*! \brief Linked list of e-mail templates in various languages
377 These are used as templates for e-mails, pager messages and jabber messages
378 \ref message_templates
380 struct minivm_template {
381 char name[80]; /*!< Template name */
382 char *body; /*!< Body of this template */
383 char fromaddress[100]; /*!< Who's sending the e-mail? */
384 char serveremail[80]; /*!< From: Mail address */
385 char subject[100]; /*!< Subject line */
386 char charset[32]; /*!< Default character set for this template */
387 char locale[20]; /*!< Locale for setlocale() */
388 char dateformat[80]; /*!< Date format to use in this attachment */
389 int attachment; /*!< Attachment of media yes/no - no for pager messages */
390 AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
393 /*! \brief The list of e-mail templates */
394 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
396 /*! \brief Options for leaving voicemail with the voicemail() application */
397 struct leave_vm_options {
399 signed char record_gain;
402 /*! \brief Structure for base64 encoding */
408 unsigned char iobuf[B64_BASEMAXINLINE];
411 /*! \brief Voicemail time zones */
413 char name[80]; /*!< Name of this time zone */
414 char timezone[80]; /*!< Timezone definition */
415 char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
416 AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
419 /*! \brief The list of e-mail time zones */
420 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
422 /*! \brief Structure for gathering statistics */
423 struct minivm_stats {
424 int voicemailaccounts; /*!< Number of static accounts */
425 int timezones; /*!< Number of time zones */
426 int templates; /*!< Number of templates */
428 struct timeval reset; /*!< Time for last reset */
429 int receivedmessages; /*!< Number of received messages since reset */
430 struct timeval lastreceived; /*!< Time for last voicemail sent */
433 /*! \brief Statistics for voicemail */
434 static struct minivm_stats global_stats;
436 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
437 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
439 FILE *minivmlogfile; /*!< The minivm log file */
441 static int global_vmminmessage; /*!< Minimum duration of messages */
442 static int global_vmmaxmessage; /*!< Maximum duration of message */
443 static int global_maxsilence; /*!< Maximum silence during recording */
444 static int global_maxgreet; /*!< Maximum length of prompts */
445 static int global_silencethreshold = 128;
446 static char global_mailcmd[160]; /*!< Configurable mail cmd */
447 static char global_externnotify[160]; /*!< External notification application */
448 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
449 static char default_vmformat[80];
451 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
452 static int global_saydurationminfo;
453 static char global_charset[32]; /*!< Global charset in messages */
455 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
457 /*! \brief Default dateformat, can be overridden in configuration file */
458 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
459 #define DEFAULT_CHARSET "ISO-8859-1"
461 /* Forward declarations */
462 static char *message_template_parse_filebody(char *filename);
463 static char *message_template_parse_emailbody(const char *body);
464 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
465 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
466 static int handle_minivm_reload(int fd, int argc, char *argv[]);
468 /*! \brief Create message template */
469 static struct minivm_template *message_template_create(const char *name)
471 struct minivm_template *template;
473 template = ast_calloc(1, sizeof(*template));
477 /* Set some defaults for templates */
478 ast_copy_string(template->name, name, sizeof(template->name));
479 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
480 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
481 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
482 template->attachment = TRUE;
487 /*! \brief Release memory allocated by message template */
488 static void message_template_free(struct minivm_template *template)
491 ast_free(template->body);
496 /*! \brief Build message template from configuration */
497 static int message_template_build(const char *name, struct ast_variable *var)
499 struct minivm_template *template;
502 template = message_template_create(name);
504 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
509 ast_debug(3, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
510 if (!strcasecmp(var->name, "fromaddress")) {
511 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
512 } else if (!strcasecmp(var->name, "fromemail")) {
513 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
514 } else if (!strcasecmp(var->name, "subject")) {
515 ast_copy_string(template->subject, var->value, sizeof(template->subject));
516 } else if (!strcasecmp(var->name, "locale")) {
517 ast_copy_string(template->locale, var->value, sizeof(template->locale));
518 } else if (!strcasecmp(var->name, "attachmedia")) {
519 template->attachment = ast_true(var->value);
520 } else if (!strcasecmp(var->name, "dateformat")) {
521 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
522 } else if (!strcasecmp(var->name, "charset")) {
523 ast_copy_string(template->charset, var->value, sizeof(template->charset));
524 } else if (!strcasecmp(var->name, "templatefile")) {
526 ast_free(template->body);
527 template->body = message_template_parse_filebody(var->value);
528 if (!template->body) {
529 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
532 } else if (!strcasecmp(var->name, "messagebody")) {
534 ast_free(template->body);
535 template->body = message_template_parse_emailbody(var->value);
536 if (!template->body) {
537 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
541 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
547 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
549 AST_LIST_LOCK(&message_templates);
550 AST_LIST_INSERT_TAIL(&message_templates, template, list);
551 AST_LIST_UNLOCK(&message_templates);
553 global_stats.templates++;
558 /*! \brief Find named template */
559 static struct minivm_template *message_template_find(const char *name)
561 struct minivm_template *this, *res = NULL;
563 if (ast_strlen_zero(name))
566 AST_LIST_LOCK(&message_templates);
567 AST_LIST_TRAVERSE(&message_templates, this, list) {
568 if (!strcasecmp(this->name, name)) {
573 AST_LIST_UNLOCK(&message_templates);
579 /*! \brief Clear list of templates */
580 static void message_destroy_list(void)
582 struct minivm_template *this;
583 AST_LIST_LOCK(&message_templates);
584 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list)))
585 message_template_free(this);
587 AST_LIST_UNLOCK(&message_templates);
590 /*! \brief read buffer from file (base64 conversion) */
591 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
598 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
612 /*! \brief read character from file to buffer (base64 conversion) */
613 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
615 if (bio->iocp >= bio->iolen) {
616 if (!b64_inbuf(bio, fi))
620 return bio->iobuf[bio->iocp++];
623 /*! \brief write buffer to file (base64 conversion) */
624 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
626 if (bio->linelength >= B64_BASELINELEN) {
627 if (fputs(EOL,so) == EOF)
633 if (putc(((unsigned char) c), so) == EOF)
641 /*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
642 static int base_encode(char *filename, FILE *so)
644 unsigned char dtable[B64_BASEMAXINLINE];
647 struct b64_baseio bio;
649 memset(&bio, 0, sizeof(bio));
650 bio.iocp = B64_BASEMAXINLINE;
652 if (!(fi = fopen(filename, "rb"))) {
653 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
657 for (i= 0; i<9; i++) {
661 dtable[26+i+9]= 'j'+i;
663 for (i= 0; i < 8; i++) {
665 dtable[26+i+18]= 's'+i;
667 for (i= 0; i < 10; i++) {
674 unsigned char igroup[3], ogroup[4];
677 igroup[0]= igroup[1]= igroup[2]= 0;
679 for (n= 0; n < 3; n++) {
680 if ((c = b64_inchar(&bio, fi)) == EOF) {
684 igroup[n]= (unsigned char)c;
688 ogroup[0]= dtable[igroup[0]>>2];
689 ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
690 ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
691 ogroup[3]= dtable[igroup[2]&0x3F];
701 b64_ochar(&bio, ogroup[i], so);
705 /* Put end of line - line feed */
706 if (fputs(EOL, so) == EOF)
714 static int get_date(char *s, int len)
717 struct timeval tv = ast_tvnow();
719 ast_localtime(&tv, &tm, NULL);
720 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
724 /*! \brief Free user structure - if it's allocated */
725 static void free_user(struct minivm_account *vmu)
728 ast_variables_destroy(vmu->chanvars);
734 /*! \brief Prepare for voicemail template by adding channel variables
737 static void prep_email_sub_vars(struct ast_channel *channel, const struct minivm_account *vmu, const char *cidnum, const char *cidname, const char *dur, const char *date, const char *counter)
740 struct ast_variable *var;
743 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
747 for (var = vmu->chanvars ; var ; var = var->next)
748 pbx_builtin_setvar_helper(channel, var->name, var->value);
750 /* Prepare variables for substition in email body and subject */
751 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
752 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
753 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
754 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
755 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
756 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
757 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
758 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
759 if (!ast_strlen_zero(counter))
760 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
763 /*! \brief Set default values for Mini-Voicemail users */
764 static void populate_defaults(struct minivm_account *vmu)
766 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
767 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
768 vmu->volgain = global_volgain;
771 /*! \brief Fix quote of mail headers for non-ascii characters */
772 static char *mailheader_quote(const char *from, char *to, size_t len)
776 for (; ptr < to + len - 1; from++) {
779 else if (*from == '\0')
783 if (ptr < to + len - 1)
790 /*! \brief Allocate new vm user and set default values */
791 static struct minivm_account *mvm_user_alloc(void)
793 struct minivm_account *new;
795 new = ast_calloc(1, sizeof(*new));
798 populate_defaults(new);
804 /*! \brief Clear list of users */
805 static void vmaccounts_destroy_list(void)
807 struct minivm_account *this;
808 AST_LIST_LOCK(&minivm_accounts);
809 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
811 AST_LIST_UNLOCK(&minivm_accounts);
815 /*! \brief Find user from static memory object list */
816 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
818 struct minivm_account *vmu = NULL, *cur;
821 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
822 ast_log(LOG_NOTICE, "No username or domain? \n");
825 ast_debug(3, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain);
827 AST_LIST_LOCK(&minivm_accounts);
828 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
829 /* Is this the voicemail account we're looking for? */
830 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
833 AST_LIST_UNLOCK(&minivm_accounts);
836 ast_debug(3, "-_-_- Found account for %s@%s\n", username, domain);
840 vmu = find_user_realtime(domain, username);
842 if (createtemp && !vmu) {
843 /* Create a temporary user, send e-mail and be gone */
844 vmu = mvm_user_alloc();
845 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
847 ast_copy_string(vmu->username, username, sizeof(vmu->username));
848 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
849 ast_debug(1, "--- Created temporary account\n");
856 /*! \brief Find user in realtime storage
857 Returns pointer to minivm_account structure
859 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
861 struct ast_variable *var;
862 struct minivm_account *retval;
863 char name[MAXHOSTNAMELEN];
865 retval = mvm_user_alloc();
870 ast_copy_string(retval->username, username, sizeof(retval->username));
872 populate_defaults(retval);
873 var = ast_load_realtime("minivm", "username", username, "domain", domain, NULL);
880 snprintf(name, sizeof(name), "%s@%s", username, domain);
881 create_vmaccount(name, var, TRUE);
883 ast_variables_destroy(var);
887 /*! \brief Send voicemail with audio file as an attachment */
888 static int sendmail(struct minivm_template *template, struct minivm_account *vmu, char *cidnum, char *cidname, const char *filename, char *format, int duration, int attach_user_voicemail, enum mvm_messagetype type, const char *counter)
892 char email[256] = "";
896 char fname[PATH_MAX];
898 char tmp[80] = "/tmp/astmail-XXXXXX";
902 struct minivm_zone *the_zone = NULL;
904 struct ast_channel *ast;
906 char *passdata = NULL;
907 char *passdata2 = NULL;
911 if (type == MVM_MESSAGE_EMAIL) {
912 if (vmu && !ast_strlen_zero(vmu->email)) {
913 ast_copy_string(email, vmu->email, sizeof(email));
914 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
915 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
916 } else if (type == MVM_MESSAGE_PAGE) {
917 ast_copy_string(email, vmu->pager, sizeof(email));
920 if (ast_strlen_zero(email)) {
921 ast_log(LOG_WARNING, "No address to send message to.\n");
925 ast_debug(3, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
927 if (!strcmp(format, "wav49"))
931 /* If we have a gain option, process it now with sox */
932 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
933 char newtmp[PATH_MAX];
934 char tmpcmd[PATH_MAX];
937 snprintf(newtmp, sizeof(newtmp), "/tmp/XXXXXX");
938 ast_debug(3, "newtmp: %s\n", newtmp);
939 tmpfd = mkstemp(newtmp);
940 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
941 ast_safe_system(tmpcmd);
942 finalfilename = newtmp;
943 ast_debug(3, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
945 finalfilename = ast_strdupa(filename);
948 /* Create file name */
949 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
951 if (template->attachment)
952 ast_debug(1, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
954 /* Make a temporary file instead of piping directly to sendmail, in case the mail
958 p = fdopen(pfd, "w");
963 ast_debug(1, "-_-_- Opening temp file for e-mail: %s\n", tmp);
966 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
969 /* Allocate channel used for chanvar substitution */
970 ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0);
973 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
975 /* Does this user have a timezone specified? */
976 if (!ast_strlen_zero(vmu->zonetag)) {
977 /* Find the zone in the list */
978 struct minivm_zone *z;
979 AST_LIST_LOCK(&minivm_zones);
980 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
981 if (strcmp(z->name, vmu->zonetag))
985 AST_LIST_UNLOCK(&minivm_zones);
989 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
990 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
992 /* Start printing the email to the temporary file */
993 fprintf(p, "Date: %s\n", date);
995 /* Set date format for voicemail mail */
996 ast_strftime(date, sizeof(date), template->dateformat, &tm);
999 /* Populate channel with channel variables for substitution */
1000 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1002 /* Find email address to use */
1003 /* If there's a server e-mail adress in the account, user that, othterwise template */
1004 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1006 /* Find name to user for server e-mail */
1007 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1009 /* If needed, add hostname as domain */
1010 if (ast_strlen_zero(fromemail))
1011 fromemail = "asterisk";
1013 if (strchr(fromemail, '@'))
1014 ast_copy_string(who, fromemail, sizeof(who));
1016 char host[MAXHOSTNAMELEN];
1017 gethostname(host, sizeof(host)-1);
1018 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1021 if (ast_strlen_zero(fromaddress)) {
1022 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1024 /* Allocate a buffer big enough for variable substitution */
1025 int vmlen = strlen(fromaddress) * 3 + 200;
1027 ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress);
1028 if ((passdata = alloca(vmlen))) {
1029 memset(passdata, 0, vmlen);
1030 pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen);
1031 len_passdata = strlen(passdata) * 2 + 3;
1032 passdata2 = alloca(len_passdata);
1033 fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who);
1035 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1040 ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
1042 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, getpid(), who);
1043 len_passdata = strlen(vmu->fullname) * 2 + 3;
1044 passdata2 = alloca(len_passdata);
1045 if (!ast_strlen_zero(vmu->email))
1046 fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1048 fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain);
1050 if (!ast_strlen_zero(template->subject)) {
1052 int vmlen = strlen(template->subject) * 3 + 200;
1053 if ((passdata = alloca(vmlen))) {
1054 memset(passdata, 0, vmlen);
1055 pbx_substitute_variables_helper(ast, template->subject, passdata, vmlen);
1056 fprintf(p, "Subject: %s\n", passdata);
1058 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1063 ast_debug(4, "-_-_- Subject now: %s\n", passdata);
1066 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1067 ast_debug(1, "-_-_- Using default subject for this email \n");
1071 if (option_debug > 2)
1072 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1073 fprintf(p, "MIME-Version: 1.0\n");
1075 /* Something unique. */
1076 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, getpid(), (unsigned int)rand());
1078 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1080 fprintf(p, "--%s\n", bound);
1081 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset);
1082 if (!ast_strlen_zero(template->body)) {
1084 int vmlen = strlen(template->body)*3 + 200;
1085 if ((passdata = alloca(vmlen))) {
1086 memset(passdata, 0, vmlen);
1087 pbx_substitute_variables_helper(ast, template->body, passdata, vmlen);
1088 ast_debug(3, "Message now: %s\n-----\n", passdata);
1089 fprintf(p, "%s\n", passdata);
1091 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1093 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1095 "in mailbox %s from %s, on %s so you might\n"
1096 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1097 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1098 ast_debug(3, "Using default message body (no template)\n-----\n");
1100 /* Eww. We want formats to tell us their own MIME type */
1101 if (template->attachment) {
1102 char *ctype = "audio/x-";
1103 ast_debug(3, "-_-_- Attaching file to message: %s\n", fname);
1104 if (!strcasecmp(format, "ogg"))
1105 ctype = "application/";
1107 fprintf(p, "--%s\n", bound);
1108 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1109 fprintf(p, "Content-Transfer-Encoding: base64\n");
1110 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1111 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1113 base_encode(fname, p);
1114 fprintf(p, "\n\n--%s--\n.\n", bound);
1117 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1118 ast_safe_system(tmp2);
1119 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1120 ast_debug(3, "-_-_- Actual command used: %s\n", tmp2);
1122 ast_channel_free(ast);
1126 /*! \brief Create directory based on components */
1127 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1129 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1132 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1133 * \param dest String. base directory.
1134 * \param len Int. Length base directory string.
1135 * \param domain String. Ignored if is null or empty string.
1136 * \param username String. Ignored if is null or empty string.
1137 * \param folder String. Ignored if is null or empty string.
1138 * \return 0 on failure, 1 on success.
1140 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1142 struct stat filestat;
1143 make_dir(dest, len, domain, username, folder ? folder : "");
1144 if (stat(dest, &filestat)== -1)
1150 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1151 * \param dest String. base directory.
1152 * \param len Length of directory string
1153 * \param domain String. Ignored if is null or empty string.
1154 * \param folder String. Ignored if is null or empty string.
1155 * \param username String. Ignored if is null or empty string.
1156 * \return -1 on failure, 0 on success.
1158 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1161 make_dir(dest, len, domain, username, folder);
1162 if ((res = ast_mkdir(dest, 0777))) {
1163 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1166 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1171 /*! \brief Play intro message before recording voicemail
1173 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1178 ast_debug(2, "-_-_- Still preparing to play message ...\n");
1180 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1182 if (ast_fileexists(fn, NULL, NULL) > 0) {
1183 res = ast_streamfile(chan, fn, chan->language);
1186 res = ast_waitstream(chan, ecodes);
1190 int numericusername = 1;
1193 ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n");
1196 ast_debug(2, "-_-_- Numeric? Checking %c\n", *i);
1198 numericusername = FALSE;
1204 if (numericusername) {
1205 if(ast_streamfile(chan, "vm-theperson", chan->language))
1207 if ((res = ast_waitstream(chan, ecodes)))
1210 res = ast_say_digit_str(chan, username, ecodes, chan->language);
1214 if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1216 if ((res = ast_waitstream(chan, ecodes)))
1221 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1224 res = ast_waitstream(chan, ecodes);
1228 /*! \brief Delete media files and attribute file */
1229 static int vm_delete(char *file)
1233 ast_debug(1, "-_-_- Deleting voicemail file %s\n", file);
1235 res = unlink(file); /* Remove the meta data file */
1236 res |= ast_filedelete(file, NULL); /* remove the media file */
1241 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1242 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1243 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1244 signed char record_gain)
1247 int max_attempts = 3;
1250 int message_exists = 0;
1251 signed char zero_gain = 0;
1252 char *acceptdtmf = "#";
1253 char *canceldtmf = "";
1255 /* Note that urgent and private are for flagging messages as such in the future */
1257 /* barf if no pointer passed to store duration in */
1258 if (duration == NULL) {
1259 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1263 cmd = '3'; /* Want to start by recording */
1265 while ((cmd >= 0) && (cmd != 't')) {
1269 if (option_verbose > 2)
1270 ast_verbose(VERBOSE_PREFIX_3 "Reviewing the message\n");
1271 ast_streamfile(chan, recordfile, chan->language);
1272 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1277 if (option_verbose > 2) {
1279 ast_verbose(VERBOSE_PREFIX_3 "Re-recording the message\n");
1281 ast_verbose(VERBOSE_PREFIX_3 "Recording the message\n");
1283 if (recorded && outsidecaller)
1284 cmd = ast_play_and_wait(chan, "beep");
1286 /* After an attempt has been made to record message, we have to take care of INTRO and beep for incoming messages, but not for greetings */
1288 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1289 if (ast_test_flag(vmu, MVM_OPERATOR))
1291 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1293 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1294 if (cmd == -1) /* User has hung up, no options to give */
1298 else if (cmd == '*')
1301 /* If all is well, a message exists */
1314 cmd = ast_play_and_wait(chan, "vm-sorry");
1317 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1318 cmd = ast_play_and_wait(chan, "vm-sorry");
1321 if (message_exists || recorded) {
1322 cmd = ast_play_and_wait(chan, "vm-saveoper");
1324 cmd = ast_waitfordigit(chan, 3000);
1326 ast_play_and_wait(chan, "vm-msgsaved");
1329 ast_play_and_wait(chan, "vm-deleted");
1330 vm_delete(recordfile);
1336 /* If the caller is an ouside caller, and the review option is enabled,
1337 allow them to review the message, but let the owner of the box review
1339 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1341 if (message_exists) {
1342 cmd = ast_play_and_wait(chan, "vm-review");
1344 cmd = ast_play_and_wait(chan, "vm-torerecord");
1346 cmd = ast_waitfordigit(chan, 600);
1349 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1350 cmd = ast_play_and_wait(chan, "vm-reachoper");
1352 cmd = ast_waitfordigit(chan, 600);
1355 cmd = ast_waitfordigit(chan, 6000);
1359 if (attempts > max_attempts) {
1365 ast_play_and_wait(chan, "vm-goodbye");
1371 /*! \brief Run external notification for voicemail message */
1372 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1374 char arguments[BUFSIZ];
1376 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1379 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1380 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1381 vmu->username, vmu->domain,
1382 chan->cid.cid_name, chan->cid.cid_num);
1384 ast_debug(1, "Executing: %s\n", arguments);
1385 ast_safe_system(arguments);
1388 /*! \brief Send message to voicemail account owner */
1389 static int notify_new_message(struct ast_channel *chan, const char *templatename, struct minivm_account *vmu, const char *filename, long duration, const char *format, char *cidnum, char *cidname)
1392 struct minivm_template *etemplate;
1393 char *messageformat;
1395 char oldlocale[100];
1396 const char *counter;
1398 if (!ast_strlen_zero(vmu->attachfmt)) {
1399 if (strstr(format, vmu->attachfmt)) {
1400 format = vmu->attachfmt;
1402 ast_log(LOG_WARNING, "Attachment format '%s' is not one of the recorded formats '%s'. Falling back to default format for '%s@%s'.\n", vmu->attachfmt, format, vmu->username, vmu->domain);
1405 etemplate = message_template_find(vmu->etemplate);
1407 etemplate = message_template_find(templatename);
1409 etemplate = message_template_find("email-default");
1411 /* Attach only the first format */
1412 stringp = messageformat = ast_strdupa(format);
1413 strsep(&stringp, "|");
1415 if (!ast_strlen_zero(etemplate->locale)) {
1417 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1418 ast_debug(2, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1419 newlocale = setlocale(LC_TIME, etemplate->locale);
1420 if (newlocale == NULL) {
1421 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1427 /* Read counter if available */
1428 counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER");
1429 if (ast_strlen_zero(counter)) {
1430 ast_debug(2, "-_-_- MVM_COUNTER not found\n");
1432 ast_debug(2, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter);
1435 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1437 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1438 /* Find template for paging */
1439 etemplate = message_template_find(vmu->ptemplate);
1441 etemplate = message_template_find("pager-default");
1442 if (etemplate->locale) {
1443 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1444 setlocale(LC_TIME, etemplate->locale);
1447 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1450 manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1452 run_externnotify(chan, vmu); /* Run external notification */
1454 if (etemplate->locale)
1455 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1460 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1461 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1463 char tmptxtfile[PATH_MAX];
1466 int res = 0, txtdes;
1470 char tmpdir[PATH_MAX];
1471 char ext_context[256] = "";
1475 struct minivm_account *vmu;
1478 ast_copy_string(tmp, username, sizeof(tmp));
1480 domain = strchr(tmp, '@');
1486 if (!(vmu = find_account(domain, username, TRUE))) {
1487 /* We could not find user, let's exit */
1488 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1489 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1493 /* Setup pre-file if appropriate */
1494 if (strcmp(vmu->domain, "localhost"))
1495 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1497 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1499 /* The meat of recording the message... All the announcements and beeps have been played*/
1500 if (ast_strlen_zero(vmu->attachfmt))
1501 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1503 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1505 if (ast_strlen_zero(fmt)) {
1506 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1507 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1512 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1514 /* If we have no user directory, use generic temporary directory */
1516 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1517 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1521 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1524 /* XXX This file needs to be in temp directory */
1525 txtdes = mkstemp(tmptxtfile);
1527 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1528 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1530 res = ast_waitstream(chan, "");
1531 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1536 /* Unless we're *really* silent, try to send the beep */
1537 res = ast_streamfile(chan, "beep", chan->language);
1539 res = ast_waitstream(chan, "");
1542 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1543 /* Store information */
1544 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1546 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1548 txt = fdopen(txtdes, "w+");
1550 ast_log(LOG_WARNING, "Error opening text file for output\n");
1553 struct timeval now = ast_tvnow();
1555 char logbuf[BUFSIZ];
1556 get_date(date, sizeof(date));
1557 ast_localtime(&now, &tm, NULL);
1558 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1560 snprintf(logbuf, sizeof(logbuf),
1561 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1562 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1569 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1573 duration < global_vmminmessage ? "IGNORED" : "OK",
1576 fprintf(txt, logbuf);
1577 if (minivmlogfile) {
1578 ast_mutex_lock(&minivmloglock);
1579 fprintf(minivmlogfile, logbuf);
1580 ast_mutex_unlock(&minivmloglock);
1583 if (duration < global_vmminmessage) {
1584 if (option_verbose > 2)
1585 ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1587 ast_filedelete(tmptxtfile, NULL);
1589 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1592 fclose(txt); /* Close log file */
1593 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1594 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1596 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1597 if(ast_test_flag(vmu, MVM_ALLOCED))
1602 /* Set channel variables for the notify application */
1603 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1604 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1605 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1606 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1609 global_stats.lastreceived = ast_tvnow();
1610 global_stats.receivedmessages++;
1611 // /* Go ahead and delete audio files from system, they're not needed any more */
1612 // if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1613 // ast_filedelete(tmptxtfile, NULL);
1614 // /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1615 // ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1621 if(ast_test_flag(vmu, MVM_ALLOCED))
1624 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1628 /*! \brief Notify voicemail account owners - either generic template or user specific */
1629 static int minivm_notify_exec(struct ast_channel *chan, void *data)
1637 struct minivm_account *vmu;
1638 char *username = argv[0];
1639 const char *template = "";
1640 const char *filename;
1642 const char *duration_string;
1644 if (ast_strlen_zero(data)) {
1645 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1648 tmpptr = ast_strdupa((char *)data);
1650 ast_log(LOG_ERROR, "Out of memory\n");
1653 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1655 if (argc == 2 && !ast_strlen_zero(argv[1]))
1658 ast_copy_string(tmp, argv[0], sizeof(tmp));
1660 domain = strchr(tmp, '@');
1665 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1666 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
1670 if(!(vmu = find_account(domain, username, TRUE))) {
1671 /* We could not find user, let's exit */
1672 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
1673 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
1677 filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME");
1678 format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT");
1679 duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION");
1680 /* Notify of new message to e-mail and pager */
1681 if (!ast_strlen_zero(filename)) {
1682 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
1685 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
1688 if(ast_test_flag(vmu, MVM_ALLOCED))
1691 /* Ok, we're ready to rock and roll. Return to dialplan */
1697 /*! \brief Dialplan function to record voicemail */
1698 static int minivm_record_exec(struct ast_channel *chan, void *data)
1702 struct leave_vm_options leave_options;
1705 struct ast_flags flags = { 0 };
1706 char *opts[OPT_ARG_ARRAY_SIZE];
1708 memset(&leave_options, 0, sizeof(leave_options));
1710 /* Answer channel if it's not already answered */
1711 if (chan->_state != AST_STATE_UP)
1714 if (ast_strlen_zero(data)) {
1715 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1718 tmp = ast_strdupa((char *)data);
1720 ast_log(LOG_ERROR, "Out of memory\n");
1723 argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
1725 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1728 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1729 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
1732 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
1733 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
1736 leave_options.record_gain = (signed char) gain;
1740 /* Now run the appliation and good luck to you! */
1741 res = leave_voicemail(chan, argv[0], &leave_options);
1743 if (res == ERROR_LOCK_PATH) {
1744 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
1745 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1748 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1753 /*! \brief Play voicemail prompts - either generic or user specific */
1754 static int minivm_greet_exec(struct ast_channel *chan, void *data)
1756 struct leave_vm_options leave_options = { 0, '\0'};
1759 struct ast_flags flags = { 0 };
1760 char *opts[OPT_ARG_ARRAY_SIZE];
1766 char dest[PATH_MAX];
1767 char prefile[PATH_MAX];
1768 char tempfile[PATH_MAX] = "";
1769 char ext_context[256] = "";
1771 char ecodes[16] = "#";
1773 struct minivm_account *vmu;
1774 char *username = argv[0];
1776 if (ast_strlen_zero(data)) {
1777 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1780 tmpptr = ast_strdupa((char *)data);
1782 ast_log(LOG_ERROR, "Out of memory\n");
1785 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1788 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
1790 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1793 ast_copy_string(tmp, argv[0], sizeof(tmp));
1795 domain = strchr(tmp, '@');
1800 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1801 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
1804 ast_debug(1, "-_-_- Trying to find configuration for user %s in domain %s\n", username, domain);
1806 if (!(vmu = find_account(domain, username, TRUE))) {
1807 ast_log(LOG_ERROR, "Could not allocate memory. \n");
1811 /* Answer channel if it's not already answered */
1812 if (chan->_state != AST_STATE_UP)
1815 /* Setup pre-file if appropriate */
1816 if (strcmp(vmu->domain, "localhost"))
1817 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1819 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1821 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
1822 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
1824 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
1825 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
1826 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
1828 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
1830 /* Check for temporary greeting - it overrides busy and unavail */
1831 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
1832 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
1833 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
1834 ast_copy_string(prefile, tempfile, sizeof(prefile));
1836 ast_debug(2, "-_-_- Preparing to play message ...\n");
1838 /* Check current or macro-calling context for special extensions */
1839 if (ast_test_flag(vmu, MVM_OPERATOR)) {
1840 if (!ast_strlen_zero(vmu->exit)) {
1841 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
1842 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1845 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
1846 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1849 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
1850 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1855 if (!ast_strlen_zero(vmu->exit)) {
1856 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
1857 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1858 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
1859 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1860 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
1861 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1865 res = 0; /* Reset */
1866 /* Play the beginning intro if desired */
1867 if (!ast_strlen_zero(prefile)) {
1868 if (ast_streamfile(chan, prefile, chan->language) > -1)
1869 res = ast_waitstream(chan, ecodes);
1871 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
1872 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
1875 ast_debug(2, "Hang up during prefile playback\n");
1876 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
1877 if(ast_test_flag(vmu, MVM_ALLOCED))
1882 /* On a '#' we skip the instructions */
1883 ast_set_flag(&leave_options, OPT_SILENT);
1886 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
1887 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
1889 res = ast_waitstream(chan, ecodes);
1891 ast_set_flag(&leave_options, OPT_SILENT);
1896 ast_stopstream(chan);
1897 /* Check for a '*' here in case the caller wants to escape from voicemail to something
1898 other than the operator -- an automated attendant or mailbox login for example */
1900 chan->exten[0] = 'a';
1901 chan->exten[1] = '\0';
1902 if (!ast_strlen_zero(vmu->exit)) {
1903 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
1904 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
1905 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
1908 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
1910 } else if (res == '0') { /* Check for a '0' here */
1911 if(ouseexten || ousemacro) {
1912 chan->exten[0] = 'o';
1913 chan->exten[1] = '\0';
1914 if (!ast_strlen_zero(vmu->exit)) {
1915 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
1916 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
1917 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
1919 ast_play_and_wait(chan, "transfer");
1921 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
1924 } else if (res < 0) {
1925 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
1928 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "SUCCESS");
1930 if(ast_test_flag(vmu, MVM_ALLOCED))
1934 /* Ok, we're ready to rock and roll. Return to dialplan */
1939 /*! \brief Dialplan application to delete voicemail */
1940 static int minivm_delete_exec(struct ast_channel *chan, void *data)
1943 char filename[BUFSIZ];
1945 if (!ast_strlen_zero(data))
1946 ast_copy_string(filename, (char *) data, sizeof(filename));
1948 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
1950 if (ast_strlen_zero(filename)) {
1951 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
1955 /* Go ahead and delete audio files from system, they're not needed any more */
1956 /* We should look for both audio and text files here */
1957 if (ast_fileexists(filename, NULL, NULL) > 0) {
1958 res = vm_delete(filename);
1960 ast_debug(2, "-_-_- Can't delete file: %s\n", filename);
1961 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
1963 ast_debug(2, "-_-_- Deleted voicemail file :: %s \n", filename);
1964 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "SUCCESS");
1967 ast_debug(2, "-_-_- Filename does not exist: %s\n", filename);
1968 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
1974 /*! \brief Record specific messages for voicemail account */
1975 static int minivm_accmess_exec(struct ast_channel *chan, void *data)
1980 char filename[PATH_MAX];
1983 char *tmpptr = NULL;
1984 struct minivm_account *vmu;
1985 char *username = argv[0];
1986 struct ast_flags flags = { 0 };
1987 char *opts[OPT_ARG_ARRAY_SIZE];
1989 char *message = NULL;
1990 char *prompt = NULL;
1994 if (ast_strlen_zero(data)) {
1995 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
1998 tmpptr = ast_strdupa((char *)data);
2001 ast_log(LOG_ERROR, "Out of memory\n");
2004 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
2008 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2011 if (!error && strlen(argv[1]) > 1) {
2012 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2016 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2017 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2024 ast_copy_string(tmp, argv[0], sizeof(tmp));
2026 domain = strchr(tmp, '@');
2031 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2032 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2036 if(!(vmu = find_account(domain, username, TRUE))) {
2037 /* We could not find user, let's exit */
2038 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2039 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
2043 /* Answer channel if it's not already answered */
2044 if (chan->_state != AST_STATE_UP)
2047 /* Here's where the action is */
2048 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2050 prompt = "vm-rec-busy";
2051 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2052 message = "unavailable";
2053 prompt = "vm-rec-unavail";
2054 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2056 prompt = "vm-temp-greeting";
2057 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2059 prompt = "vm-rec-name";
2061 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2062 /* Maybe we should check the result of play_record_review ? */
2063 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2065 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2067 if(ast_test_flag(vmu, MVM_ALLOCED))
2071 /* Ok, we're ready to rock and roll. Return to dialplan */
2076 /*! \brief Append new mailbox to mailbox list from configuration file */
2077 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2079 struct minivm_account *vmu;
2082 char accbuf[BUFSIZ];
2084 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2086 ast_copy_string(accbuf, name, sizeof(accbuf));
2088 domain = strchr(accbuf, '@');
2093 if (ast_strlen_zero(domain)) {
2094 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2098 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2100 /* Allocate user account */
2101 vmu = ast_calloc(1, sizeof(*vmu));
2105 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2106 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2108 populate_defaults(vmu);
2110 ast_debug(3, "...Configuring account %s\n", name);
2113 ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2114 if (!strcasecmp(var->name, "serveremail")) {
2115 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2116 } else if (!strcasecmp(var->name, "email")) {
2117 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2118 } else if (!strcasecmp(var->name, "accountcode")) {
2119 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2120 } else if (!strcasecmp(var->name, "pincode")) {
2121 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2122 } else if (!strcasecmp(var->name, "domain")) {
2123 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2124 } else if (!strcasecmp(var->name, "language")) {
2125 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2126 } else if (!strcasecmp(var->name, "timezone")) {
2127 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2128 } else if (!strcasecmp(var->name, "externnotify")) {
2129 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2130 } else if (!strcasecmp(var->name, "etemplate")) {
2131 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2132 } else if (!strcasecmp(var->name, "ptemplate")) {
2133 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2134 } else if (!strcasecmp(var->name, "fullname")) {
2135 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2136 } else if (!strcasecmp(var->name, "setvar")) {
2138 char *varname = ast_strdupa(var->value);
2139 struct ast_variable *tmpvar;
2141 if (varname && (varval = strchr(varname, '='))) {
2144 if ((tmpvar = ast_variable_new(varname, varval))) {
2145 tmpvar->next = vmu->chanvars;
2146 vmu->chanvars = tmpvar;
2149 } else if (!strcasecmp(var->name, "pager")) {
2150 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2151 } else if (!strcasecmp(var->name, "volgain")) {
2152 sscanf(var->value, "%lf", &vmu->volgain);
2154 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2158 ast_debug(3, "...Linking account %s\n", name);
2160 AST_LIST_LOCK(&minivm_accounts);
2161 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2162 AST_LIST_UNLOCK(&minivm_accounts);
2164 global_stats.voicemailaccounts++;
2166 ast_debug(2, "MINIVM :: Created account %s@%s - tz %s etemplate %s %s\n", username, domain, ast_strlen_zero(vmu->zonetag) ? "" : vmu->zonetag, ast_strlen_zero(vmu->etemplate) ? "" : vmu->etemplate, realtime ? "(realtime)" : "");
2170 /*! \brief Free Mini Voicemail timezone */
2171 static void free_zone(struct minivm_zone *z)
2176 /*! \brief Clear list of timezones */
2177 static void timezone_destroy_list(void)
2179 struct minivm_zone *this;
2181 AST_LIST_LOCK(&minivm_zones);
2182 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2185 AST_LIST_UNLOCK(&minivm_zones);
2188 /*! \brief Add time zone to memory list */
2189 static int timezone_add(char *zonename, char *config)
2192 struct minivm_zone *newzone;
2193 char *msg_format, *timezone;
2195 newzone = ast_calloc(1, sizeof(*newzone));
2196 if (newzone == NULL)
2199 msg_format = ast_strdupa(config);
2200 if (msg_format == NULL) {
2201 ast_log(LOG_WARNING, "Out of memory.\n");
2206 timezone = strsep(&msg_format, "|");
2208 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2213 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2214 ast_copy_string(newzone->timezone, timezone, sizeof(newzone->timezone));
2215 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2217 AST_LIST_LOCK(&minivm_zones);
2218 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2219 AST_LIST_UNLOCK(&minivm_zones);
2221 global_stats.timezones++;
2226 /*! \brief Read message template from file */
2227 static char *message_template_parse_filebody(char *filename) {
2228 char buf[BUFSIZ * 6];
2229 char readbuf[BUFSIZ];
2230 char filenamebuf[BUFSIZ];
2236 if (ast_strlen_zero(filename))
2238 if (*filename == '/')
2239 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2241 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2243 if (!(fi = fopen(filenamebuf, "r"))) {
2244 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2248 while (fgets(readbuf, sizeof(readbuf), fi)) {
2250 if (writepos != buf) {
2251 *writepos = '\n'; /* Replace EOL with new line */
2254 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2255 writepos += strlen(readbuf) - 1;
2258 messagebody = ast_calloc(1, strlen(buf + 1));
2259 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2260 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2261 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2266 /*! \brief Parse emailbody template from configuration file */
2267 static char *message_template_parse_emailbody(const char *configuration)
2269 char *tmpread, *tmpwrite;
2270 char *emailbody = ast_strdup(configuration);
2272 /* substitute strings \t and \n into the apropriate characters */
2273 tmpread = tmpwrite = emailbody;
2274 while ((tmpwrite = strchr(tmpread,'\\'))) {
2275 int len = strlen("\n");
2276 switch (tmpwrite[1]) {
2278 strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2279 strncpy(tmpwrite, "\n", len);
2282 strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2283 strncpy(tmpwrite, "\t", len);
2286 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2288 tmpread = tmpwrite + len;
2293 /*! \brief Apply general configuration options */
2294 static int apply_general_options(struct ast_variable *var)
2300 if (!strcmp(var->name, "mailcmd")) {
2301 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2302 } else if (!strcmp(var->name, "maxgreet")) {
2303 global_maxgreet = atoi(var->value);
2304 } else if (!strcmp(var->name, "maxsilence")) {
2305 global_maxsilence = atoi(var->value);
2306 if (global_maxsilence > 0)
2307 global_maxsilence *= 1000;
2308 } else if (!strcmp(var->name, "logfile")) {
2309 if (!ast_strlen_zero(var->value) ) {
2310 if(*(var->value) == '/')
2311 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2313 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2315 } else if (!strcmp(var->name, "externnotify")) {
2316 /* External voicemail notify application */
2317 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2318 } else if (!strcmp(var->name, "silencetreshold")) {
2319 /* Silence treshold */
2320 global_silencethreshold = atoi(var->value);
2321 } else if (!strcmp(var->name, "maxmessage")) {
2323 if (sscanf(var->value, "%d", &x) == 1) {
2324 global_vmmaxmessage = x;
2327 ast_log(LOG_WARNING, "Invalid max message time length\n");
2329 } else if (!strcmp(var->name, "minmessage")) {
2331 if (sscanf(var->value, "%d", &x) == 1) {
2332 global_vmminmessage = x;
2333 if (global_maxsilence <= global_vmminmessage)
2334 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2337 ast_log(LOG_WARNING, "Invalid min message time length\n");
2339 } else if (!strcmp(var->name, "format")) {
2340 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2341 } else if (!strcmp(var->name, "review")) {
2342 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2343 } else if (!strcmp(var->name, "operator")) {
2344 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2351 /*! \brief Load minivoicemail configuration */
2352 static int load_config(void)
2354 struct ast_config *cfg;
2355 struct ast_variable *var;
2357 const char *chanvar;
2359 struct minivm_template *template;
2361 cfg = ast_config_load(VOICEMAIL_CONFIG);
2362 ast_mutex_lock(&minivmlock);
2364 /* Destroy lists to reconfigure */
2365 message_destroy_list(); /* Destroy list of voicemail message templates */
2366 timezone_destroy_list(); /* Destroy list of timezones */
2367 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2368 ast_debug(2, "Destroyed memory objects...\n");
2370 /* First, set some default settings */
2371 global_externnotify[0] = '\0';
2372 global_logfile[0] = '\0';
2373 global_silencethreshold = 256;
2374 global_vmmaxmessage = 2000;
2375 global_maxgreet = 2000;
2376 global_vmminmessage = 0;
2377 strcpy(global_mailcmd, SENDMAIL);
2378 global_maxsilence = 0;
2379 global_saydurationminfo = 2;
2380 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2381 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2382 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2383 strcpy(global_charset, "ISO-8859-1");
2384 /* Reset statistics */
2385 memset(&global_stats, 0, sizeof(global_stats));
2386 global_stats.reset = ast_tvnow();
2388 /* Make sure we could load configuration file */
2390 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2391 ast_mutex_unlock(&minivmlock);
2395 ast_debug(2, "-_-_- Loaded configuration file, now parsing\n");
2397 /* General settings */
2399 cat = ast_category_browse(cfg, NULL);
2401 ast_debug(3, "-_-_- Found configuration section [%s]\n", cat);
2402 if (!strcasecmp(cat, "general")) {
2403 /* Nothing right now */
2404 error += apply_general_options(ast_variable_browse(cfg, cat));
2405 } else if (!strncasecmp(cat, "template-", 9)) {
2407 char *name = cat + 9;
2409 /* Now build and link template to list */
2410 error += message_template_build(name, ast_variable_browse(cfg, cat));
2412 var = ast_variable_browse(cfg, cat);
2413 if (!strcasecmp(cat, "zonemessages")) {
2414 /* Timezones in this context */
2416 timezone_add(var->name, var->value);
2420 /* Create mailbox from this */
2421 error += create_vmaccount(cat, var, FALSE);
2424 /* Find next section in configuration file */
2425 cat = ast_category_browse(cfg, cat);
2428 /* Configure the default email template */
2429 message_template_build("email-default", NULL);
2430 template = message_template_find("email-default");
2432 /* Load date format config for voicemail mail */
2433 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2434 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2435 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2436 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2437 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2438 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2439 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2440 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2441 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2442 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2443 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2444 template->body = message_template_parse_emailbody(chanvar);
2445 template->attachment = TRUE;
2447 message_template_build("pager-default", NULL);
2448 template = message_template_find("pager-default");
2449 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2450 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2451 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2452 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2453 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2454 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2455 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2456 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2457 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2458 template->body = message_template_parse_emailbody(chanvar);
2459 template->attachment = FALSE;
2462 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2464 ast_mutex_unlock(&minivmlock);
2465 ast_config_destroy(cfg);
2467 /* Close log file if it's open and disabled */
2469 fclose(minivmlogfile);
2471 /* Open log file if it's enabled */
2472 if(!ast_strlen_zero(global_logfile)) {
2473 minivmlogfile = fopen(global_logfile, "a");
2475 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2477 ast_debug(3, "-_-_- Opened log file %s \n", global_logfile);
2483 static const char minivm_show_users_help[] =
2484 "Usage: minivm list accounts\n"
2485 " Lists all mailboxes currently set up\n";
2487 static const char minivm_show_zones_help[] =
2488 "Usage: minivm list zones\n"
2489 " Lists zone message formats\n";
2491 static const char minivm_list_templates_help[] =
2492 "Usage: minivm list templates\n"
2493 " Lists message templates for e-mail, paging and IM\n";
2495 static const char minivm_show_stats_help[] =
2496 "Usage: minivm show stats\n"
2497 " Display Mini-Voicemail counters\n";
2499 static const char minivm_show_settings_help[] =
2500 "Usage: minivm show settings\n"
2501 " Display Mini-Voicemail general settings\n";
2503 static const char minivm_reload_help[] =
2504 "Usage: minivm reload\n"
2505 " Reload mini-voicemail configuration and reset statistics\n";
2507 /*! \brief CLI routine for listing templates */
2508 static int handle_minivm_list_templates(int fd, int argc, char *argv[])
2510 struct minivm_template *this;
2511 char *output_format = "%-15s %-10s %-10s %-15.15s %-50s\n";
2515 return RESULT_SHOWUSAGE;
2517 AST_LIST_LOCK(&message_templates);
2518 if (AST_LIST_EMPTY(&message_templates)) {
2519 ast_cli(fd, "There are no message templates defined\n");
2520 AST_LIST_UNLOCK(&message_templates);
2521 return RESULT_FAILURE;
2523 ast_cli(fd, output_format, "Template name", "Charset", "Locale", "Attach media", "Subject");
2524 ast_cli(fd, output_format, "-------------", "-------", "------", "------------", "-------");
2525 AST_LIST_TRAVERSE(&message_templates, this, list) {
2526 ast_cli(fd, output_format, this->name,
2527 this->charset ? this->charset : "-",
2528 this->locale ? this->locale : "-",
2529 this->attachment ? "Yes" : "No",
2530 this->subject ? this->subject : "-");
2533 AST_LIST_UNLOCK(&message_templates);
2534 ast_cli(fd, "\n * Total: %d minivoicemail message templates\n", count);
2535 return RESULT_SUCCESS;
2538 /*! \brief CLI command to list voicemail accounts */
2539 static int handle_minivm_show_users(int fd, int argc, char *argv[])
2541 struct minivm_account *vmu;
2542 char *output_format = "%-23s %-15s %-15s %-10s %-10s %-50s\n";
2545 if ((argc < 3) || (argc > 5) || (argc == 4))
2546 return RESULT_SHOWUSAGE;
2547 if ((argc == 5) && strcmp(argv[3],"for"))
2548 return RESULT_SHOWUSAGE;
2550 AST_LIST_LOCK(&minivm_accounts);
2551 if (AST_LIST_EMPTY(&minivm_accounts)) {
2552 ast_cli(fd, "There are no voicemail users currently defined\n");
2553 AST_LIST_UNLOCK(&minivm_accounts);
2554 return RESULT_FAILURE;
2556 ast_cli(fd, output_format, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2557 ast_cli(fd, output_format, "----", "----------", "----------", "----", "------", "---------");
2558 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2562 if ((argc == 3) || ((argc == 5) && !strcmp(argv[4], vmu->domain))) {
2564 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2565 ast_cli(fd, output_format, tmp, vmu->etemplate ? vmu->etemplate : "-",
2566 vmu->ptemplate ? vmu->ptemplate : "-",
2567 vmu->zonetag ? vmu->zonetag : "-",
2568 vmu->attachfmt ? vmu->attachfmt : "-",
2572 AST_LIST_UNLOCK(&minivm_accounts);
2573 ast_cli(fd, "\n * Total: %d minivoicemail accounts\n", count);
2574 return RESULT_SUCCESS;
2577 /*! \brief Show a list of voicemail zones in the CLI */
2578 static int handle_minivm_show_zones(int fd, int argc, char *argv[])
2580 struct minivm_zone *zone;
2581 char *output_format = "%-15s %-20s %-45s\n";
2582 int res = RESULT_SUCCESS;
2585 return RESULT_SHOWUSAGE;
2587 AST_LIST_LOCK(&minivm_zones);
2588 if (!AST_LIST_EMPTY(&minivm_zones)) {
2589 ast_cli(fd, output_format, "Zone", "Timezone", "Message Format");
2590 ast_cli(fd, output_format, "----", "--------", "--------------");
2591 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2592 ast_cli(fd, output_format, zone->name, zone->timezone, zone->msg_format);
2595 ast_cli(fd, "There are no voicemail zones currently defined\n");
2596 res = RESULT_FAILURE;
2598 AST_LIST_UNLOCK(&minivm_zones);
2604 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2608 struct minivm_account *vmu;
2609 const char *domain = "";
2611 /* 0 - show; 1 - voicemail; 2 - users; 3 - for; 4 - <domain> */
2615 return (state == 0) ? ast_strdup("for") : NULL;
2616 wordlen = strlen(word);
2617 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2618 if (!strncasecmp(word, vmu->domain, wordlen)) {
2619 if (domain && strcmp(domain, vmu->domain) && ++which > state)
2620 return ast_strdup(vmu->domain);
2621 /* ignore repeated domains ? */
2622 domain = vmu->domain;
2628 /*! \brief CLI Show settings */
2629 static int handle_minivm_show_settings(int fd, int argc, char *argv[])
2631 ast_cli(fd, "* Mini-Voicemail general settings\n");
2632 ast_cli(fd, " -------------------------------\n");
2634 ast_cli(fd, " Mail command (shell): %s\n", global_mailcmd);
2635 ast_cli(fd, " Max silence: %d\n", global_maxsilence);
2636 ast_cli(fd, " Silence treshold: %d\n", global_silencethreshold);
2637 ast_cli(fd, " Max message length (secs): %d\n", global_vmmaxmessage);
2638 ast_cli(fd, " Min message length (secs): %d\n", global_vmminmessage);
2639 ast_cli(fd, " Default format: %s\n", default_vmformat);
2640 ast_cli(fd, " Extern notify (shell): %s\n", global_externnotify);
2641 ast_cli(fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2642 ast_cli(fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2643 ast_cli(fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2646 return RESULT_SUCCESS;
2649 /*! \brief Show stats */
2650 static int handle_minivm_show_stats(int fd, int argc, char *argv[])
2655 ast_cli(fd, "* Mini-Voicemail statistics\n");
2656 ast_cli(fd, " -------------------------\n");
2658 ast_cli(fd, " Voicemail accounts: %-5.5d\n", global_stats.voicemailaccounts);
2659 ast_cli(fd, " Templates: %-5.5d\n", global_stats.templates);
2660 ast_cli(fd, " Timezones: %-5.5d\n", global_stats.timezones);
2661 if (global_stats.receivedmessages == 0) {
2662 ast_cli(fd, " Received messages since last reset: <none>\n");
2664 ast_cli(fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
2665 ast_localtime(&global_stats.lastreceived, &time, NULL);
2666 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2667 ast_cli(fd, " Last received voicemail: %s\n", buf);
2669 ast_localtime(&global_stats.reset, &time, NULL);
2670 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2671 ast_cli(fd, " Last reset: %s\n", buf);
2674 return RESULT_SUCCESS;
2677 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
2678 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2680 struct minivm_account *vmu;
2681 char *username, *domain, *colname;
2683 if (!(username = ast_strdupa(data))) {
2684 ast_log(LOG_ERROR, "Memory Error!\n");
2688 if ((colname = strchr(username, ':'))) {
2694 if ((domain = strchr(username, '@'))) {
2698 if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2699 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2703 if (!(vmu = find_account(domain, username, TRUE)))
2706 if (!strcasecmp(colname, "hasaccount")) {
2707 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2708 } else if (!strcasecmp(colname, "fullname")) {
2709 ast_copy_string(buf, vmu->fullname, len);
2710 } else if (!strcasecmp(colname, "email")) {
2711 if (!ast_strlen_zero(vmu->email))
2712 ast_copy_string(buf, vmu->email, len);
2714 snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2715 } else if (!strcasecmp(colname, "pager")) {
2716 ast_copy_string(buf, vmu->pager, len);
2717 } else if (!strcasecmp(colname, "etemplate")) {
2718 if (!ast_strlen_zero(vmu->etemplate))
2719 ast_copy_string(buf, vmu->etemplate, len);
2721 ast_copy_string(buf, "email-default", len);
2722 } else if (!strcasecmp(colname, "language")) {
2723 ast_copy_string(buf, vmu->language, len);
2724 } else if (!strcasecmp(colname, "timezone")) {
2725 ast_copy_string(buf, vmu->zonetag, len);
2726 } else if (!strcasecmp(colname, "ptemplate")) {
2727 if (!ast_strlen_zero(vmu->ptemplate))
2728 ast_copy_string(buf, vmu->ptemplate, len);
2730 ast_copy_string(buf, "email-default", len);
2731 } else if (!strcasecmp(colname, "accountcode")) {
2732 ast_copy_string(buf, vmu->accountcode, len);
2733 } else if (!strcasecmp(colname, "pincode")) {
2734 ast_copy_string(buf, vmu->pincode, len);
2735 } else if (!strcasecmp(colname, "path")) {
2736 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2737 } else { /* Look in channel variables */
2738 struct ast_variable *var;
2741 for (var = vmu->chanvars ; var ; var = var->next)
2742 if (!strcmp(var->name, colname)) {
2743 ast_copy_string(buf, var->value, len);
2749 if(ast_test_flag(vmu, MVM_ALLOCED))
2755 /*! \brief lock directory
2757 only return failure if ast_lock_path returns 'timeout',
2758 not if the path does not exist or any other reason
2760 static int vm_lock_path(const char *path)
2762 switch (ast_lock_path(path)) {
2763 case AST_LOCK_TIMEOUT:
2770 /*! \brief Access counter file, lock directory, read and possibly write it again changed
2771 \param directory Directory to crate file in
2772 \param countername filename
2773 \param value If set to zero, we only read the variable
2774 \param operand 0 to read, 1 to set new value, 2 to change
2775 \return -1 on error, otherwise counter value
2777 static int access_counter_file(char *directory, char *countername, int value, int operand)
2779 char filename[BUFSIZ];
2780 char readbuf[BUFSIZ];
2782 int old = 0, counter = 0;
2784 /* Lock directory */
2785 if (vm_lock_path(directory)) {
2786 return -1; /* Could not lock directory */
2788 snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
2790 counterfile = fopen(filename, "r");
2792 if(fgets(readbuf, sizeof(readbuf), counterfile)) {
2793 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
2794 old = counter = atoi(readbuf);
2796 fclose(counterfile);
2800 case 0: /* Read only */
2801 ast_unlock_path(directory);
2802 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
2805 case 1: /* Set new value */
2808 case 2: /* Change value */
2810 if (counter < 0) /* Don't allow counters to fall below zero */
2815 /* Now, write the new value to the file */
2816 counterfile = fopen(filename, "w");
2818 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
2819 ast_unlock_path(directory);
2820 return -1; /* Could not open file for writing */
2822 fprintf(counterfile, "%d\n\n", counter);
2823 fclose(counterfile);
2824 ast_unlock_path(directory);
2825 ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
2829 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
2830 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2832 char *username, *domain, *countername;
2833 struct minivm_account *vmu = NULL;
2834 char userpath[BUFSIZ];
2839 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
2840 ast_log(LOG_WARNING, "Memory error!\n");
2843 if ((countername = strchr(username, ':'))) {
2844 *countername = '\0';
2848 if ((domain = strchr(username, '@'))) {
2853 /* If we have neither username nor domain now, let's give up */
2854 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2855 ast_log(LOG_ERROR, "No account given\n");
2859 if (ast_strlen_zero(countername)) {
2860 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
2864 /* We only have a domain, no username */
2865 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2870 /* If we can't find account or if the account is temporary, return. */
2871 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
2872 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
2876 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
2878 /* We have the path, now read the counter file */
2879 res = access_counter_file(userpath, countername, 0, 0);
2881 snprintf(buf, len, "%d", res);
2885 /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
2886 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
2888 char *username, *domain, *countername, *operand;
2889 char userpath[BUFSIZ];
2890 struct minivm_account *vmu;
2896 change = atoi(value);
2898 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
2899 ast_log(LOG_WARNING, "Memory error!\n");
2903 if ((countername = strchr(username, ':'))) {
2904 *countername = '\0';
2907 if ((operand = strchr(countername, ':'))) {
2912 if ((domain = strchr(username, '@'))) {
2917 /* If we have neither username nor domain now, let's give up */
2918 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2919 ast_log(LOG_ERROR, "No account given\n");
2923 /* We only have a domain, no username */
2924 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2929 if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
2930 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
2934 /* If we can't find account or if the account is temporary, return. */
2935 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
2936 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
2940 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
2941 /* Now, find out our operator */
2942 if (*operand == 'i') /* Increment */
2944 else if (*operand == 'd') {
2945 change = change * -1;
2947 } else if (*operand == 's')
2950 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
2954 /* We have the path, now read the counter file */
2955 access_counter_file(userpath, countername, change, operation);
2960 /*! \brief CLI commands for Mini-voicemail */
2961 static struct ast_cli_entry cli_minivm[] = {
2962 { { "minivm", "list", "accounts", NULL },
2963 handle_minivm_show_users, "List defined mini-voicemail boxes",
2964 minivm_show_users_help, complete_minivm_show_users, NULL },
2966 { { "minivm", "list", "zones", NULL },
2967 handle_minivm_show_zones, "List zone message formats",
2968 minivm_show_zones_help, NULL, NULL },
2970 { { "minivm", "list", "templates", NULL },
2971 handle_minivm_list_templates, "List message templates",
2972 minivm_list_templates_help, NULL, NULL },
2974 { { "minivm", "reload", NULL, NULL },
2975 handle_minivm_reload, "Reload Mini-voicemail configuration",
2976 minivm_reload_help, NULL, NULL },
2978 { { "minivm", "show", "stats", NULL },
2979 handle_minivm_show_stats, "Show some mini-voicemail statistics",
2980 minivm_show_stats_help, NULL, NULL },
2982 { { "minivm", "show", "settings", NULL },
2983 handle_minivm_show_settings, "Show mini-voicemail general settings",
2984 minivm_show_settings_help, NULL, NULL },
2987 static struct ast_custom_function minivm_counter_function = {
2988 .name = "MINIVMCOUNTER",
2989 .synopsis = "Reads or sets counters for MiniVoicemail message",
2990 .syntax = "MINIVMCOUNTER(<account>:name[:operand])",
2991 .read = minivm_counter_func_read,
2992 .write = minivm_counter_func_write,
2993 .desc = "Valid operands for changing the value of a counter when assigning a value are:\n"
2994 "- i Increment by value\n"
2995 "- d Decrement by value\n"
2996 "- s Set to value\n"
2997 "\nThe counters never goes below zero.\n"
2998 "- The name of the counter is a string, up to 10 characters\n"
2999 "- If account is given and it exists, the counter is specific for the account\n"
3000 "- If account is a domain and the domain directory exists, counters are specific for a domain\n"
3001 "The operation is atomic and the counter is locked while changing the value\n"
3002 "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n"
3003 "realtime functions if you are using a database to operate your Asterisk\n",
3006 static struct ast_custom_function minivm_account_function = {
3007 .name = "MINIVMACCOUNT",
3008 .synopsis = "Gets MiniVoicemail account information",
3009 .syntax = "MINIVMACCOUNT(<account>:item)",
3010 .read = minivm_account_func_read,
3011 .desc = "Valid items are:\n"
3012 "- path Path to account mailbox (if account exists, otherwise temporary mailbox)\n"
3013 "- hasaccount 1 if static Minivm account exists, 0 otherwise\n"
3014 "- fullname Full name of account owner\n"
3015 "- email Email address used for account\n"
3016 "- etemplate E-mail template for account (default template if none is configured)\n"
3017 "- ptemplate Pager template for account (default template if none is configured)\n"
3018 "- accountcode Account code for voicemail account\n"
3019 "- pincode Pin code for voicemail account\n"
3020 "- timezone Time zone for voicemail account\n"
3021 "- language Language for voicemail account\n"
3022 "- <channel variable name> Channel variable value (set in configuration for account)\n"
3026 /*! \brief Load mini voicemail module */
3027 static int load_module(void)
3031 res = ast_register_application(app_minivm_record, minivm_record_exec, synopsis_minivm_record, descrip_minivm_record);
3032 res = ast_register_application(app_minivm_greet, minivm_greet_exec, synopsis_minivm_greet, descrip_minivm_greet);
3033 res = ast_register_application(app_minivm_notify, minivm_notify_exec, synopsis_minivm_notify, descrip_minivm_notify);
3034 res = ast_register_application(app_minivm_delete, minivm_delete_exec, synopsis_minivm_delete, descrip_minivm_delete);
3035 res = ast_register_application(app_minivm_accmess, minivm_accmess_exec, synopsis_minivm_accmess, descrip_minivm_accmess);
3037 ast_custom_function_register(&minivm_account_function);
3038 ast_custom_function_register(&minivm_counter_function);
3042 if ((res = load_config()))
3045 ast_cli_register_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0]));
3047 /* compute the location of the voicemail spool directory */
3048 snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3053 /*! \brief Reload mini voicemail module */
3054 static int reload(void)
3056 return(load_config());
3059 /*! \brief Reload cofiguration */
3060 static int handle_minivm_reload(int fd, int argc, char *argv[])
3063 ast_cli(fd, "\n-- Mini voicemail re-configured \n");
3064 return RESULT_SUCCESS;
3067 /*! \brief Unload mini voicemail module */
3068 static int unload_module(void)
3072 res = ast_unregister_application(app_minivm_record);
3073 res |= ast_unregister_application(app_minivm_greet);
3074 res |= ast_unregister_application(app_minivm_notify);
3075 res |= ast_unregister_application(app_minivm_delete);
3076 res |= ast_unregister_application(app_minivm_accmess);
3077 ast_cli_unregister_multiple(cli_minivm, sizeof(cli_minivm)/sizeof(cli_minivm[0]));
3078 ast_custom_function_unregister(&minivm_account_function);
3079 ast_custom_function_unregister(&minivm_counter_function);
3081 message_destroy_list(); /* Destroy list of voicemail message templates */
3082 timezone_destroy_list(); /* Destroy list of timezones */
3083 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
3089 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3090 .load = load_module,
3091 .unload = unload_module,