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 unsigned int 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 time_t reset; /*!< Time for last reset */
429 int receivedmessages; /*!< Number of received messages since reset */
430 time_t 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(struct minivm_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 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 if (option_debug > 2)
510 ast_log(LOG_DEBUG, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
511 if (!strcasecmp(var->name, "fromaddress")) {
512 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
513 } else if (!strcasecmp(var->name, "fromemail")) {
514 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
515 } else if (!strcasecmp(var->name, "subject")) {
516 ast_copy_string(template->subject, var->value, sizeof(template->subject));
517 } else if (!strcasecmp(var->name, "locale")) {
518 ast_copy_string(template->locale, var->value, sizeof(template->locale));
519 } else if (!strcasecmp(var->name, "attachmedia")) {
520 template->attachment = ast_true(var->value);
521 } else if (!strcasecmp(var->name, "dateformat")) {
522 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
523 } else if (!strcasecmp(var->name, "charset")) {
524 ast_copy_string(template->charset, var->value, sizeof(template->charset));
525 } else if (!strcasecmp(var->name, "templatefile")) {
527 free(template->body);
528 template->body = message_template_parse_filebody(var->value);
529 if (!template->body) {
530 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
533 } else if (!strcasecmp(var->name, "messagebody")) {
535 free(template->body);
536 template->body = message_template_parse_emailbody(var->value);
537 if (!template->body) {
538 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
542 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
548 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
550 AST_LIST_LOCK(&message_templates);
551 AST_LIST_INSERT_TAIL(&message_templates, template, list);
552 AST_LIST_UNLOCK(&message_templates);
554 global_stats.templates++;
559 /*! \brief Find named template */
560 static struct minivm_template *message_template_find(const char *name)
562 struct minivm_template *this, *res = NULL;
564 if (ast_strlen_zero(name))
567 AST_LIST_LOCK(&message_templates);
568 AST_LIST_TRAVERSE(&message_templates, this, list) {
569 if (!strcasecmp(this->name, name)) {
574 AST_LIST_UNLOCK(&message_templates);
580 /*! \brief Clear list of templates */
581 static void message_destroy_list(void)
583 struct minivm_template *this;
584 AST_LIST_LOCK(&message_templates);
585 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list)))
586 message_template_free(this);
588 AST_LIST_UNLOCK(&message_templates);
591 /*! \brief read buffer from file (base64 conversion) */
592 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
599 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
613 /*! \brief read character from file to buffer (base64 conversion) */
614 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
616 if (bio->iocp >= bio->iolen) {
617 if (!b64_inbuf(bio, fi))
621 return bio->iobuf[bio->iocp++];
624 /*! \brief write buffer to file (base64 conversion) */
625 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
627 if (bio->linelength >= B64_BASELINELEN) {
628 if (fputs(EOL,so) == EOF)
634 if (putc(((unsigned char) c), so) == EOF)
642 /*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
643 static int base_encode(char *filename, FILE *so)
645 unsigned char dtable[B64_BASEMAXINLINE];
648 struct b64_baseio bio;
650 memset(&bio, 0, sizeof(bio));
651 bio.iocp = B64_BASEMAXINLINE;
653 if (!(fi = fopen(filename, "rb"))) {
654 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
658 for (i= 0; i<9; i++) {
662 dtable[26+i+9]= 'j'+i;
664 for (i= 0; i < 8; i++) {
666 dtable[26+i+18]= 's'+i;
668 for (i= 0; i < 10; i++) {
675 unsigned char igroup[3], ogroup[4];
678 igroup[0]= igroup[1]= igroup[2]= 0;
680 for (n= 0; n < 3; n++) {
681 if ((c = b64_inchar(&bio, fi)) == EOF) {
685 igroup[n]= (unsigned char)c;
689 ogroup[0]= dtable[igroup[0]>>2];
690 ogroup[1]= dtable[((igroup[0]&3)<<4)|(igroup[1]>>4)];
691 ogroup[2]= dtable[((igroup[1]&0xF)<<2)|(igroup[2]>>6)];
692 ogroup[3]= dtable[igroup[2]&0x3F];
702 b64_ochar(&bio, ogroup[i], so);
706 /* Put end of line - line feed */
707 if (fputs(EOL, so) == EOF)
715 static int get_date(char *s, int len)
721 return strftime(s, len, "%a %b %e %r %Z %Y", &tm);
725 /*! \brief Free user structure - if it's allocated */
726 static void free_user(struct minivm_account *vmu)
729 ast_variables_destroy(vmu->chanvars);
735 /*! \brief Prepare for voicemail template by adding channel variables
738 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)
741 struct ast_variable *var;
744 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
748 for (var = vmu->chanvars ; var ; var = var->next)
749 pbx_builtin_setvar_helper(channel, var->name, var->value);
751 /* Prepare variables for substition in email body and subject */
752 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
753 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
754 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
755 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
756 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
757 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
758 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
759 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
760 if (!ast_strlen_zero(counter))
761 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
764 /*! \brief Set default values for Mini-Voicemail users */
765 static void populate_defaults(struct minivm_account *vmu)
767 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
768 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
769 vmu->volgain = global_volgain;
772 /*! \brief Fix quote of mail headers for non-ascii characters */
773 static char *mailheader_quote(const char *from, char *to, size_t len)
777 for (; ptr < to + len - 1; from++) {
780 else if (*from == '\0')
784 if (ptr < to + len - 1)
791 /*! \brief Allocate new vm user and set default values */
792 static struct minivm_account *mvm_user_alloc(void)
794 struct minivm_account *new;
796 new = calloc(1, sizeof(struct minivm_account));
799 populate_defaults(new);
805 /*! \brief Clear list of users */
806 static void vmaccounts_destroy_list(void)
808 struct minivm_account *this;
809 AST_LIST_LOCK(&minivm_accounts);
810 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
812 AST_LIST_UNLOCK(&minivm_accounts);
816 /*! \brief Find user from static memory object list */
817 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
819 struct minivm_account *vmu = NULL, *cur;
822 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
823 ast_log(LOG_NOTICE, "No username or domain? \n");
826 if (option_debug > 2)
827 ast_log(LOG_DEBUG, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain);
829 AST_LIST_LOCK(&minivm_accounts);
830 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
831 /* Is this the voicemail account we're looking for? */
832 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
835 AST_LIST_UNLOCK(&minivm_accounts);
838 if (option_debug > 2)
839 ast_log(LOG_DEBUG, "-_-_- Found account for %s@%s\n", username, domain);
843 vmu = find_user_realtime(domain, username);
845 if (createtemp && !vmu) {
846 /* Create a temporary user, send e-mail and be gone */
847 vmu = mvm_user_alloc();
848 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
850 ast_copy_string(vmu->username, username, sizeof(vmu->username));
851 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
853 ast_log(LOG_DEBUG, "--- Created temporary account\n");
860 /*! \brief Find user in realtime storage
861 Returns pointer to minivm_account structure
863 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
865 struct ast_variable *var;
866 struct minivm_account *retval;
867 char name[MAXHOSTNAMELEN];
869 retval = mvm_user_alloc();
874 ast_copy_string(retval->username, username, sizeof(retval->username));
876 populate_defaults(retval);
877 var = ast_load_realtime("minivm", "username", username, "domain", domain, NULL);
884 snprintf(name, sizeof(name), "%s@%s", username, domain);
885 create_vmaccount(name, var, TRUE);
887 ast_variables_destroy(var);
891 /*! \brief Send voicemail with audio file as an attachment */
892 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)
896 char email[256] = "";
900 char fname[PATH_MAX];
902 char tmp[80] = "/tmp/astmail-XXXXXX";
906 struct minivm_zone *the_zone = NULL;
908 struct ast_channel *ast;
910 char *passdata = NULL;
911 char *passdata2 = NULL;
915 if (type == MVM_MESSAGE_EMAIL) {
916 if (vmu && !ast_strlen_zero(vmu->email)) {
917 ast_copy_string(email, vmu->email, sizeof(email));
918 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
919 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
920 } else if (type == MVM_MESSAGE_PAGE) {
921 ast_copy_string(email, vmu->pager, sizeof(email));
924 if (ast_strlen_zero(email)) {
925 ast_log(LOG_WARNING, "No address to send message to.\n");
929 if (option_debug > 2)
930 ast_log(LOG_DEBUG, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
932 if (!strcmp(format, "wav49"))
936 /* If we have a gain option, process it now with sox */
937 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
938 char newtmp[PATH_MAX];
939 char tmpcmd[PATH_MAX];
942 snprintf(newtmp, sizeof(newtmp), "/tmp/XXXXXX");
943 if (option_debug > 2)
944 ast_log(LOG_DEBUG, "newtmp: %s\n", newtmp);
945 tmpfd = mkstemp(newtmp);
946 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
947 ast_safe_system(tmpcmd);
948 finalfilename = newtmp;
949 if (option_debug > 2)
950 ast_log (LOG_DEBUG, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
952 finalfilename = ast_strdupa(filename);
955 /* Create file name */
956 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
958 if (option_debug && template->attachment)
959 ast_log(LOG_DEBUG, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
961 /* Make a temporary file instead of piping directly to sendmail, in case the mail
965 p = fdopen(pfd, "w");
971 ast_log(LOG_DEBUG, "-_-_- Opening temp file for e-mail: %s\n", tmp);
974 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
977 /* Allocate channel used for chanvar substitution */
978 ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0);
981 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
983 /* Does this user have a timezone specified? */
984 if (!ast_strlen_zero(vmu->zonetag)) {
985 /* Find the zone in the list */
986 struct minivm_zone *z;
987 AST_LIST_LOCK(&minivm_zones);
988 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
989 if (strcmp(z->name, vmu->zonetag))
993 AST_LIST_UNLOCK(&minivm_zones);
997 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
998 strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1000 /* Start printing the email to the temporary file */
1001 fprintf(p, "Date: %s\n", date);
1003 /* Set date format for voicemail mail */
1004 strftime(date, sizeof(date), template->dateformat, &tm);
1007 /* Populate channel with channel variables for substitution */
1008 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1010 /* Find email address to use */
1011 /* If there's a server e-mail adress in the account, user that, othterwise template */
1012 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1014 /* Find name to user for server e-mail */
1015 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1017 /* If needed, add hostname as domain */
1018 if (ast_strlen_zero(fromemail))
1019 fromemail = "asterisk";
1021 if (strchr(fromemail, '@'))
1022 ast_copy_string(who, fromemail, sizeof(who));
1024 char host[MAXHOSTNAMELEN];
1025 gethostname(host, sizeof(host)-1);
1026 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1029 if (ast_strlen_zero(fromaddress)) {
1030 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1032 if (option_debug > 3)
1033 ast_log(LOG_DEBUG, "-_-_- Fromaddress template: %s\n", fromaddress);
1034 /* Allocate a buffer big enough for variable substitution */
1035 int vmlen = strlen(fromaddress) * 3 + 200;
1037 if ((passdata = alloca(vmlen))) {
1038 memset(passdata, 0, vmlen);
1039 pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen);
1040 len_passdata = strlen(passdata) * 2 + 3;
1041 passdata2 = alloca(len_passdata);
1042 fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who);
1044 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1049 if (option_debug > 3)
1050 ast_log(LOG_DEBUG, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
1052 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, getpid(), who);
1053 len_passdata = strlen(vmu->fullname) * 2 + 3;
1054 passdata2 = alloca(len_passdata);
1055 if (!ast_strlen_zero(vmu->email))
1056 fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1058 fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain);
1060 if (!ast_strlen_zero(template->subject)) {
1062 int vmlen = strlen(template->subject) * 3 + 200;
1063 if ((passdata = alloca(vmlen))) {
1064 memset(passdata, 0, vmlen);
1065 pbx_substitute_variables_helper(ast, template->subject, passdata, vmlen);
1066 fprintf(p, "Subject: %s\n", passdata);
1068 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1072 if (option_debug > 3)
1073 ast_log(LOG_DEBUG, "-_-_- Subject now: %s\n", passdata);
1075 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1076 ast_log(LOG_DEBUG, "-_-_- Using default subject for this email \n");
1080 if (option_debug > 2)
1081 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1082 fprintf(p, "MIME-Version: 1.0\n");
1084 /* Something unique. */
1085 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, getpid(), (unsigned int)rand());
1087 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1089 fprintf(p, "--%s\n", bound);
1090 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset);
1091 if (!ast_strlen_zero(template->body)) {
1093 int vmlen = strlen(template->body)*3 + 200;
1094 if ((passdata = alloca(vmlen))) {
1095 memset(passdata, 0, vmlen);
1096 pbx_substitute_variables_helper(ast, template->body, passdata, vmlen);
1097 if (option_debug > 2)
1098 ast_log(LOG_DEBUG, "Message now: %s\n-----\n", passdata);
1099 fprintf(p, "%s\n", passdata);
1101 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1103 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1105 "in mailbox %s from %s, on %s so you might\n"
1106 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1107 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1108 if (option_debug > 2)
1109 ast_log(LOG_DEBUG, "Using default message body (no template)\n-----\n");
1111 /* Eww. We want formats to tell us their own MIME type */
1112 if (template->attachment) {
1113 if (option_debug > 2)
1114 ast_log(LOG_DEBUG, "-_-_- Attaching file to message: %s\n", fname);
1115 char *ctype = "audio/x-";
1116 if (!strcasecmp(format, "ogg"))
1117 ctype = "application/";
1119 fprintf(p, "--%s\n", bound);
1120 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1121 fprintf(p, "Content-Transfer-Encoding: base64\n");
1122 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1123 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1125 base_encode(fname, p);
1126 fprintf(p, "\n\n--%s--\n.\n", bound);
1129 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1130 ast_safe_system(tmp2);
1132 ast_log(LOG_DEBUG, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1133 if (option_debug > 2)
1134 ast_log(LOG_DEBUG, "-_-_- Actual command used: %s\n", tmp2);
1137 ast_channel_free(ast);
1141 /*! \brief Create directory based on components */
1142 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1144 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1147 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1148 * \param dest String. base directory.
1149 * \param domain String. Ignored if is null or empty string.
1150 * \param username String. Ignored if is null or empty string.
1151 * \param folder String. Ignored if is null or empty string.
1152 * \return 0 on failure, 1 on success.
1154 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1156 struct stat filestat;
1157 make_dir(dest, len, domain, username, folder ? folder : "");
1158 if (stat(dest, &filestat)== -1)
1164 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1165 * \param dest String. base directory.
1166 * \param len Length of directory string
1167 * \param domain String. Ignored if is null or empty string.
1168 * \param folder String. Ignored if is null or empty string.
1169 * \param ext String. Ignored if is null or empty string.
1170 * \return -1 on failure, 0 on success.
1172 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1174 mode_t mode = VOICEMAIL_DIR_MODE;
1176 if(!ast_strlen_zero(domain)) {
1177 make_dir(dest, len, domain, "", "");
1178 if(mkdir(dest, mode) && errno != EEXIST) {
1179 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
1183 if(!ast_strlen_zero(username)) {
1184 make_dir(dest, len, domain, username, "");
1185 if(mkdir(dest, mode) && errno != EEXIST) {
1186 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
1190 if(!ast_strlen_zero(folder)) {
1191 make_dir(dest, len, domain, username, folder);
1192 if(mkdir(dest, mode) && errno != EEXIST) {
1193 ast_log(LOG_WARNING, "mkdir '%s' failed: %s\n", dest, strerror(errno));
1197 if (option_debug > 1)
1198 ast_log(LOG_DEBUG, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1203 /*! \brief Play intro message before recording voicemail
1205 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1210 if (option_debug > 1)
1211 ast_log(LOG_DEBUG, "-_-_- Still preparing to play message ...\n");
1213 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1215 if (ast_fileexists(fn, NULL, NULL) > 0) {
1216 res = ast_streamfile(chan, fn, chan->language);
1219 res = ast_waitstream(chan, ecodes);
1223 int numericusername = 1;
1226 if (option_debug > 1)
1227 ast_log(LOG_DEBUG, "-_-_- No personal prompts. Using default prompt set for language\n");
1230 if (option_debug > 1)
1231 ast_log(LOG_DEBUG, "-_-_- Numeric? Checking %c\n", *i);
1233 numericusername = FALSE;
1239 if (numericusername) {
1240 if(ast_streamfile(chan, "vm-theperson", chan->language))
1242 if ((res = ast_waitstream(chan, ecodes)))
1245 res = ast_say_digit_str(chan, username, ecodes, chan->language);
1249 if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1251 if ((res = ast_waitstream(chan, ecodes)))
1256 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1259 res = ast_waitstream(chan, ecodes);
1263 /*! \brief Delete media files and attribute file */
1264 static int vm_delete(char *file)
1269 ast_log(LOG_DEBUG, "-_-_- Deleting voicemail file %s\n", file);
1271 res = unlink(file); /* Remove the meta data file */
1272 res |= ast_filedelete(file, NULL); /* remove the media file */
1277 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1278 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1279 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1280 signed char record_gain)
1283 int max_attempts = 3;
1286 int message_exists = 0;
1287 signed char zero_gain = 0;
1288 char *acceptdtmf = "#";
1289 char *canceldtmf = "";
1291 /* Note that urgent and private are for flagging messages as such in the future */
1293 /* barf if no pointer passed to store duration in */
1294 if (duration == NULL) {
1295 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1299 cmd = '3'; /* Want to start by recording */
1301 while ((cmd >= 0) && (cmd != 't')) {
1305 if (option_verbose > 2)
1306 ast_verbose(VERBOSE_PREFIX_3 "Reviewing the message\n");
1307 ast_streamfile(chan, recordfile, chan->language);
1308 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1313 if (option_verbose > 2) {
1315 ast_verbose(VERBOSE_PREFIX_3 "Re-recording the message\n");
1317 ast_verbose(VERBOSE_PREFIX_3 "Recording the message\n");
1319 if (recorded && outsidecaller)
1320 cmd = ast_play_and_wait(chan, "beep");
1322 /* 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 */
1324 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1325 if (ast_test_flag(vmu, MVM_OPERATOR))
1327 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1329 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1330 if (cmd == -1) /* User has hung up, no options to give */
1334 else if (cmd == '*')
1337 /* If all is well, a message exists */
1350 cmd = ast_play_and_wait(chan, "vm-sorry");
1353 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1354 cmd = ast_play_and_wait(chan, "vm-sorry");
1357 if (message_exists || recorded) {
1358 cmd = ast_play_and_wait(chan, "vm-saveoper");
1360 cmd = ast_waitfordigit(chan, 3000);
1362 ast_play_and_wait(chan, "vm-msgsaved");
1365 ast_play_and_wait(chan, "vm-deleted");
1366 vm_delete(recordfile);
1372 /* If the caller is an ouside caller, and the review option is enabled,
1373 allow them to review the message, but let the owner of the box review
1375 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1377 if (message_exists) {
1378 cmd = ast_play_and_wait(chan, "vm-review");
1380 cmd = ast_play_and_wait(chan, "vm-torerecord");
1382 cmd = ast_waitfordigit(chan, 600);
1385 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1386 cmd = ast_play_and_wait(chan, "vm-reachoper");
1388 cmd = ast_waitfordigit(chan, 600);
1391 cmd = ast_waitfordigit(chan, 6000);
1395 if (attempts > max_attempts) {
1401 ast_play_and_wait(chan, "vm-goodbye");
1407 /*! \brief Run external notification for voicemail message */
1408 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1410 char arguments[BUFSIZ];
1412 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1415 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1416 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1417 vmu->username, vmu->domain,
1418 chan->cid.cid_name, chan->cid.cid_num);
1421 ast_log(LOG_DEBUG, "Executing: %s\n", arguments);
1423 ast_safe_system(arguments);
1426 /*! \brief Send message to voicemail account owner */
1427 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)
1430 struct minivm_template *etemplate;
1431 char *messageformat;
1433 char oldlocale[100];
1434 const char *counter;
1436 if (!ast_strlen_zero(vmu->attachfmt)) {
1437 if (strstr(format, vmu->attachfmt)) {
1438 format = vmu->attachfmt;
1440 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);
1443 etemplate = message_template_find(vmu->etemplate);
1445 etemplate = message_template_find(templatename);
1447 etemplate = message_template_find("email-default");
1449 /* Attach only the first format */
1450 stringp = messageformat = ast_strdupa(format);
1451 strsep(&stringp, "|");
1453 if (!ast_strlen_zero(etemplate->locale)) {
1455 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1456 if (option_debug > 1)
1457 ast_log(LOG_DEBUG, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1458 newlocale = setlocale(LC_TIME, etemplate->locale);
1459 if (newlocale == NULL) {
1460 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1466 /* Read counter if available */
1467 counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER");
1468 if (option_debug > 1) {
1469 if (ast_strlen_zero(counter))
1470 ast_log(LOG_DEBUG, "-_-_- MVM_COUNTER not found\n");
1472 ast_log(LOG_DEBUG, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter);
1475 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1477 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1478 /* Find template for paging */
1479 etemplate = message_template_find(vmu->ptemplate);
1481 etemplate = message_template_find("pager-default");
1482 if (etemplate->locale) {
1483 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1484 setlocale(LC_TIME, etemplate->locale);
1487 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1490 manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1492 run_externnotify(chan, vmu); /* Run external notification */
1494 if (etemplate->locale)
1495 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1500 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1501 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1503 char tmptxtfile[PATH_MAX];
1506 int res = 0, txtdes;
1510 char tmpdir[PATH_MAX];
1511 char ext_context[256] = "";
1515 struct minivm_account *vmu;
1518 ast_copy_string(tmp, username, sizeof(tmp));
1520 domain = strchr(tmp, '@');
1526 if (!(vmu = find_account(domain, username, TRUE))) {
1527 /* We could not find user, let's exit */
1528 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1529 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1533 /* Setup pre-file if appropriate */
1534 if (strcmp(vmu->domain, "localhost"))
1535 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1537 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1539 /* The meat of recording the message... All the announcements and beeps have been played*/
1540 if (ast_strlen_zero(vmu->attachfmt))
1541 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1543 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1545 if (ast_strlen_zero(fmt)) {
1546 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1547 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1552 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1554 /* If we have no user directory, use generic temporary directory */
1556 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1557 if (option_debug > 2)
1558 ast_log(LOG_DEBUG, "Creating temporary directory %s\n", tmpdir);
1562 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1565 /* XXX This file needs to be in temp directory */
1566 txtdes = mkstemp(tmptxtfile);
1568 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1569 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1571 res = ast_waitstream(chan, "");
1572 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1577 /* Unless we're *really* silent, try to send the beep */
1578 res = ast_streamfile(chan, "beep", chan->language);
1580 res = ast_waitstream(chan, "");
1583 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1584 /* Store information */
1585 if (option_debug > 1)
1586 ast_log(LOG_DEBUG, "Open file for metadata: %s\n", tmptxtfile);
1588 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1590 txt = fdopen(txtdes, "w+");
1592 ast_log(LOG_WARNING, "Error opening text file for output\n");
1597 char logbuf[BUFSIZ];
1598 get_date(date, sizeof(date));
1600 ast_localtime(&now, &tm, NULL);
1601 strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1603 snprintf(logbuf, sizeof(logbuf),
1604 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1605 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1612 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1616 duration < global_vmminmessage ? "IGNORED" : "OK",
1619 fprintf(txt, logbuf);
1620 if (minivmlogfile) {
1621 ast_mutex_lock(&minivmloglock);
1622 fprintf(minivmlogfile, logbuf);
1623 ast_mutex_unlock(&minivmloglock);
1626 if (duration < global_vmminmessage) {
1627 if (option_verbose > 2)
1628 ast_verbose( VERBOSE_PREFIX_3 "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1630 ast_filedelete(tmptxtfile, NULL);
1632 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1635 fclose(txt); /* Close log file */
1636 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1638 ast_log(LOG_DEBUG, "The recorded media file is gone, so we should remove the .txt file too!\n");
1640 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1641 if(ast_test_flag(vmu, MVM_ALLOCED))
1646 /* Set channel variables for the notify application */
1647 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1648 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1649 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1650 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1653 global_stats.lastreceived = time(NULL);
1654 global_stats.receivedmessages++;
1655 // /* Go ahead and delete audio files from system, they're not needed any more */
1656 // if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1657 // ast_filedelete(tmptxtfile, NULL);
1658 // if (option_debug > 1)
1659 // ast_log(LOG_DEBUG, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1665 if(ast_test_flag(vmu, MVM_ALLOCED))
1668 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1672 /*! \brief Notify voicemail account owners - either generic template or user specific */
1673 static int minivm_notify_exec(struct ast_channel *chan, void *data)
1675 struct ast_module_user *u;
1682 struct minivm_account *vmu;
1683 char *username = argv[0];
1684 const char *template = "";
1685 const char *filename;
1687 const char *duration_string;
1689 u = ast_module_user_add(chan);
1692 if (ast_strlen_zero(data)) {
1693 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1694 ast_module_user_remove(u);
1697 tmpptr = ast_strdupa((char *)data);
1699 ast_log(LOG_ERROR, "Out of memory\n");
1700 ast_module_user_remove(u);
1703 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1705 if (argc == 2 && !ast_strlen_zero(argv[1]))
1708 ast_copy_string(tmp, argv[0], sizeof(tmp));
1710 domain = strchr(tmp, '@');
1715 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1716 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
1717 ast_module_user_remove(u);
1721 if(!(vmu = find_account(domain, username, TRUE))) {
1722 /* We could not find user, let's exit */
1723 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
1724 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
1725 ast_module_user_remove(u);
1729 filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME");
1730 format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT");
1731 duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION");
1732 /* Notify of new message to e-mail and pager */
1733 if (!ast_strlen_zero(filename)) {
1734 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
1737 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
1740 if(ast_test_flag(vmu, MVM_ALLOCED))
1743 /* Ok, we're ready to rock and roll. Return to dialplan */
1744 ast_module_user_remove(u);
1750 /*! \brief Dialplan function to record voicemail */
1751 static int minivm_record_exec(struct ast_channel *chan, void *data)
1754 struct ast_module_user *u;
1756 struct leave_vm_options leave_options;
1759 struct ast_flags flags = { 0 };
1760 char *opts[OPT_ARG_ARRAY_SIZE];
1762 u = ast_module_user_add(chan);
1764 memset(&leave_options, 0, sizeof(leave_options));
1766 /* Answer channel if it's not already answered */
1767 if (chan->_state != AST_STATE_UP)
1770 if (ast_strlen_zero(data)) {
1771 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1772 ast_module_user_remove(u);
1775 tmp = ast_strdupa((char *)data);
1777 ast_log(LOG_ERROR, "Out of memory\n");
1778 ast_module_user_remove(u);
1781 argc = ast_app_separate_args(tmp, '|', argv, sizeof(argv) / sizeof(argv[0]));
1783 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1784 ast_module_user_remove(u);
1787 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1788 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
1791 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
1792 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
1793 ast_module_user_remove(u);
1796 leave_options.record_gain = (signed char) gain;
1800 /* Now run the appliation and good luck to you! */
1801 res = leave_voicemail(chan, argv[0], &leave_options);
1803 if (res == ERROR_LOCK_PATH) {
1804 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
1805 /* Send the call to n+101 priority, where n is the current priority*/
1806 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1809 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1812 ast_module_user_remove(u);
1817 /*! \brief Play voicemail prompts - either generic or user specific */
1818 static int minivm_greet_exec(struct ast_channel *chan, void *data)
1820 struct ast_module_user *u;
1821 struct leave_vm_options leave_options = { 0, '\0'};
1824 struct ast_flags flags = { 0 };
1825 char *opts[OPT_ARG_ARRAY_SIZE];
1831 char dest[PATH_MAX];
1832 char prefile[PATH_MAX];
1833 char tempfile[PATH_MAX] = "";
1834 char ext_context[256] = "";
1836 char ecodes[16] = "#";
1838 struct minivm_account *vmu;
1839 char *username = argv[0];
1841 u = ast_module_user_add(chan);
1843 if (ast_strlen_zero(data)) {
1844 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1845 ast_module_user_remove(u);
1848 tmpptr = ast_strdupa((char *)data);
1850 ast_log(LOG_ERROR, "Out of memory\n");
1851 ast_module_user_remove(u);
1854 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
1857 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1858 ast_module_user_remove(u);
1861 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1864 ast_copy_string(tmp, argv[0], sizeof(tmp));
1866 domain = strchr(tmp, '@');
1871 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1872 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
1873 ast_module_user_remove(u);
1877 ast_log(LOG_DEBUG, "-_-_- Trying to find configuration for user %s in domain %s\n", username, domain);
1879 if (!(vmu = find_account(domain, username, TRUE))) {
1880 ast_log(LOG_ERROR, "Could not allocate memory. \n");
1881 ast_module_user_remove(u);
1885 /* Answer channel if it's not already answered */
1886 if (chan->_state != AST_STATE_UP)
1889 /* Setup pre-file if appropriate */
1890 if (strcmp(vmu->domain, "localhost"))
1891 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1893 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1895 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
1896 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
1898 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
1899 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
1900 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
1902 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
1904 /* Check for temporary greeting - it overrides busy and unavail */
1905 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
1906 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
1907 if (option_debug > 1)
1908 ast_log(LOG_DEBUG, "Temporary message directory does not exist, using default (%s)\n", tempfile);
1909 ast_copy_string(prefile, tempfile, sizeof(prefile));
1911 if (option_debug > 1)
1912 ast_log(LOG_DEBUG, "-_-_- Preparing to play message ...\n");
1914 /* Check current or macro-calling context for special extensions */
1915 if (ast_test_flag(vmu, MVM_OPERATOR)) {
1916 if (!ast_strlen_zero(vmu->exit)) {
1917 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
1918 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1921 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
1922 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1925 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
1926 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
1931 if (!ast_strlen_zero(vmu->exit)) {
1932 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
1933 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1934 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
1935 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1936 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
1937 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
1941 res = 0; /* Reset */
1942 /* Play the beginning intro if desired */
1943 if (!ast_strlen_zero(prefile)) {
1944 if (ast_streamfile(chan, prefile, chan->language) > -1)
1945 res = ast_waitstream(chan, ecodes);
1947 if (option_debug > 1)
1948 ast_log(LOG_DEBUG, "%s doesn't exist, doing what we can\n", prefile);
1949 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
1952 if (option_debug > 1)
1953 ast_log(LOG_DEBUG, "Hang up during prefile playback\n");
1954 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
1955 if(ast_test_flag(vmu, MVM_ALLOCED))
1957 ast_module_user_remove(u);
1961 /* On a '#' we skip the instructions */
1962 ast_set_flag(&leave_options, OPT_SILENT);
1965 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
1966 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
1968 res = ast_waitstream(chan, ecodes);
1970 ast_set_flag(&leave_options, OPT_SILENT);
1975 ast_stopstream(chan);
1976 /* Check for a '*' here in case the caller wants to escape from voicemail to something
1977 other than the operator -- an automated attendant or mailbox login for example */
1979 chan->exten[0] = 'a';
1980 chan->exten[1] = '\0';
1981 if (!ast_strlen_zero(vmu->exit)) {
1982 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
1983 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
1984 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
1987 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
1989 } else if (res == '0') { /* Check for a '0' here */
1990 if(ouseexten || ousemacro) {
1991 chan->exten[0] = 'o';
1992 chan->exten[1] = '\0';
1993 if (!ast_strlen_zero(vmu->exit)) {
1994 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
1995 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
1996 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
1998 ast_play_and_wait(chan, "transfer");
2000 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
2003 } else if (res < 0) {
2004 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
2007 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "SUCCESS");
2009 if(ast_test_flag(vmu, MVM_ALLOCED))
2013 /* Ok, we're ready to rock and roll. Return to dialplan */
2014 ast_module_user_remove(u);
2020 /*! \brief Dialplan application to delete voicemail */
2021 static int minivm_delete_exec(struct ast_channel *chan, void *data)
2024 struct ast_module_user *u;
2025 char filename[BUFSIZ];
2027 u = ast_module_user_add(chan);
2029 if (!ast_strlen_zero(data))
2030 ast_copy_string(filename, (char *) data, sizeof(filename));
2032 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2034 if (ast_strlen_zero(filename)) {
2035 ast_module_user_remove(u);
2036 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2040 /* Go ahead and delete audio files from system, they're not needed any more */
2041 /* We should look for both audio and text files here */
2042 if (ast_fileexists(filename, NULL, NULL) > 0) {
2043 res = vm_delete(filename);
2045 if (option_debug > 1)
2046 ast_log(LOG_DEBUG, "-_-_- Can't delete file: %s\n", filename);
2047 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2049 if (option_debug > 1)
2050 ast_log(LOG_DEBUG, "-_-_- Deleted voicemail file :: %s \n", filename);
2051 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "SUCCESS");
2054 if (option_debug > 1)
2055 ast_log(LOG_DEBUG, "-_-_- Filename does not exist: %s\n", filename);
2056 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2059 ast_module_user_remove(u);
2064 /*! \brief Record specific messages for voicemail account */
2065 static int minivm_accmess_exec(struct ast_channel *chan, void *data)
2067 struct ast_module_user *u;
2071 char filename[PATH_MAX];
2075 struct minivm_account *vmu;
2076 char *username = argv[0];
2077 struct ast_flags flags = { 0 };
2078 char *opts[OPT_ARG_ARRAY_SIZE];
2080 char *message = NULL;
2081 char *prompt = NULL;
2085 u = ast_module_user_add(chan);
2087 if (ast_strlen_zero(data)) {
2088 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2091 tmpptr = ast_strdupa((char *)data);
2094 ast_log(LOG_ERROR, "Out of memory\n");
2097 argc = ast_app_separate_args(tmpptr, '|', argv, sizeof(argv) / sizeof(argv[0]));
2101 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2104 if (!error && strlen(argv[1]) > 1) {
2105 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2109 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2110 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2115 ast_module_user_remove(u);
2119 ast_copy_string(tmp, argv[0], sizeof(tmp));
2121 domain = strchr(tmp, '@');
2126 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2127 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2128 ast_module_user_remove(u);
2132 if(!(vmu = find_account(domain, username, TRUE))) {
2133 /* We could not find user, let's exit */
2134 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2135 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
2136 ast_module_user_remove(u);
2140 /* Answer channel if it's not already answered */
2141 if (chan->_state != AST_STATE_UP)
2144 /* Here's where the action is */
2145 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2147 prompt = "vm-rec-busy";
2148 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2149 message = "unavailable";
2150 prompt = "vm-rec-unavail";
2151 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2153 prompt = "vm-temp-greeting";
2154 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2156 prompt = "vm-rec-name";
2158 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2159 /* Maybe we should check the result of play_record_review ? */
2160 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2163 ast_log(LOG_DEBUG, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2165 if(ast_test_flag(vmu, MVM_ALLOCED))
2169 /* Ok, we're ready to rock and roll. Return to dialplan */
2170 ast_module_user_remove(u);
2176 /*! \brief Append new mailbox to mailbox list from configuration file */
2177 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2179 struct minivm_account *vmu;
2182 char accbuf[BUFSIZ];
2184 if (option_debug > 2)
2185 ast_log(LOG_DEBUG, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2187 ast_copy_string(accbuf, name, sizeof(accbuf));
2189 domain = strchr(accbuf, '@');
2194 if (ast_strlen_zero(domain)) {
2195 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2199 if (option_debug > 2)
2200 ast_log(LOG_DEBUG, "Creating static account for user %s domain %s\n", username, domain);
2202 /* Allocate user account */
2203 vmu = ast_calloc(1, sizeof(struct minivm_account));
2207 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2208 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2210 populate_defaults(vmu);
2212 if (option_debug > 2)
2213 ast_log(LOG_DEBUG, "...Configuring account %s\n", name);
2216 if (option_debug > 2)
2217 ast_log(LOG_DEBUG, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2218 if (!strcasecmp(var->name, "serveremail")) {
2219 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2220 } else if (!strcasecmp(var->name, "email")) {
2221 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2222 } else if (!strcasecmp(var->name, "accountcode")) {
2223 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2224 } else if (!strcasecmp(var->name, "pincode")) {
2225 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2226 } else if (!strcasecmp(var->name, "domain")) {
2227 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2228 } else if (!strcasecmp(var->name, "language")) {
2229 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2230 } else if (!strcasecmp(var->name, "timezone")) {
2231 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2232 } else if (!strcasecmp(var->name, "externnotify")) {
2233 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2234 } else if (!strcasecmp(var->name, "etemplate")) {
2235 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2236 } else if (!strcasecmp(var->name, "ptemplate")) {
2237 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2238 } else if (!strcasecmp(var->name, "fullname")) {
2239 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2240 } else if (!strcasecmp(var->name, "setvar")) {
2242 char *varname = ast_strdupa(var->value);
2243 struct ast_variable *tmpvar;
2245 if (varname && (varval = strchr(varname, '='))) {
2248 if ((tmpvar = ast_variable_new(varname, varval))) {
2249 tmpvar->next = vmu->chanvars;
2250 vmu->chanvars = tmpvar;
2253 } else if (!strcasecmp(var->name, "pager")) {
2254 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2255 } else if (!strcasecmp(var->name, "volgain")) {
2256 sscanf(var->value, "%lf", &vmu->volgain);
2258 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2262 if (option_debug > 2)
2263 ast_log(LOG_DEBUG, "...Linking account %s\n", name);
2265 AST_LIST_LOCK(&minivm_accounts);
2266 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2267 AST_LIST_UNLOCK(&minivm_accounts);
2269 global_stats.voicemailaccounts++;
2271 if (option_debug > 1)
2272 ast_log(LOG_DEBUG, "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)" : "");
2276 /*! \brief Free Mini Voicemail timezone */
2277 static void free_zone(struct minivm_zone *z)
2282 /*! \brief Clear list of timezones */
2283 static void timezone_destroy_list(void)
2285 struct minivm_zone *this;
2286 AST_LIST_LOCK(&minivm_zones);
2287 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2290 AST_LIST_UNLOCK(&minivm_zones);
2293 /*! \brief Add time zone to memory list */
2294 static int timezone_add(char *zonename, char *config)
2297 struct minivm_zone *newzone;
2298 char *msg_format, *timezone;
2300 newzone = malloc(sizeof(struct minivm_zone));
2301 if (newzone == NULL)
2304 msg_format = ast_strdupa(config);
2305 if (msg_format == NULL) {
2306 ast_log(LOG_WARNING, "Out of memory.\n");
2311 timezone = strsep(&msg_format, "|");
2313 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2318 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2319 ast_copy_string(newzone->timezone, timezone, sizeof(newzone->timezone));
2320 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2322 AST_LIST_LOCK(&minivm_zones);
2323 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2324 AST_LIST_UNLOCK(&minivm_zones);
2326 global_stats.timezones++;
2331 /*! \brief Read message template from file */
2332 static char *message_template_parse_filebody(char *filename) {
2333 char buf[BUFSIZ * 6];
2334 char readbuf[BUFSIZ];
2335 char filenamebuf[BUFSIZ];
2341 if (ast_strlen_zero(filename))
2343 if (*filename == '/')
2344 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2346 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2348 if (!(fi = fopen(filenamebuf, "r"))) {
2349 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2353 while (fgets(readbuf, sizeof(readbuf), fi)) {
2355 if (writepos != buf) {
2356 *writepos = '\n'; /* Replace EOL with new line */
2359 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2360 writepos += strlen(readbuf) - 1;
2363 messagebody = ast_calloc(1, strlen(buf + 1));
2364 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2365 if (option_debug > 3) {
2366 ast_log(LOG_DEBUG, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2367 ast_log(LOG_DEBUG, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2373 /*! \brief Parse emailbody template from configuration file */
2374 static char *message_template_parse_emailbody(const char *configuration)
2376 char *tmpread, *tmpwrite;
2377 char *emailbody = strdup(configuration);
2379 /* substitute strings \t and \n into the apropriate characters */
2380 tmpread = tmpwrite = emailbody;
2381 while ((tmpwrite = strchr(tmpread,'\\'))) {
2382 int len = strlen("\n");
2383 switch (tmpwrite[1]) {
2385 strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2386 strncpy(tmpwrite, "\n", len);
2389 strncpy(tmpwrite+len, tmpwrite+2, strlen(tmpwrite+2)+1);
2390 strncpy(tmpwrite, "\t", len);
2393 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2395 tmpread = tmpwrite + len;
2400 /*! \brief Apply general configuration options */
2401 static int apply_general_options(struct ast_variable *var)
2407 if (!strcmp(var->name, "mailcmd")) {
2408 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2409 } else if (!strcmp(var->name, "maxgreet")) {
2410 global_maxgreet = atoi(var->value);
2411 } else if (!strcmp(var->name, "maxsilence")) {
2412 global_maxsilence = atoi(var->value);
2413 if (global_maxsilence > 0)
2414 global_maxsilence *= 1000;
2415 } else if (!strcmp(var->name, "logfile")) {
2416 if (!ast_strlen_zero(var->value) ) {
2417 if(*(var->value) == '/')
2418 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2420 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2422 } else if (!strcmp(var->name, "externnotify")) {
2423 /* External voicemail notify application */
2424 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2425 } else if (!strcmp(var->name, "silencetreshold")) {
2426 /* Silence treshold */
2427 global_silencethreshold = atoi(var->value);
2428 } else if (!strcmp(var->name, "maxmessage")) {
2430 if (sscanf(var->value, "%d", &x) == 1) {
2431 global_vmmaxmessage = x;
2434 ast_log(LOG_WARNING, "Invalid max message time length\n");
2436 } else if (!strcmp(var->name, "minmessage")) {
2438 if (sscanf(var->value, "%d", &x) == 1) {
2439 global_vmminmessage = x;
2440 if (global_maxsilence <= global_vmminmessage)
2441 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2444 ast_log(LOG_WARNING, "Invalid min message time length\n");
2446 } else if (!strcmp(var->name, "format")) {
2447 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2448 } else if (!strcmp(var->name, "review")) {
2449 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2450 } else if (!strcmp(var->name, "operator")) {
2451 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2458 /*! \brief Load minivoicemail configuration */
2459 static int load_config(void)
2461 struct minivm_account *cur;
2462 struct minivm_zone *zcur;
2463 struct minivm_template *tcur;
2464 struct ast_config *cfg;
2465 struct ast_variable *var;
2467 const char *chanvar;
2470 cfg = ast_config_load(VOICEMAIL_CONFIG);
2471 ast_mutex_lock(&minivmlock);
2473 AST_LIST_LOCK(&minivm_accounts);
2474 while ((cur = AST_LIST_REMOVE_HEAD(&minivm_accounts, list))) {
2477 AST_LIST_UNLOCK(&minivm_accounts);
2479 /* Free all zones */
2480 AST_LIST_LOCK(&minivm_zones);
2481 while ((zcur = AST_LIST_REMOVE_HEAD(&minivm_zones, list))) {
2484 AST_LIST_UNLOCK(&minivm_zones);
2486 /* Free all templates */
2487 AST_LIST_LOCK(&message_templates);
2488 while ((tcur = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
2489 message_template_free(tcur);
2491 AST_LIST_UNLOCK(&message_templates);
2493 /* First, set some default settings */
2494 global_externnotify[0] = '\0';
2495 global_logfile[0] = '\0';
2496 global_silencethreshold = 256;
2497 global_vmmaxmessage = 2000;
2498 global_maxgreet = 2000;
2499 global_vmminmessage = 0;
2500 strcpy(global_mailcmd, SENDMAIL);
2501 global_maxsilence = 0;
2502 global_saydurationminfo = 2;
2503 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2504 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2505 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2506 strcpy(global_charset, "ISO-8859-1");
2507 struct minivm_template *template;
2508 /* Reset statistics */
2509 memset(&global_stats, 0, sizeof(struct minivm_stats));
2510 global_stats.reset = time(NULL);
2512 /* Make sure we could load configuration file */
2514 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2518 if (option_debug > 1)
2519 ast_log(LOG_DEBUG, "-_-_- Loaded configuration file, now parsing\n");
2521 /* General settings */
2523 cat = ast_category_browse(cfg, NULL);
2525 if (option_debug > 2)
2526 ast_log(LOG_DEBUG, "-_-_- Found configuration section [%s]\n", cat);
2527 if (!strcasecmp(cat, "general")) {
2528 /* Nothing right now */
2529 error += apply_general_options(ast_variable_browse(cfg, cat));
2530 } else if (!strncasecmp(cat, "template-", 9)) {
2532 char *name = cat + 9;
2534 /* Now build and link template to list */
2535 error += message_template_build(name, ast_variable_browse(cfg, cat));
2537 var = ast_variable_browse(cfg, cat);
2538 if (!strcasecmp(cat, "zonemessages")) {
2539 /* Timezones in this context */
2541 timezone_add(var->name, var->value);
2545 /* Create mailbox from this */
2546 error += create_vmaccount(cat, var, FALSE);
2549 /* Find next section in configuration file */
2550 cat = ast_category_browse(cfg, cat);
2553 /* Configure the default email template */
2554 message_template_build("email-default", NULL);
2555 template = message_template_find("email-default");
2557 /* Load date format config for voicemail mail */
2558 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2559 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2560 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2561 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2562 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2563 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2564 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2565 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2566 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2567 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2568 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2569 template->body = message_template_parse_emailbody(chanvar);
2570 template->attachment = TRUE;
2572 message_template_build("pager-default", NULL);
2573 template = message_template_find("pager-default");
2574 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2575 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2576 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2577 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2578 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2579 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2580 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2581 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2582 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2583 template->body = message_template_parse_emailbody(chanvar);
2584 template->attachment = FALSE;
2587 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2589 ast_mutex_unlock(&minivmlock);
2590 ast_config_destroy(cfg);
2592 /* Close log file if it's open and disabled */
2594 fclose(minivmlogfile);
2596 /* Open log file if it's enabled */
2597 if(!ast_strlen_zero(global_logfile)) {
2598 minivmlogfile = fopen(global_logfile, "a");
2600 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2601 if (option_debug > 2 && minivmlogfile)
2602 ast_log(LOG_DEBUG, "-_-_- Opened log file %s \n", global_logfile);
2608 static const char minivm_show_users_help[] =
2609 "Usage: minivm list accounts\n"
2610 " Lists all mailboxes currently set up\n";
2612 static const char minivm_show_zones_help[] =
2613 "Usage: minivm list zones\n"
2614 " Lists zone message formats\n";
2616 static const char minivm_list_templates_help[] =
2617 "Usage: minivm list templates\n"
2618 " Lists message templates for e-mail, paging and IM\n";
2620 static const char minivm_show_stats_help[] =
2621 "Usage: minivm show stats\n"
2622 " Display Mini-Voicemail counters\n";
2624 static const char minivm_show_settings_help[] =
2625 "Usage: minivm show settings\n"
2626 " Display Mini-Voicemail general settings\n";
2628 static const char minivm_reload_help[] =
2629 "Usage: minivm reload\n"
2630 " Reload mini-voicemail configuration and reset statistics\n";
2632 /*! \brief CLI routine for listing templates */
2633 static int handle_minivm_list_templates(int fd, int argc, char *argv[])
2635 struct minivm_template *this;
2636 char *output_format = "%-15s %-10s %-10s %-15.15s %-50s\n";
2640 return RESULT_SHOWUSAGE;
2642 AST_LIST_LOCK(&message_templates);
2643 if (AST_LIST_EMPTY(&message_templates)) {
2644 ast_cli(fd, "There are no message templates defined\n");
2645 AST_LIST_UNLOCK(&message_templates);
2646 return RESULT_FAILURE;
2648 ast_cli(fd, output_format, "Template name", "Charset", "Locale", "Attach media", "Subject");
2649 ast_cli(fd, output_format, "-------------", "-------", "------", "------------", "-------");
2650 AST_LIST_TRAVERSE(&message_templates, this, list) {
2651 ast_cli(fd, output_format, this->name,
2652 this->charset ? this->charset : "-",
2653 this->locale ? this->locale : "-",
2654 this->attachment ? "Yes" : "No",
2655 this->subject ? this->subject : "-");
2658 AST_LIST_UNLOCK(&message_templates);
2659 ast_cli(fd, "\n * Total: %d minivoicemail message templates\n", count);
2660 return RESULT_SUCCESS;
2663 /*! \brief CLI command to list voicemail accounts */
2664 static int handle_minivm_show_users(int fd, int argc, char *argv[])
2666 struct minivm_account *vmu;
2667 char *output_format = "%-23s %-15s %-15s %-10s %-10s %-50s\n";
2670 if ((argc < 3) || (argc > 5) || (argc == 4))
2671 return RESULT_SHOWUSAGE;
2672 if ((argc == 5) && strcmp(argv[3],"for"))
2673 return RESULT_SHOWUSAGE;
2675 AST_LIST_LOCK(&minivm_accounts);
2676 if (AST_LIST_EMPTY(&minivm_accounts)) {
2677 ast_cli(fd, "There are no voicemail users currently defined\n");
2678 AST_LIST_UNLOCK(&minivm_accounts);
2679 return RESULT_FAILURE;
2681 ast_cli(fd, output_format, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2682 ast_cli(fd, output_format, "----", "----------", "----------", "----", "------", "---------");
2683 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2687 if ((argc == 3) || ((argc == 5) && !strcmp(argv[4], vmu->domain))) {
2689 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2690 ast_cli(fd, output_format, tmp, vmu->etemplate ? vmu->etemplate : "-",
2691 vmu->ptemplate ? vmu->ptemplate : "-",
2692 vmu->zonetag ? vmu->zonetag : "-",
2693 vmu->attachfmt ? vmu->attachfmt : "-",
2697 AST_LIST_UNLOCK(&minivm_accounts);
2698 ast_cli(fd, "\n * Total: %d minivoicemail accounts\n", count);
2699 return RESULT_SUCCESS;
2702 /*! \brief Show a list of voicemail zones in the CLI */
2703 static int handle_minivm_show_zones(int fd, int argc, char *argv[])
2705 struct minivm_zone *zone;
2706 char *output_format = "%-15s %-20s %-45s\n";
2707 int res = RESULT_SUCCESS;
2710 return RESULT_SHOWUSAGE;
2712 AST_LIST_LOCK(&minivm_zones);
2713 if (!AST_LIST_EMPTY(&minivm_zones)) {
2714 ast_cli(fd, output_format, "Zone", "Timezone", "Message Format");
2715 ast_cli(fd, output_format, "----", "--------", "--------------");
2716 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2717 ast_cli(fd, output_format, zone->name, zone->timezone, zone->msg_format);
2720 ast_cli(fd, "There are no voicemail zones currently defined\n");
2721 res = RESULT_FAILURE;
2723 AST_LIST_UNLOCK(&minivm_zones);
2729 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2733 struct minivm_account *vmu;
2734 const char *domain = "";
2736 /* 0 - show; 1 - voicemail; 2 - users; 3 - for; 4 - <domain> */
2740 return (state == 0) ? strdup("for") : NULL;
2741 wordlen = strlen(word);
2742 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2743 if (!strncasecmp(word, vmu->domain, wordlen)) {
2744 if (domain && strcmp(domain, vmu->domain) && ++which > state)
2745 return strdup(vmu->domain);
2746 /* ignore repeated domains ? */
2747 domain = vmu->domain;
2753 /*! \brief CLI Show settings */
2754 static int handle_minivm_show_settings(int fd, int argc, char *argv[])
2756 ast_cli(fd, "* Mini-Voicemail general settings\n");
2757 ast_cli(fd, " -------------------------------\n");
2759 ast_cli(fd, " Mail command (shell): %s\n", global_mailcmd);
2760 ast_cli(fd, " Max silence: %d\n", global_maxsilence);
2761 ast_cli(fd, " Silence treshold: %d\n", global_silencethreshold);
2762 ast_cli(fd, " Max message length (secs): %d\n", global_vmmaxmessage);
2763 ast_cli(fd, " Min message length (secs): %d\n", global_vmminmessage);
2764 ast_cli(fd, " Default format: %s\n", default_vmformat);
2765 ast_cli(fd, " Extern notify (shell): %s\n", global_externnotify);
2766 ast_cli(fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2767 ast_cli(fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2768 ast_cli(fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2771 return RESULT_SUCCESS;
2774 /*! \brief Show stats */
2775 static int handle_minivm_show_stats(int fd, int argc, char *argv[])
2780 ast_cli(fd, "* Mini-Voicemail statistics\n");
2781 ast_cli(fd, " -------------------------\n");
2783 ast_cli(fd, " Voicemail accounts: %-5.5d\n", global_stats.voicemailaccounts);
2784 ast_cli(fd, " Templates: %-5.5d\n", global_stats.templates);
2785 ast_cli(fd, " Timezones: %-5.5d\n", global_stats.timezones);
2786 if (global_stats.receivedmessages == 0) {
2787 ast_cli(fd, " Received messages since last reset: <none>\n");
2789 ast_cli(fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
2790 ast_localtime(&global_stats.lastreceived, &time, NULL);
2791 strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2792 ast_cli(fd, " Last received voicemail: %s\n", buf);
2794 ast_localtime(&global_stats.reset, &time, NULL);
2795 strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &time);
2796 ast_cli(fd, " Last reset: %s\n", buf);
2799 return RESULT_SUCCESS;
2802 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
2803 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2805 struct minivm_account *vmu;
2806 char *username, *domain, *colname;
2808 if (!(username = ast_strdupa(data))) {
2809 ast_log(LOG_ERROR, "Memory Error!\n");
2813 if ((colname = strchr(username, ':'))) {
2819 if ((domain = strchr(username, '@'))) {
2823 if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2824 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2828 if (!(vmu = find_account(domain, username, TRUE)))
2831 if (!strcasecmp(colname, "hasaccount")) {
2832 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2833 } else if (!strcasecmp(colname, "fullname")) {
2834 ast_copy_string(buf, vmu->fullname, len);
2835 } else if (!strcasecmp(colname, "email")) {
2836 if (!ast_strlen_zero(vmu->email))
2837 ast_copy_string(buf, vmu->email, len);
2839 snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2840 } else if (!strcasecmp(colname, "pager")) {
2841 ast_copy_string(buf, vmu->pager, len);
2842 } else if (!strcasecmp(colname, "etemplate")) {
2843 if (!ast_strlen_zero(vmu->etemplate))
2844 ast_copy_string(buf, vmu->etemplate, len);
2846 ast_copy_string(buf, "email-default", len);
2847 } else if (!strcasecmp(colname, "language")) {
2848 ast_copy_string(buf, vmu->language, len);
2849 } else if (!strcasecmp(colname, "timezone")) {
2850 ast_copy_string(buf, vmu->zonetag, len);
2851 } else if (!strcasecmp(colname, "ptemplate")) {
2852 if (!ast_strlen_zero(vmu->ptemplate))
2853 ast_copy_string(buf, vmu->ptemplate, len);
2855 ast_copy_string(buf, "email-default", len);
2856 } else if (!strcasecmp(colname, "accountcode")) {
2857 ast_copy_string(buf, vmu->accountcode, len);
2858 } else if (!strcasecmp(colname, "pincode")) {
2859 ast_copy_string(buf, vmu->pincode, len);
2860 } else if (!strcasecmp(colname, "path")) {
2861 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2862 } else { /* Look in channel variables */
2863 struct ast_variable *var;
2866 for (var = vmu->chanvars ; var ; var = var->next)
2867 if (!strcmp(var->name, colname)) {
2868 ast_copy_string(buf, var->value, len);
2874 if(ast_test_flag(vmu, MVM_ALLOCED))
2880 /*! \brief lock directory
2882 only return failure if ast_lock_path returns 'timeout',
2883 not if the path does not exist or any other reason
2885 static int vm_lock_path(const char *path)
2887 switch (ast_lock_path(path)) {
2888 case AST_LOCK_TIMEOUT:
2895 /*! \brief Access counter file, lock directory, read and possibly write it again changed
2896 \param directory Directory to crate file in
2897 \param value If set to zero, we only read the variable
2898 \param operand 0 to read, 1 to set new value, 2 to change
2899 \return -1 on error, otherwise counter value
2901 static int access_counter_file(char *directory, char *countername, int value, int operand)
2903 char filename[BUFSIZ];
2904 char readbuf[BUFSIZ];
2906 int old = 0, counter = 0;
2908 /* Lock directory */
2909 if (vm_lock_path(directory)) {
2910 return -1; /* Could not lock directory */
2912 snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
2914 counterfile = fopen(filename, "r");
2916 if(fgets(readbuf, sizeof(readbuf), counterfile)) {
2917 if (option_debug > 2)
2918 ast_log(LOG_DEBUG, "Read this string from counter file: %s\n", readbuf);
2919 old = counter = atoi(readbuf);
2921 fclose(counterfile);
2925 case 0: /* Read only */
2926 ast_unlock_path(directory);
2927 if (option_debug > 1)
2928 ast_log(LOG_DEBUG, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
2931 case 1: /* Set new value */
2934 case 2: /* Change value */
2936 if (counter < 0) /* Don't allow counters to fall below zero */
2941 /* Now, write the new value to the file */
2942 counterfile = fopen(filename, "w");
2944 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
2945 ast_unlock_path(directory);
2946 return -1; /* Could not open file for writing */
2948 fprintf(counterfile, "%d\n\n", counter);
2949 fclose(counterfile);
2950 ast_unlock_path(directory);
2951 if (option_debug > 1)
2952 ast_log(LOG_DEBUG, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
2956 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
2957 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2959 char *username, *domain, *countername;
2960 struct minivm_account *vmu = NULL;
2961 char userpath[BUFSIZ];
2966 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
2967 ast_log(LOG_WARNING, "Memory error!\n");
2970 if ((countername = strchr(username, ':'))) {
2971 *countername = '\0';
2975 if ((domain = strchr(username, '@'))) {
2980 /* If we have neither username nor domain now, let's give up */
2981 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2982 ast_log(LOG_ERROR, "No account given\n");
2986 if (ast_strlen_zero(countername)) {
2987 ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
2991 /* We only have a domain, no username */
2992 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
2997 /* If we can't find account or if the account is temporary, return. */
2998 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
2999 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3003 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3005 /* We have the path, now read the counter file */
3006 res = access_counter_file(userpath, countername, 0, 0);
3008 snprintf(buf, len, "%d", res);
3012 /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
3013 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
3015 char *username, *domain, *countername, *operand;
3016 char userpath[BUFSIZ];
3017 struct minivm_account *vmu;
3023 change = atoi(value);
3025 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
3026 ast_log(LOG_WARNING, "Memory error!\n");
3030 if ((countername = strchr(username, ':'))) {
3031 *countername = '\0';
3034 if ((operand = strchr(countername, ':'))) {
3039 if ((domain = strchr(username, '@'))) {
3044 /* If we have neither username nor domain now, let's give up */
3045 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3046 ast_log(LOG_ERROR, "No account given\n");
3050 /* We only have a domain, no username */
3051 if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3056 if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
3057 ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
3061 /* If we can't find account or if the account is temporary, return. */
3062 if (!ast_strlen_zero(username) && !(vmu = find_account(domain, username, FALSE))) {
3063 ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3067 create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3068 /* Now, find out our operator */
3069 if (*operand == 'i') /* Increment */
3071 else if (*operand == 'd') {
3072 change = change * -1;
3074 } else if (*operand == 's')
3077 ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3081 /* We have the path, now read the counter file */
3082 access_counter_file(userpath, countername, change, operation);
3087 /*! \brief CLI commands for Mini-voicemail */
3088 static struct ast_cli_entry cli_minivm[] = {
3089 { { "minivm", "list", "accounts", NULL },
3090 handle_minivm_show_users, "List defined mini-voicemail boxes",
3091 minivm_show_users_help, complete_minivm_show_users, NULL },
3093 { { "minivm", "list", "zones", NULL },
3094 handle_minivm_show_zones, "List zone message formats",
3095 minivm_show_zones_help, NULL, NULL },
3097 { { "minivm", "list", "templates", NULL },
3098 handle_minivm_list_templates, "List message templates",
3099 minivm_list_templates_help, NULL, NULL },
3101 { { "minivm", "reload", NULL, NULL },
3102 handle_minivm_reload, "Reload Mini-voicemail configuration",
3103 minivm_reload_help, NULL, NULL },
3105 { { "minivm", "show", "stats", NULL },
3106 handle_minivm_show_stats, "Show some mini-voicemail statistics",
3107 minivm_show_stats_help, NULL, NULL },
3109 { { "minivm", "show", "settings", NULL },
3110 handle_minivm_show_settings, "Show mini-voicemail general settings",
3111 minivm_show_settings_help, NULL, NULL },
3114 static struct ast_custom_function minivm_counter_function = {
3115 .name = "MINIVMCOUNTER",
3116 .synopsis = "Reads or sets counters for MiniVoicemail message",
3117 .syntax = "MINIVMCOUNTER(<account>:name[:operand])",
3118 .read = minivm_counter_func_read,
3119 .write = minivm_counter_func_write,
3120 .desc = "Valid operands for changing the value of a counter when assigning a value are:\n"
3121 "- i Increment by value\n"
3122 "- d Decrement by value\n"
3123 "- s Set to value\n"
3124 "\nThe counters never goes below zero.\n"
3125 "- The name of the counter is a string, up to 10 characters\n"
3126 "- If account is given and it exists, the counter is specific for the account\n"
3127 "- If account is a domain and the domain directory exists, counters are specific for a domain\n"
3128 "The operation is atomic and the counter is locked while changing the value\n"
3129 "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n"
3130 "realtime functions if you are using a database to operate your Asterisk\n",
3133 static struct ast_custom_function minivm_account_function = {
3134 .name = "MINIVMACCOUNT",
3135 .synopsis = "Gets MiniVoicemail account information",
3136 .syntax = "MINIVMACCOUNT(<account>:item)",
3137 .read = minivm_account_func_read,
3138 .desc = "Valid items are:\n"
3139 "- path Path to account mailbox (if account exists, otherwise temporary mailbox)\n"
3140 "- hasaccount 1 if static Minivm account