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
144 #include "asterisk.h"
146 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
149 #include <sys/time.h>
150 #include <sys/stat.h>
151 #include <sys/mman.h>
157 #include "asterisk/paths.h" /* use various paths */
158 #include "asterisk/lock.h"
159 #include "asterisk/file.h"
160 #include "asterisk/channel.h"
161 #include "asterisk/pbx.h"
162 #include "asterisk/config.h"
163 #include "asterisk/say.h"
164 #include "asterisk/module.h"
165 #include "asterisk/app.h"
166 #include "asterisk/manager.h"
167 #include "asterisk/dsp.h"
168 #include "asterisk/localtime.h"
169 #include "asterisk/cli.h"
170 #include "asterisk/utils.h"
171 #include "asterisk/linkedlists.h"
172 #include "asterisk/callerid.h"
173 #include "asterisk/event.h"
176 <application name="MinivmRecord" language="en_US">
178 Receive Mini-Voicemail and forward via e-mail.
181 <parameter name="mailbox" required="true" argsep="@">
182 <argument name="username" required="true">
183 <para>Voicemail username</para>
185 <argument name="domain" required="true">
186 <para>Voicemail domain</para>
189 <parameter name="options" required="false">
192 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
195 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
198 <argument name="gain">
199 <para>Amount of gain to use</para>
201 <para>Use the specified amount of gain when recording the voicemail message.
202 The units are whole-number decibels (dB).</para>
208 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
209 <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
210 <para>If there's no user account for that address, a temporary account will be used with default options.</para>
211 <para>The recorded file name and path will be stored in <variable>MINIVM_FILENAME</variable> and the duration
212 of the message will be stored in <variable>MINIVM_DURATION</variable></para>
213 <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
214 execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
215 are received and the requested extension exist in the current context.</para></note>
217 <variable name="MINIVM_RECORD_STATUS">
218 <para>This is the status of the record operation</para>
219 <value name="SUCCESS" />
220 <value name="USEREXIT" />
221 <value name="FAILED" />
226 <application name="MinivmGreet" language="en_US">
228 Play Mini-Voicemail prompts.
231 <parameter name="mailbox" required="true" argsep="@">
232 <argument name="username" required="true">
233 <para>Voicemail username</para>
235 <argument name="domain" required="true">
236 <para>Voicemail domain</para>
239 <parameter name="options" required="false">
242 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
245 <para>Skip the playback of instructions for leaving a message to the calling party.</para>
248 <para>Play the <literal>unavailable</literal> greeting.</para>
254 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
255 <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
256 <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
257 message exists for the account.</para>
259 <variable name="MINIVM_GREET_STATUS">
260 <para>This is the status of the greeting playback.</para>
261 <value name="SUCCESS" />
262 <value name="USEREXIT" />
263 <value name="FAILED" />
268 <application name="MinivmNotify" language="en_US">
270 Notify voicemail owner about new messages.
273 <parameter name="mailbox" required="true" argsep="@">
274 <argument name="username" required="true">
275 <para>Voicemail username</para>
277 <argument name="domain" required="true">
278 <para>Voicemail domain</para>
281 <parameter name="options" required="false">
283 <option name="template">
284 <para>E-mail template to use for voicemail notification</para>
290 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
291 <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
292 account for that address, a temporary account will be used with default options (set in
293 <filename>minivm.conf</filename>).</para>
294 <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
295 file name and available in the template for the message.</para>
296 <para>If no template is given, the default email template will be used to send email and default pager
297 template to send paging message (if the user account is configured with a paging address.</para>
299 <variable name="MINIVM_NOTIFY_STATUS">
300 <para>This is the status of the notification attempt</para>
301 <value name="SUCCESS" />
302 <value name="FAILED" />
307 <application name="MinivmDelete" language="en_US">
309 Delete Mini-Voicemail voicemail messages.
312 <parameter name="filename" required="true">
313 <para>File to delete</para>
317 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
318 <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
320 <variable name="MINIVM_DELETE_STATUS">
321 <para>This is the status of the delete operation.</para>
322 <value name="SUCCESS" />
323 <value name="FAILED" />
329 <application name="MinivmAccMess" language="en_US">
331 Record account specific messages.
334 <parameter name="mailbox" required="true" argsep="@">
335 <argument name="username" required="true">
336 <para>Voicemail username</para>
338 <argument name="domain" required="true">
339 <para>Voicemail domain</para>
342 <parameter name="options" required="false">
345 <para>Record the <literal>unavailable</literal> greeting.</para>
348 <para>Record the <literal>busy</literal> greeting.</para>
351 <para>Record the temporary greeting.</para>
354 <para>Account name.</para>
360 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
361 <para>Use this application to record account specific audio/video messages for busy, unavailable
362 and temporary messages.</para>
363 <para>Account specific directories will be created if they do not exist.</para>
365 <variable name="MINIVM_ACCMESS_STATUS">
366 <para>This is the result of the attempt to record the specified greeting.</para>
367 <para><literal>FAILED</literal> is set if the file can't be created.</para>
368 <value name="SUCCESS" />
369 <value name="FAILED" />
374 <application name="MinivmMWI" language="en_US">
376 Send Message Waiting Notification to subscriber(s) of mailbox.
379 <parameter name="mailbox" required="true" argsep="@">
380 <argument name="username" required="true">
381 <para>Voicemail username</para>
383 <argument name="domain" required="true">
384 <para>Voicemail domain</para>
387 <parameter name="urgent" required="true">
388 <para>Number of urgent messages in mailbox.</para>
390 <parameter name="new" required="true">
391 <para>Number of new messages in mailbox.</para>
393 <parameter name="old" required="true">
394 <para>Number of old messages in mailbox.</para>
398 <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
399 <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
400 subscribed to the mailbox passed in the first parameter.</para>
413 #define MVM_REVIEW (1 << 0) /*!< Review message */
414 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
415 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
416 #define MVM_SVMAIL (1 << 3)
417 #define MVM_ENVELOPE (1 << 4)
418 #define MVM_PBXSKIP (1 << 9)
419 #define MVM_ALLOCED (1 << 13)
421 /*! \brief Default mail command to mail voicemail. Change it with the
422 mailcmd= command in voicemail.conf */
423 #define SENDMAIL "/usr/sbin/sendmail -t"
425 #define SOUND_INTRO "vm-intro"
426 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
427 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
430 #define MAX_DATETIME_FORMAT 512
431 #define MAX_NUM_CID_CONTEXTS 10
433 #define ERROR_LOCK_PATH -100
434 #define VOICEMAIL_DIR_MODE 0700
436 #define VOICEMAIL_CONFIG "minivm.conf"
437 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
439 /*! \brief Message types for notification */
440 enum mvm_messagetype {
443 /* For trunk: MVM_MESSAGE_JABBER, */
446 static char MVM_SPOOL_DIR[PATH_MAX];
448 /* Module declarations */
449 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
450 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
451 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
452 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
453 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
454 static char *app_minivm_mwi = "MinivmMWI";
459 OPT_SILENT = (1 << 0),
460 OPT_BUSY_GREETING = (1 << 1),
461 OPT_UNAVAIL_GREETING = (1 << 2),
462 OPT_TEMP_GREETING = (1 << 3),
463 OPT_NAME_GREETING = (1 << 4),
464 OPT_RECORDGAIN = (1 << 5),
465 } minivm_option_flags;
468 OPT_ARG_RECORDGAIN = 0,
469 OPT_ARG_ARRAY_SIZE = 1,
470 } minivm_option_args;
472 AST_APP_OPTIONS(minivm_app_options, {
473 AST_APP_OPTION('s', OPT_SILENT),
474 AST_APP_OPTION('b', OPT_BUSY_GREETING),
475 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
476 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
479 AST_APP_OPTIONS(minivm_accmess_options, {
480 AST_APP_OPTION('b', OPT_BUSY_GREETING),
481 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
482 AST_APP_OPTION('t', OPT_TEMP_GREETING),
483 AST_APP_OPTION('n', OPT_NAME_GREETING),
486 /*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
487 struct minivm_account {
488 char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
489 char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
491 char pincode[10]; /*!< Secret pin code, numbers only */
492 char fullname[120]; /*!< Full name, for directory app */
493 char email[80]; /*!< E-mail address - override */
494 char pager[80]; /*!< E-mail address to pager (no attachment) */
495 char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
496 char serveremail[80]; /*!< From: Mail address */
497 char externnotify[160]; /*!< Configurable notification command */
498 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
499 char zonetag[80]; /*!< Time zone */
500 char uniqueid[20]; /*!< Unique integer identifier */
501 char exit[80]; /*!< Options for exiting from voicemail() */
502 char attachfmt[80]; /*!< Format for voicemail audio file attachment */
503 char etemplate[80]; /*!< Pager template */
504 char ptemplate[80]; /*!< Voicemail format */
505 unsigned int flags; /*!< MVM_ flags */
506 struct ast_variable *chanvars; /*!< Variables for e-mail template */
507 double volgain; /*!< Volume gain for voicemails sent via e-mail */
508 AST_LIST_ENTRY(minivm_account) list;
511 /*! \brief The list of e-mail accounts */
512 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
514 /*! \brief Linked list of e-mail templates in various languages
515 These are used as templates for e-mails, pager messages and jabber messages
516 \ref message_templates
518 struct minivm_template {
519 char name[80]; /*!< Template name */
520 char *body; /*!< Body of this template */
521 char fromaddress[100]; /*!< Who's sending the e-mail? */
522 char serveremail[80]; /*!< From: Mail address */
523 char subject[100]; /*!< Subject line */
524 char charset[32]; /*!< Default character set for this template */
525 char locale[20]; /*!< Locale for setlocale() */
526 char dateformat[80]; /*!< Date format to use in this attachment */
527 int attachment; /*!< Attachment of media yes/no - no for pager messages */
528 AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
531 /*! \brief The list of e-mail templates */
532 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
534 /*! \brief Options for leaving voicemail with the voicemail() application */
535 struct leave_vm_options {
537 signed char record_gain;
540 /*! \brief Structure for base64 encoding */
546 unsigned char iobuf[B64_BASEMAXINLINE];
549 /*! \brief Voicemail time zones */
551 char name[80]; /*!< Name of this time zone */
552 char timezone[80]; /*!< Timezone definition */
553 char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
554 AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
557 /*! \brief The list of e-mail time zones */
558 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
560 /*! \brief Structure for gathering statistics */
561 struct minivm_stats {
562 int voicemailaccounts; /*!< Number of static accounts */
563 int timezones; /*!< Number of time zones */
564 int templates; /*!< Number of templates */
566 struct timeval reset; /*!< Time for last reset */
567 int receivedmessages; /*!< Number of received messages since reset */
568 struct timeval lastreceived; /*!< Time for last voicemail sent */
571 /*! \brief Statistics for voicemail */
572 static struct minivm_stats global_stats;
574 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
575 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
577 FILE *minivmlogfile; /*!< The minivm log file */
579 static int global_vmminmessage; /*!< Minimum duration of messages */
580 static int global_vmmaxmessage; /*!< Maximum duration of message */
581 static int global_maxsilence; /*!< Maximum silence during recording */
582 static int global_maxgreet; /*!< Maximum length of prompts */
583 static int global_silencethreshold = 128;
584 static char global_mailcmd[160]; /*!< Configurable mail cmd */
585 static char global_externnotify[160]; /*!< External notification application */
586 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
587 static char default_vmformat[80];
589 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
590 static int global_saydurationminfo;
591 static char global_charset[32]; /*!< Global charset in messages */
593 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
595 /*! \brief Default dateformat, can be overridden in configuration file */
596 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
597 #define DEFAULT_CHARSET "ISO-8859-1"
599 /* Forward declarations */
600 static char *message_template_parse_filebody(const char *filename);
601 static char *message_template_parse_emailbody(const char *body);
602 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
603 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
604 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
606 /*! \brief Create message template */
607 static struct minivm_template *message_template_create(const char *name)
609 struct minivm_template *template;
611 template = ast_calloc(1, sizeof(*template));
615 /* Set some defaults for templates */
616 ast_copy_string(template->name, name, sizeof(template->name));
617 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
618 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
619 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
620 template->attachment = TRUE;
625 /*! \brief Release memory allocated by message template */
626 static void message_template_free(struct minivm_template *template)
629 ast_free(template->body);
634 /*! \brief Build message template from configuration */
635 static int message_template_build(const char *name, struct ast_variable *var)
637 struct minivm_template *template;
640 template = message_template_create(name);
642 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
647 ast_debug(3, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
648 if (!strcasecmp(var->name, "fromaddress")) {
649 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
650 } else if (!strcasecmp(var->name, "fromemail")) {
651 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
652 } else if (!strcasecmp(var->name, "subject")) {
653 ast_copy_string(template->subject, var->value, sizeof(template->subject));
654 } else if (!strcasecmp(var->name, "locale")) {
655 ast_copy_string(template->locale, var->value, sizeof(template->locale));
656 } else if (!strcasecmp(var->name, "attachmedia")) {
657 template->attachment = ast_true(var->value);
658 } else if (!strcasecmp(var->name, "dateformat")) {
659 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
660 } else if (!strcasecmp(var->name, "charset")) {
661 ast_copy_string(template->charset, var->value, sizeof(template->charset));
662 } else if (!strcasecmp(var->name, "templatefile")) {
664 ast_free(template->body);
665 template->body = message_template_parse_filebody(var->value);
666 if (!template->body) {
667 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
670 } else if (!strcasecmp(var->name, "messagebody")) {
672 ast_free(template->body);
673 template->body = message_template_parse_emailbody(var->value);
674 if (!template->body) {
675 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
679 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
685 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
687 AST_LIST_LOCK(&message_templates);
688 AST_LIST_INSERT_TAIL(&message_templates, template, list);
689 AST_LIST_UNLOCK(&message_templates);
691 global_stats.templates++;
696 /*! \brief Find named template */
697 static struct minivm_template *message_template_find(const char *name)
699 struct minivm_template *this, *res = NULL;
701 if (ast_strlen_zero(name))
704 AST_LIST_LOCK(&message_templates);
705 AST_LIST_TRAVERSE(&message_templates, this, list) {
706 if (!strcasecmp(this->name, name)) {
711 AST_LIST_UNLOCK(&message_templates);
717 /*! \brief Clear list of templates */
718 static void message_destroy_list(void)
720 struct minivm_template *this;
721 AST_LIST_LOCK(&message_templates);
722 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list)))
723 message_template_free(this);
725 AST_LIST_UNLOCK(&message_templates);
728 /*! \brief read buffer from file (base64 conversion) */
729 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
736 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
750 /*! \brief read character from file to buffer (base64 conversion) */
751 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
753 if (bio->iocp >= bio->iolen) {
754 if (!b64_inbuf(bio, fi))
758 return bio->iobuf[bio->iocp++];
761 /*! \brief write buffer to file (base64 conversion) */
762 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
764 if (bio->linelength >= B64_BASELINELEN) {
765 if (fputs(EOL,so) == EOF)
771 if (putc(((unsigned char) c), so) == EOF)
779 /*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
780 static int base_encode(char *filename, FILE *so)
782 unsigned char dtable[B64_BASEMAXINLINE];
785 struct b64_baseio bio;
787 memset(&bio, 0, sizeof(bio));
788 bio.iocp = B64_BASEMAXINLINE;
790 if (!(fi = fopen(filename, "rb"))) {
791 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
795 for (i= 0; i<9; i++) {
799 dtable[26+i+9]= 'j'+i;
801 for (i= 0; i < 8; i++) {
803 dtable[26+i+18]= 's'+i;
805 for (i= 0; i < 10; i++) {
812 unsigned char igroup[3], ogroup[4];
815 igroup[0]= igroup[1]= igroup[2]= 0;
817 for (n= 0; n < 3; n++) {
818 if ((c = b64_inchar(&bio, fi)) == EOF) {
822 igroup[n]= (unsigned char)c;
826 ogroup[0]= dtable[igroup[0]>>2];
827 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
828 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
829 ogroup[3]= dtable[igroup[2]&0x3F];
839 b64_ochar(&bio, ogroup[i], so);
843 /* Put end of line - line feed */
844 if (fputs(EOL, so) == EOF)
852 static int get_date(char *s, int len)
855 struct timeval now = ast_tvnow();
857 ast_localtime(&now, &tm, NULL);
858 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
862 /*! \brief Free user structure - if it's allocated */
863 static void free_user(struct minivm_account *vmu)
866 ast_variables_destroy(vmu->chanvars);
872 /*! \brief Prepare for voicemail template by adding channel variables
875 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)
878 struct ast_variable *var;
881 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
885 for (var = vmu->chanvars ; var ; var = var->next) {
886 pbx_builtin_setvar_helper(channel, var->name, var->value);
889 /* Prepare variables for substition in email body and subject */
890 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
891 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
892 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
893 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
894 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
895 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
896 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
897 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
898 if (!ast_strlen_zero(counter))
899 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
902 /*! \brief Set default values for Mini-Voicemail users */
903 static void populate_defaults(struct minivm_account *vmu)
905 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
906 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
907 vmu->volgain = global_volgain;
910 /*! \brief Fix quote of mail headers for non-ascii characters */
911 static char *mailheader_quote(const char *from, char *to, size_t len)
915 for (; ptr < to + len - 1; from++) {
918 else if (*from == '\0')
922 if (ptr < to + len - 1)
929 /*! \brief Allocate new vm user and set default values */
930 static struct minivm_account *mvm_user_alloc(void)
932 struct minivm_account *new;
934 new = ast_calloc(1, sizeof(*new));
937 populate_defaults(new);
943 /*! \brief Clear list of users */
944 static void vmaccounts_destroy_list(void)
946 struct minivm_account *this;
947 AST_LIST_LOCK(&minivm_accounts);
948 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
950 AST_LIST_UNLOCK(&minivm_accounts);
954 /*! \brief Find user from static memory object list */
955 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
957 struct minivm_account *vmu = NULL, *cur;
960 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
961 ast_log(LOG_NOTICE, "No username or domain? \n");
964 ast_debug(3, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain);
966 AST_LIST_LOCK(&minivm_accounts);
967 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
968 /* Is this the voicemail account we're looking for? */
969 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
972 AST_LIST_UNLOCK(&minivm_accounts);
975 ast_debug(3, "-_-_- Found account for %s@%s\n", username, domain);
979 vmu = find_user_realtime(domain, username);
981 if (createtemp && !vmu) {
982 /* Create a temporary user, send e-mail and be gone */
983 vmu = mvm_user_alloc();
984 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
986 ast_copy_string(vmu->username, username, sizeof(vmu->username));
987 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
988 ast_debug(1, "--- Created temporary account\n");
995 /*! \brief Find user in realtime storage
996 Returns pointer to minivm_account structure
998 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
1000 struct ast_variable *var;
1001 struct minivm_account *retval;
1002 char name[MAXHOSTNAMELEN];
1004 retval = mvm_user_alloc();
1009 ast_copy_string(retval->username, username, sizeof(retval->username));
1011 populate_defaults(retval);
1012 var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
1019 snprintf(name, sizeof(name), "%s@%s", username, domain);
1020 create_vmaccount(name, var, TRUE);
1022 ast_variables_destroy(var);
1026 /*! \brief Send voicemail with audio file as an attachment */
1027 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)
1031 char email[256] = "";
1035 char fname[PATH_MAX];
1037 char tmp[80] = "/tmp/astmail-XXXXXX";
1038 char tmp2[PATH_MAX];
1041 struct minivm_zone *the_zone = NULL;
1043 struct ast_channel *ast;
1044 char *finalfilename;
1045 char *passdata = NULL;
1046 char *passdata2 = NULL;
1050 if (type == MVM_MESSAGE_EMAIL) {
1051 if (vmu && !ast_strlen_zero(vmu->email)) {
1052 ast_copy_string(email, vmu->email, sizeof(email));
1053 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
1054 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1055 } else if (type == MVM_MESSAGE_PAGE) {
1056 ast_copy_string(email, vmu->pager, sizeof(email));
1059 if (ast_strlen_zero(email)) {
1060 ast_log(LOG_WARNING, "No address to send message to.\n");
1064 ast_debug(3, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1066 if (!strcmp(format, "wav49"))
1070 /* If we have a gain option, process it now with sox */
1071 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1072 char newtmp[PATH_MAX];
1073 char tmpcmd[PATH_MAX];
1076 ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
1077 ast_debug(3, "newtmp: %s\n", newtmp);
1078 tmpfd = mkstemp(newtmp);
1079 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
1080 ast_safe_system(tmpcmd);
1081 finalfilename = newtmp;
1082 ast_debug(3, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1084 finalfilename = ast_strdupa(filename);
1087 /* Create file name */
1088 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
1090 if (template->attachment)
1091 ast_debug(1, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
1093 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1097 p = fdopen(pfd, "w");
1102 ast_debug(1, "-_-_- Opening temp file for e-mail: %s\n", tmp);
1105 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1108 /* Allocate channel used for chanvar substitution */
1109 ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0);
1112 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1114 /* Does this user have a timezone specified? */
1115 if (!ast_strlen_zero(vmu->zonetag)) {
1116 /* Find the zone in the list */
1117 struct minivm_zone *z;
1118 AST_LIST_LOCK(&minivm_zones);
1119 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1120 if (strcmp(z->name, vmu->zonetag))
1124 AST_LIST_UNLOCK(&minivm_zones);
1128 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1129 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1131 /* Start printing the email to the temporary file */
1132 fprintf(p, "Date: %s\n", date);
1134 /* Set date format for voicemail mail */
1135 ast_strftime(date, sizeof(date), template->dateformat, &tm);
1138 /* Populate channel with channel variables for substitution */
1139 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1141 /* Find email address to use */
1142 /* If there's a server e-mail adress in the account, user that, othterwise template */
1143 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1145 /* Find name to user for server e-mail */
1146 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1148 /* If needed, add hostname as domain */
1149 if (ast_strlen_zero(fromemail))
1150 fromemail = "asterisk";
1152 if (strchr(fromemail, '@'))
1153 ast_copy_string(who, fromemail, sizeof(who));
1155 char host[MAXHOSTNAMELEN];
1156 gethostname(host, sizeof(host)-1);
1157 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1160 if (ast_strlen_zero(fromaddress)) {
1161 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1163 /* Allocate a buffer big enough for variable substitution */
1164 int vmlen = strlen(fromaddress) * 3 + 200;
1166 ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress);
1167 if ((passdata = alloca(vmlen))) {
1168 pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen);
1169 len_passdata = strlen(passdata) * 2 + 3;
1170 passdata2 = alloca(len_passdata);
1171 fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who);
1173 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1178 ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
1180 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, (int)getpid(), who);
1181 len_passdata = strlen(vmu->fullname) * 2 + 3;
1182 passdata2 = alloca(len_passdata);
1183 if (!ast_strlen_zero(vmu->email))
1184 fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1186 fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain);
1188 if (!ast_strlen_zero(template->subject)) {
1190 int vmlen = strlen(template->subject) * 3 + 200;
1191 if ((pass_data = alloca(vmlen))) {
1192 pbx_substitute_variables_helper(ast, template->subject, pass_data, vmlen);
1193 fprintf(p, "Subject: %s\n", pass_data);
1195 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1200 ast_debug(4, "-_-_- Subject now: %s\n", pass_data);
1203 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1204 ast_debug(1, "-_-_- Using default subject for this email \n");
1208 if (option_debug > 2)
1209 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1210 fprintf(p, "MIME-Version: 1.0\n");
1212 /* Something unique. */
1213 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)rand());
1215 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1217 fprintf(p, "--%s\n", bound);
1218 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset);
1219 if (!ast_strlen_zero(template->body)) {
1221 int vmlen = strlen(template->body)*3 + 200;
1222 if ((pass_data = alloca(vmlen))) {
1223 pbx_substitute_variables_helper(ast, template->body, pass_data, vmlen);
1224 ast_debug(3, "Message now: %s\n-----\n", pass_data);
1225 fprintf(p, "%s\n", pass_data);
1227 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1229 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1231 "in mailbox %s from %s, on %s so you might\n"
1232 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1233 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1234 ast_debug(3, "Using default message body (no template)\n-----\n");
1236 /* Eww. We want formats to tell us their own MIME type */
1237 if (template->attachment) {
1238 char *ctype = "audio/x-";
1239 ast_debug(3, "-_-_- Attaching file to message: %s\n", fname);
1240 if (!strcasecmp(format, "ogg"))
1241 ctype = "application/";
1243 fprintf(p, "--%s\n", bound);
1244 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1245 fprintf(p, "Content-Transfer-Encoding: base64\n");
1246 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1247 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1249 base_encode(fname, p);
1250 fprintf(p, "\n\n--%s--\n.\n", bound);
1253 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1254 ast_safe_system(tmp2);
1255 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1256 ast_debug(3, "-_-_- Actual command used: %s\n", tmp2);
1258 ast_channel_free(ast);
1262 /*! \brief Create directory based on components */
1263 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1265 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1268 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1269 * \param dest String. base directory.
1270 * \param len Int. Length base directory string.
1271 * \param domain String. Ignored if is null or empty string.
1272 * \param username String. Ignored if is null or empty string.
1273 * \param folder String. Ignored if is null or empty string.
1274 * \return 0 on failure, 1 on success.
1276 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1278 struct stat filestat;
1279 make_dir(dest, len, domain, username, folder ? folder : "");
1280 if (stat(dest, &filestat)== -1)
1286 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1287 * \param dest String. base directory.
1288 * \param len Length of directory string
1289 * \param domain String. Ignored if is null or empty string.
1290 * \param folder String. Ignored if is null or empty string.
1291 * \param username String. Ignored if is null or empty string.
1292 * \return -1 on failure, 0 on success.
1294 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1297 make_dir(dest, len, domain, username, folder);
1298 if ((res = ast_mkdir(dest, 0777))) {
1299 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1302 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1307 /*! \brief Play intro message before recording voicemail
1309 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1314 ast_debug(2, "-_-_- Still preparing to play message ...\n");
1316 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1318 if (ast_fileexists(fn, NULL, NULL) > 0) {
1319 res = ast_streamfile(chan, fn, chan->language);
1322 res = ast_waitstream(chan, ecodes);
1326 int numericusername = 1;
1329 ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n");
1332 ast_debug(2, "-_-_- Numeric? Checking %c\n", *i);
1334 numericusername = FALSE;
1340 if (numericusername) {
1341 if(ast_streamfile(chan, "vm-theperson", chan->language))
1343 if ((res = ast_waitstream(chan, ecodes)))
1346 res = ast_say_digit_str(chan, username, ecodes, chan->language);
1350 if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1352 if ((res = ast_waitstream(chan, ecodes)))
1357 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1360 res = ast_waitstream(chan, ecodes);
1364 /*! \brief Delete media files and attribute file */
1365 static int vm_delete(char *file)
1369 ast_debug(1, "-_-_- Deleting voicemail file %s\n", file);
1371 res = unlink(file); /* Remove the meta data file */
1372 res |= ast_filedelete(file, NULL); /* remove the media file */
1377 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1378 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1379 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1380 signed char record_gain)
1383 int max_attempts = 3;
1386 int message_exists = 0;
1387 signed char zero_gain = 0;
1388 char *acceptdtmf = "#";
1389 char *canceldtmf = "";
1391 /* Note that urgent and private are for flagging messages as such in the future */
1393 /* barf if no pointer passed to store duration in */
1394 if (duration == NULL) {
1395 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1399 cmd = '3'; /* Want to start by recording */
1401 while ((cmd >= 0) && (cmd != 't')) {
1405 ast_verb(3, "Reviewing the message\n");
1406 ast_streamfile(chan, recordfile, chan->language);
1407 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1413 ast_verb(3, "Re-recording the message\n");
1415 ast_verb(3, "Recording the message\n");
1416 if (recorded && outsidecaller)
1417 cmd = ast_play_and_wait(chan, "beep");
1419 /* 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 */
1421 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1422 if (ast_test_flag(vmu, MVM_OPERATOR))
1424 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1426 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1427 if (cmd == -1) /* User has hung up, no options to give */
1431 else if (cmd == '*')
1434 /* If all is well, a message exists */
1447 cmd = ast_play_and_wait(chan, "vm-sorry");
1450 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1451 cmd = ast_play_and_wait(chan, "vm-sorry");
1454 if (message_exists || recorded) {
1455 cmd = ast_play_and_wait(chan, "vm-saveoper");
1457 cmd = ast_waitfordigit(chan, 3000);
1459 ast_play_and_wait(chan, "vm-msgsaved");
1462 ast_play_and_wait(chan, "vm-deleted");
1463 vm_delete(recordfile);
1469 /* If the caller is an ouside caller, and the review option is enabled,
1470 allow them to review the message, but let the owner of the box review
1472 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1474 if (message_exists) {
1475 cmd = ast_play_and_wait(chan, "vm-review");
1477 cmd = ast_play_and_wait(chan, "vm-torerecord");
1479 cmd = ast_waitfordigit(chan, 600);
1482 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1483 cmd = ast_play_and_wait(chan, "vm-reachoper");
1485 cmd = ast_waitfordigit(chan, 600);
1488 cmd = ast_waitfordigit(chan, 6000);
1492 if (attempts > max_attempts) {
1498 ast_play_and_wait(chan, "vm-goodbye");
1504 /*! \brief Run external notification for voicemail message */
1505 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1507 char arguments[BUFSIZ];
1509 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1512 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1513 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1514 vmu->username, vmu->domain,
1515 chan->cid.cid_name, chan->cid.cid_num);
1517 ast_debug(1, "Executing: %s\n", arguments);
1518 ast_safe_system(arguments);
1521 /*! \brief Send message to voicemail account owner */
1522 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)
1525 struct minivm_template *etemplate;
1526 char *messageformat;
1528 char oldlocale[100];
1529 const char *counter;
1531 if (!ast_strlen_zero(vmu->attachfmt)) {
1532 if (strstr(format, vmu->attachfmt)) {
1533 format = vmu->attachfmt;
1535 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);
1538 etemplate = message_template_find(vmu->etemplate);
1540 etemplate = message_template_find(templatename);
1542 etemplate = message_template_find("email-default");
1544 /* Attach only the first format */
1545 stringp = messageformat = ast_strdupa(format);
1546 strsep(&stringp, "|");
1548 if (!ast_strlen_zero(etemplate->locale)) {
1550 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1551 ast_debug(2, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1552 new_locale = setlocale(LC_TIME, etemplate->locale);
1553 if (new_locale == NULL) {
1554 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1560 /* Read counter if available */
1561 ast_channel_lock(chan);
1562 if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1563 counter = ast_strdupa(counter);
1565 ast_channel_unlock(chan);
1567 if (ast_strlen_zero(counter)) {
1568 ast_debug(2, "-_-_- MVM_COUNTER not found\n");
1570 ast_debug(2, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter);
1573 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1575 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1576 /* Find template for paging */
1577 etemplate = message_template_find(vmu->ptemplate);
1579 etemplate = message_template_find("pager-default");
1580 if (etemplate->locale) {
1581 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1582 setlocale(LC_TIME, etemplate->locale);
1585 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1588 manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1590 run_externnotify(chan, vmu); /* Run external notification */
1592 if (etemplate->locale)
1593 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1598 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1599 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1601 char tmptxtfile[PATH_MAX];
1604 int res = 0, txtdes;
1608 char tmpdir[PATH_MAX];
1609 char ext_context[256] = "";
1613 struct minivm_account *vmu;
1616 ast_copy_string(tmp, username, sizeof(tmp));
1618 domain = strchr(tmp, '@');
1624 if (!(vmu = find_account(domain, username, TRUE))) {
1625 /* We could not find user, let's exit */
1626 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1627 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1631 /* Setup pre-file if appropriate */
1632 if (strcmp(vmu->domain, "localhost"))
1633 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1635 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1637 /* The meat of recording the message... All the announcements and beeps have been played*/
1638 if (ast_strlen_zero(vmu->attachfmt))
1639 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1641 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1643 if (ast_strlen_zero(fmt)) {
1644 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1645 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1650 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1652 /* If we have no user directory, use generic temporary directory */
1654 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1655 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1659 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1662 /* XXX This file needs to be in temp directory */
1663 txtdes = mkstemp(tmptxtfile);
1665 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1666 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1668 res = ast_waitstream(chan, "");
1669 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1674 /* Unless we're *really* silent, try to send the beep */
1675 res = ast_streamfile(chan, "beep", chan->language);
1677 res = ast_waitstream(chan, "");
1680 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1681 /* Store information */
1682 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1684 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1686 txt = fdopen(txtdes, "w+");
1688 ast_log(LOG_WARNING, "Error opening text file for output\n");
1691 struct timeval now = ast_tvnow();
1693 char logbuf[BUFSIZ];
1694 get_date(date, sizeof(date));
1695 ast_localtime(&now, &tm, NULL);
1696 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1698 snprintf(logbuf, sizeof(logbuf),
1699 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1700 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1707 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1711 duration < global_vmminmessage ? "IGNORED" : "OK",
1714 fprintf(txt, "%s", logbuf);
1715 if (minivmlogfile) {
1716 ast_mutex_lock(&minivmloglock);
1717 fprintf(minivmlogfile, "%s", logbuf);
1718 ast_mutex_unlock(&minivmloglock);
1721 if (duration < global_vmminmessage) {
1722 ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1724 ast_filedelete(tmptxtfile, NULL);
1726 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1729 fclose(txt); /* Close log file */
1730 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1731 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1733 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1734 if(ast_test_flag(vmu, MVM_ALLOCED))
1739 /* Set channel variables for the notify application */
1740 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1741 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1742 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1743 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1746 global_stats.lastreceived = ast_tvnow();
1747 global_stats.receivedmessages++;
1748 // /* Go ahead and delete audio files from system, they're not needed any more */
1749 // if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1750 // ast_filedelete(tmptxtfile, NULL);
1751 // /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1752 // ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1758 if(ast_test_flag(vmu, MVM_ALLOCED))
1761 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1765 /*! \brief Queue a message waiting event */
1766 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
1768 struct ast_event *event;
1769 char *mailbox, *context;
1771 mailbox = ast_strdupa(mbx);
1772 context = ast_strdupa(ctx);
1773 if (ast_strlen_zero(context)) {
1774 context = "default";
1777 if (!(event = ast_event_new(AST_EVENT_MWI,
1778 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
1779 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
1780 AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
1781 AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
1782 AST_EVENT_IE_END))) {
1786 ast_event_queue_and_cache(event,
1787 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR,
1788 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR,
1792 /*! \brief Send MWI using interal Asterisk event subsystem */
1793 static int minivm_mwi_exec(struct ast_channel *chan, void *data)
1802 if (ast_strlen_zero(data)) {
1803 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1806 tmpptr = ast_strdupa((char *)data);
1808 ast_log(LOG_ERROR, "Out of memory\n");
1811 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1813 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
1816 ast_copy_string(tmp, argv[0], sizeof(tmp));
1818 domain = strchr(tmp, '@');
1823 if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
1824 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
1827 queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
1833 /*! \brief Notify voicemail account owners - either generic template or user specific */
1834 static int minivm_notify_exec(struct ast_channel *chan, void *data)
1842 struct minivm_account *vmu;
1843 char *username = argv[0];
1844 const char *template = "";
1845 const char *filename;
1847 const char *duration_string;
1849 if (ast_strlen_zero(data)) {
1850 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1853 tmpptr = ast_strdupa((char *)data);
1855 ast_log(LOG_ERROR, "Out of memory\n");
1858 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1860 if (argc == 2 && !ast_strlen_zero(argv[1]))
1863 ast_copy_string(tmp, argv[0], sizeof(tmp));
1865 domain = strchr(tmp, '@');
1870 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1871 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
1875 if(!(vmu = find_account(domain, username, TRUE))) {
1876 /* We could not find user, let's exit */
1877 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
1878 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
1882 ast_channel_lock(chan);
1883 if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
1884 filename = ast_strdupa(filename);
1886 ast_channel_unlock(chan);
1887 /* Notify of new message to e-mail and pager */
1888 if (!ast_strlen_zero(filename)) {
1889 ast_channel_lock(chan);
1890 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
1891 format = ast_strdupa(format);
1893 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
1894 duration_string = ast_strdupa(duration_string);
1896 ast_channel_unlock(chan);
1897 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
1900 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
1903 if(ast_test_flag(vmu, MVM_ALLOCED))
1906 /* Ok, we're ready to rock and roll. Return to dialplan */
1912 /*! \brief Dialplan function to record voicemail */
1913 static int minivm_record_exec(struct ast_channel *chan, void *data)
1917 struct leave_vm_options leave_options;
1920 struct ast_flags flags = { 0 };
1921 char *opts[OPT_ARG_ARRAY_SIZE];
1923 memset(&leave_options, 0, sizeof(leave_options));
1925 /* Answer channel if it's not already answered */
1926 if (chan->_state != AST_STATE_UP)
1929 if (ast_strlen_zero(data)) {
1930 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1933 tmp = ast_strdupa((char *)data);
1935 ast_log(LOG_ERROR, "Out of memory\n");
1938 argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
1940 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1943 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1944 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
1947 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
1948 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
1951 leave_options.record_gain = (signed char) gain;
1955 /* Now run the appliation and good luck to you! */
1956 res = leave_voicemail(chan, argv[0], &leave_options);
1958 if (res == ERROR_LOCK_PATH) {
1959 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
1960 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1963 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1968 /*! \brief Play voicemail prompts - either generic or user specific */
1969 static int minivm_greet_exec(struct ast_channel *chan, void *data)
1971 struct leave_vm_options leave_options = { 0, '\0'};
1974 struct ast_flags flags = { 0 };
1975 char *opts[OPT_ARG_ARRAY_SIZE];
1981 char dest[PATH_MAX];
1982 char prefile[PATH_MAX];
1983 char tempfile[PATH_MAX] = "";
1984 char ext_context[256] = "";
1986 char ecodes[16] = "#";
1988 struct minivm_account *vmu;
1989 char *username = argv[0];
1991 if (ast_strlen_zero(data)) {
1992 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1995 tmpptr = ast_strdupa((char *)data);
1997 ast_log(LOG_ERROR, "Out of memory\n");
2000 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2003 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2005 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2008 ast_copy_string(tmp, argv[0], sizeof(tmp));
2010 domain = strchr(tmp, '@');
2015 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2016 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
2019 ast_debug(1, "-_-_- Trying to find configuration for user %s in domain %s\n", username, domain);
2021 if (!(vmu = find_account(domain, username, TRUE))) {
2022 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2026 /* Answer channel if it's not already answered */
2027 if (chan->_state != AST_STATE_UP)
2030 /* Setup pre-file if appropriate */
2031 if (strcmp(vmu->domain, "localhost"))
2032 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2034 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2036 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2037 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2039 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2040 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2041 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2043 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2045 /* Check for temporary greeting - it overrides busy and unavail */
2046 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2047 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2048 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2049 ast_copy_string(prefile, tempfile, sizeof(prefile));
2051 ast_debug(2, "-_-_- Preparing to play message ...\n");
2053 /* Check current or macro-calling context for special extensions */
2054 if (ast_test_flag(vmu, MVM_OPERATOR)) {
2055 if (!ast_strlen_zero(vmu->exit)) {
2056 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
2057 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2060 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
2061 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2064 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2065 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2070 if (!ast_strlen_zero(vmu->exit)) {
2071 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2072 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2073 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2074 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2075 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2076 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2080 res = 0; /* Reset */
2081 /* Play the beginning intro if desired */
2082 if (!ast_strlen_zero(prefile)) {
2083 if (ast_streamfile(chan, prefile, chan->language) > -1)
2084 res = ast_waitstream(chan, ecodes);
2086 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2087 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2090 ast_debug(2, "Hang up during prefile playback\n");
2091 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
2092 if(ast_test_flag(vmu, MVM_ALLOCED))
2097 /* On a '#' we skip the instructions */
2098 ast_set_flag(&leave_options, OPT_SILENT);
2101 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2102 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
2104 res = ast_waitstream(chan, ecodes);
2106 ast_set_flag(&leave_options, OPT_SILENT);
2111 ast_stopstream(chan);
2112 /* Check for a '*' here in case the caller wants to escape from voicemail to something
2113 other than the operator -- an automated attendant or mailbox login for example */
2115 chan->exten[0] = 'a';
2116 chan->exten[1] = '\0';
2117 if (!ast_strlen_zero(vmu->exit)) {
2118 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2119 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2120 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2123 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
2125 } else if (res == '0') { /* Check for a '0' here */
2126 if(ouseexten || ousemacro) {
2127 chan->exten[0] = 'o';
2128 chan->exten[1] = '\0';
2129 if (!ast_strlen_zero(vmu->exit)) {
2130 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2131 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2132 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2134 ast_play_and_wait(chan, "transfer");
2136 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
2139 } else if (res < 0) {
2140 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
2143 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "SUCCESS");
2145 if(ast_test_flag(vmu, MVM_ALLOCED))
2149 /* Ok, we're ready to rock and roll. Return to dialplan */
2154 /*! \brief Dialplan application to delete voicemail */
2155 static int minivm_delete_exec(struct ast_channel *chan, void *data)
2158 char filename[BUFSIZ];
2160 if (!ast_strlen_zero(data)) {
2161 ast_copy_string(filename, (char *) data, sizeof(filename));
2163 ast_channel_lock(chan);
2164 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2165 ast_channel_unlock(chan);
2168 if (ast_strlen_zero(filename)) {
2169 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2173 /* Go ahead and delete audio files from system, they're not needed any more */
2174 /* We should look for both audio and text files here */
2175 if (ast_fileexists(filename, NULL, NULL) > 0) {
2176 res = vm_delete(filename);
2178 ast_debug(2, "-_-_- Can't delete file: %s\n", filename);
2179 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2181 ast_debug(2, "-_-_- Deleted voicemail file :: %s \n", filename);
2182 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "SUCCESS");
2185 ast_debug(2, "-_-_- Filename does not exist: %s\n", filename);
2186 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2192 /*! \brief Record specific messages for voicemail account */
2193 static int minivm_accmess_exec(struct ast_channel *chan, void *data)
2198 char filename[PATH_MAX];
2201 char *tmpptr = NULL;
2202 struct minivm_account *vmu;
2203 char *username = argv[0];
2204 struct ast_flags flags = { 0 };
2205 char *opts[OPT_ARG_ARRAY_SIZE];
2207 char *message = NULL;
2208 char *prompt = NULL;
2212 if (ast_strlen_zero(data)) {
2213 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2216 tmpptr = ast_strdupa((char *)data);
2219 ast_log(LOG_ERROR, "Out of memory\n");
2222 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2226 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2229 if (!error && strlen(argv[1]) > 1) {
2230 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2234 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2235 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2242 ast_copy_string(tmp, argv[0], sizeof(tmp));
2244 domain = strchr(tmp, '@');
2249 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2250 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2254 if(!(vmu = find_account(domain, username, TRUE))) {
2255 /* We could not find user, let's exit */
2256 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2257 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
2261 /* Answer channel if it's not already answered */
2262 if (chan->_state != AST_STATE_UP)
2265 /* Here's where the action is */
2266 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2268 prompt = "vm-rec-busy";
2269 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2270 message = "unavailable";
2271 prompt = "vm-rec-unavail";
2272 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2274 prompt = "vm-temp-greeting";
2275 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2277 prompt = "vm-rec-name";
2279 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2280 /* Maybe we should check the result of play_record_review ? */
2281 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2283 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2285 if(ast_test_flag(vmu, MVM_ALLOCED))
2289 /* Ok, we're ready to rock and roll. Return to dialplan */
2294 /*! \brief Append new mailbox to mailbox list from configuration file */
2295 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2297 struct minivm_account *vmu;
2300 char accbuf[BUFSIZ];
2302 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2304 ast_copy_string(accbuf, name, sizeof(accbuf));
2306 domain = strchr(accbuf, '@');
2311 if (ast_strlen_zero(domain)) {
2312 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2316 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2318 /* Allocate user account */
2319 vmu = ast_calloc(1, sizeof(*vmu));
2323 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2324 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2326 populate_defaults(vmu);
2328 ast_debug(3, "...Configuring account %s\n", name);
2331 ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2332 if (!strcasecmp(var->name, "serveremail")) {
2333 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2334 } else if (!strcasecmp(var->name, "email")) {
2335 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2336 } else if (!strcasecmp(var->name, "accountcode")) {
2337 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2338 } else if (!strcasecmp(var->name, "pincode")) {
2339 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2340 } else if (!strcasecmp(var->name, "domain")) {
2341 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2342 } else if (!strcasecmp(var->name, "language")) {
2343 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2344 } else if (!strcasecmp(var->name, "timezone")) {
2345 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2346 } else if (!strcasecmp(var->name, "externnotify")) {
2347 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2348 } else if (!strcasecmp(var->name, "etemplate")) {
2349 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2350 } else if (!strcasecmp(var->name, "ptemplate")) {
2351 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2352 } else if (!strcasecmp(var->name, "fullname")) {
2353 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2354 } else if (!strcasecmp(var->name, "setvar")) {
2356 char *varname = ast_strdupa(var->value);
2357 struct ast_variable *tmpvar;
2359 if (varname && (varval = strchr(varname, '='))) {
2362 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2363 tmpvar->next = vmu->chanvars;
2364 vmu->chanvars = tmpvar;
2367 } else if (!strcasecmp(var->name, "pager")) {
2368 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2369 } else if (!strcasecmp(var->name, "volgain")) {
2370 sscanf(var->value, "%lf", &vmu->volgain);
2372 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2376 ast_debug(3, "...Linking account %s\n", name);
2378 AST_LIST_LOCK(&minivm_accounts);
2379 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2380 AST_LIST_UNLOCK(&minivm_accounts);
2382 global_stats.voicemailaccounts++;
2384 ast_debug(2, "MINIVM :: Created account %s@%s - tz %s etemplate %s %s\n", username, domain, ast_strlen_zero(vmu->zonetag) ? "" : vmu->zonetag, ast_strlen_zero(vmu->etemplate) ? "" : vmu->etemplate, realtime ? "(realtime)" : "");
2388 /*! \brief Free Mini Voicemail timezone */
2389 static void free_zone(struct minivm_zone *z)
2394 /*! \brief Clear list of timezones */
2395 static void timezone_destroy_list(void)
2397 struct minivm_zone *this;
2399 AST_LIST_LOCK(&minivm_zones);
2400 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2403 AST_LIST_UNLOCK(&minivm_zones);
2406 /*! \brief Add time zone to memory list */
2407 static int timezone_add(const char *zonename, const char *config)
2409 struct minivm_zone *newzone;
2410 char *msg_format, *timezone_str;
2412 newzone = ast_calloc(1, sizeof(*newzone));
2413 if (newzone == NULL)
2416 msg_format = ast_strdupa(config);
2417 if (msg_format == NULL) {
2418 ast_log(LOG_WARNING, "Out of memory.\n");
2423 timezone_str = strsep(&msg_format, "|");
2425 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2430 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2431 ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2432 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2434 AST_LIST_LOCK(&minivm_zones);
2435 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2436 AST_LIST_UNLOCK(&minivm_zones);
2438 global_stats.timezones++;
2443 /*! \brief Read message template from file */
2444 static char *message_template_parse_filebody(const char *filename) {
2445 char buf[BUFSIZ * 6];
2446 char readbuf[BUFSIZ];
2447 char filenamebuf[BUFSIZ];
2453 if (ast_strlen_zero(filename))
2455 if (*filename == '/')
2456 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2458 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2460 if (!(fi = fopen(filenamebuf, "r"))) {
2461 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2465 while (fgets(readbuf, sizeof(readbuf), fi)) {
2467 if (writepos != buf) {
2468 *writepos = '\n'; /* Replace EOL with new line */
2471 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2472 writepos += strlen(readbuf) - 1;
2475 messagebody = ast_calloc(1, strlen(buf + 1));
2476 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2477 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2478 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2483 /*! \brief Parse emailbody template from configuration file */
2484 static char *message_template_parse_emailbody(const char *configuration)
2486 char *tmpread, *tmpwrite;
2487 char *emailbody = ast_strdup(configuration);
2489 /* substitute strings \t and \n into the apropriate characters */
2490 tmpread = tmpwrite = emailbody;
2491 while ((tmpwrite = strchr(tmpread,'\\'))) {
2492 int len = strlen("\n");
2493 switch (tmpwrite[1]) {
2495 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2496 strncpy(tmpwrite, "\n", len);
2499 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2500 strncpy(tmpwrite, "\t", len);
2503 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2505 tmpread = tmpwrite + len;
2510 /*! \brief Apply general configuration options */
2511 static int apply_general_options(struct ast_variable *var)
2517 if (!strcmp(var->name, "mailcmd")) {
2518 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2519 } else if (!strcmp(var->name, "maxgreet")) {
2520 global_maxgreet = atoi(var->value);
2521 } else if (!strcmp(var->name, "maxsilence")) {
2522 global_maxsilence = atoi(var->value);
2523 if (global_maxsilence > 0)
2524 global_maxsilence *= 1000;
2525 } else if (!strcmp(var->name, "logfile")) {
2526 if (!ast_strlen_zero(var->value) ) {
2527 if(*(var->value) == '/')
2528 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2530 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2532 } else if (!strcmp(var->name, "externnotify")) {
2533 /* External voicemail notify application */
2534 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2535 } else if (!strcmp(var->name, "silencetreshold")) {
2536 /* Silence treshold */
2537 global_silencethreshold = atoi(var->value);
2538 } else if (!strcmp(var->name, "maxmessage")) {
2540 if (sscanf(var->value, "%d", &x) == 1) {
2541 global_vmmaxmessage = x;
2544 ast_log(LOG_WARNING, "Invalid max message time length\n");
2546 } else if (!strcmp(var->name, "minmessage")) {
2548 if (sscanf(var->value, "%d", &x) == 1) {
2549 global_vmminmessage = x;
2550 if (global_maxsilence <= global_vmminmessage)
2551 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2554 ast_log(LOG_WARNING, "Invalid min message time length\n");
2556 } else if (!strcmp(var->name, "format")) {
2557 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2558 } else if (!strcmp(var->name, "review")) {
2559 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2560 } else if (!strcmp(var->name, "operator")) {
2561 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2568 /*! \brief Load minivoicemail configuration */
2569 static int load_config(int reload)
2571 struct ast_config *cfg;
2572 struct ast_variable *var;
2574 const char *chanvar;
2576 struct minivm_template *template;
2577 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2579 cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2580 if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2582 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2583 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
2587 ast_mutex_lock(&minivmlock);
2589 /* Destroy lists to reconfigure */
2590 message_destroy_list(); /* Destroy list of voicemail message templates */
2591 timezone_destroy_list(); /* Destroy list of timezones */
2592 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2593 ast_debug(2, "Destroyed memory objects...\n");
2595 /* First, set some default settings */
2596 global_externnotify[0] = '\0';
2597 global_logfile[0] = '\0';
2598 global_vmmaxmessage = 2000;
2599 global_maxgreet = 2000;
2600 global_vmminmessage = 0;
2601 strcpy(global_mailcmd, SENDMAIL);
2602 global_maxsilence = 0;
2603 global_saydurationminfo = 2;
2604 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2605 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2606 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2607 strcpy(global_charset, "ISO-8859-1");
2608 /* Reset statistics */
2609 memset(&global_stats, 0, sizeof(global_stats));
2610 global_stats.reset = ast_tvnow();
2612 global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2614 /* Make sure we could load configuration file */
2616 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2617 ast_mutex_unlock(&minivmlock);
2621 ast_debug(2, "-_-_- Loaded configuration file, now parsing\n");
2623 /* General settings */
2625 cat = ast_category_browse(cfg, NULL);
2627 ast_debug(3, "-_-_- Found configuration section [%s]\n", cat);
2628 if (!strcasecmp(cat, "general")) {
2629 /* Nothing right now */
2630 error += apply_general_options(ast_variable_browse(cfg, cat));
2631 } else if (!strncasecmp(cat, "template-", 9)) {
2633 char *name = cat + 9;
2635 /* Now build and link template to list */
2636 error += message_template_build(name, ast_variable_browse(cfg, cat));
2638 var = ast_variable_browse(cfg, cat);
2639 if (!strcasecmp(cat, "zonemessages")) {
2640 /* Timezones in this context */
2642 timezone_add(var->name, var->value);
2646 /* Create mailbox from this */
2647 error += create_vmaccount(cat, var, FALSE);
2650 /* Find next section in configuration file */
2651 cat = ast_category_browse(cfg, cat);
2654 /* Configure the default email template */
2655 message_template_build("email-default", NULL);
2656 template = message_template_find("email-default");
2658 /* Load date format config for voicemail mail */
2659 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2660 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2661 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2662 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2663 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2664 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2665 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2666 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2667 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2668 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2669 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2670 template->body = message_template_parse_emailbody(chanvar);
2671 template->attachment = TRUE;
2673 message_template_build("pager-default", NULL);
2674 template = message_template_find("pager-default");
2675 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2676 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2677 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2678 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2679 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2680 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2681 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2682 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2683 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2684 template->body = message_template_parse_emailbody(chanvar);
2685 template->attachment = FALSE;
2688 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2690 ast_mutex_unlock(&minivmlock);
2691 ast_config_destroy(cfg);
2693 /* Close log file if it's open and disabled */
2695 fclose(minivmlogfile);
2697 /* Open log file if it's enabled */
2698 if(!ast_strlen_zero(global_logfile)) {
2699 minivmlogfile = fopen(global_logfile, "a");
2701 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2703 ast_debug(3, "-_-_- Opened log file %s \n", global_logfile);
2709 /*! \brief CLI routine for listing templates */
2710 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2712 struct minivm_template *this;
2713 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2718 e->command = "minivm list templates";
2720 "Usage: minivm list templates\n"
2721 " Lists message templates for e-mail, paging and IM\n";
2728 return CLI_SHOWUSAGE;
2730 AST_LIST_LOCK(&message_templates);
2731 if (AST_LIST_EMPTY(&message_templates)) {
2732 ast_cli(a->fd, "There are no message templates defined\n");
2733 AST_LIST_UNLOCK(&message_templates);
2736 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2737 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2738 AST_LIST_TRAVERSE(&message_templates, this, list) {
2739 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
2740 this->charset ? this->charset : "-",
2741 this->locale ? this->locale : "-",
2742 this->attachment ? "Yes" : "No",
2743 this->subject ? this->subject : "-");
2746 AST_LIST_UNLOCK(&message_templates);
2747 ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2751 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2755 struct minivm_account *vmu;
2756 const char *domain = "";
2758 /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2762 return (state == 0) ? ast_strdup("for") : NULL;
2763 wordlen = strlen(word);
2764 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2765 if (!strncasecmp(word, vmu->domain, wordlen)) {
2766 if (domain && strcmp(domain, vmu->domain) && ++which > state)
2767 return ast_strdup(vmu->domain);
2768 /* ignore repeated domains ? */
2769 domain = vmu->domain;
2775 /*! \brief CLI command to list voicemail accounts */
2776 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2778 struct minivm_account *vmu;
2779 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
2784 e->command = "minivm list accounts";
2786 "Usage: minivm list accounts\n"
2787 " Lists all mailboxes currently set up\n";
2790 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
2793 if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
2794 return CLI_SHOWUSAGE;
2795 if ((a->argc == 5) && strcmp(a->argv[3],"for"))
2796 return CLI_SHOWUSAGE;
2798 AST_LIST_LOCK(&minivm_accounts);
2799 if (AST_LIST_EMPTY(&minivm_accounts)) {
2800 ast_cli(a->fd, "There are no voicemail users currently defined\n");
2801 AST_LIST_UNLOCK(&minivm_accounts);
2804 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2805 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
2806 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2808 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
2810 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2811 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-",
2812 vmu->ptemplate ? vmu->ptemplate : "-",
2813 vmu->zonetag ? vmu->zonetag : "-",
2814 vmu->attachfmt ? vmu->attachfmt : "-",
2818 AST_LIST_UNLOCK(&minivm_accounts);
2819 ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
2823 /*! \brief Show a list of voicemail zones in the CLI */
2824 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2826 struct minivm_zone *zone;
2827 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
2828 char *res = CLI_SUCCESS;
2832 e->command = "minivm list zones";
2834 "Usage: minivm list zones\n"
2835 " Lists zone message formats\n";
2841 if (a->argc != e->args)
2842 return CLI_SHOWUSAGE;
2844 AST_LIST_LOCK(&minivm_zones);
2845 if (!AST_LIST_EMPTY(&minivm_zones)) {
2846 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
2847 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
2848 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2849 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
2852 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
2855 AST_LIST_UNLOCK(&minivm_zones);
2860 /*! \brief CLI Show settings */
2861 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2865 e->command = "minivm show settings";
2867 "Usage: minivm show settings\n"
2868 " Display Mini-Voicemail general settings\n";
2874 ast_cli(a->fd, "* Mini-Voicemail general settings\n");
2875 ast_cli(a->fd, " -------------------------------\n");
2876 ast_cli(a->fd, "\n");
2877 ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd);
2878 ast_cli(a->fd, " Max silence: %d\n", global_maxsilence);
2879 ast_cli(a->fd, " Silence threshold: %d\n", global_silencethreshold);
2880 ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage);
2881 ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage);
2882 ast_cli(a->fd, " Default format: %s\n", default_vmformat);
2883 ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify);
2884 ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2885 ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2886 ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2888 ast_cli(a->fd, "\n");
2892 /*! \brief Show stats */
2893 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2895 struct ast_tm timebuf;
2901 e->command = "minivm show stats";
2903 "Usage: minivm show stats\n"
2904 " Display Mini-Voicemail counters\n";
2910 ast_cli(a->fd, "* Mini-Voicemail statistics\n");
2911 ast_cli(a->fd, " -------------------------\n");
2912 ast_cli(a->fd, "\n");
2913 ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts);
2914 ast_cli(a->fd, " Templates: %5d\n", global_stats.templates);
2915 ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones);
2916 if (global_stats.receivedmessages == 0) {
2917 ast_cli(a->fd, " Received messages since last reset: <none>\n");
2919 ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
2920 ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
2921 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
2922 ast_cli(a->fd, " Last received voicemail: %s\n", buf);
2924 ast_localtime(&global_stats.reset, &timebuf, NULL);
2925 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
2926 ast_cli(a->fd, " Last reset: %s\n", buf);
2928 ast_cli(a->fd, "\n");
2932 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
2933 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2935 struct minivm_account *vmu;
2936 char *username, *domain, *colname;
2938 if (!(username = ast_strdupa(data))) {
2939 ast_log(LOG_ERROR, "Memory Error!\n");
2943 if ((colname = strchr(username, ':'))) {
2949 if ((domain = strchr(username, '@'))) {
2953 if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2954 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2958 if (!(vmu = find_account(domain, username, TRUE)))
2961 if (!strcasecmp(colname, "hasaccount")) {
2962 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2963 } else if (!strcasecmp(colname, "fullname")) {
2964 ast_copy_string(buf, vmu->fullname, len);
2965 } else if (!strcasecmp(colname, "email")) {
2966 if (!ast_strlen_zero(vmu->email))
2967 ast_copy_string(buf, vmu->email, len);
2969 snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2970 } else if (!strcasecmp(colname, "pager")) {
2971 ast_copy_string(buf, vmu->pager, len);
2972 } else if (!strcasecmp(colname, "etemplate")) {
2973 if (!ast_strlen_zero(vmu->etemplate))
2974 ast_copy_string(buf, vmu->etemplate, len);
2976 ast_copy_string(buf, "email-default", len);
2977 } else if (!strcasecmp(colname, "language")) {
2978 ast_copy_string(buf, vmu->language, len);
2979 } else if (!strcasecmp(colname, "timezone")) {
2980 ast_copy_string(buf, vmu->zonetag, len);
2981 } else if (!strcasecmp(colname, "ptemplate")) {
2982 if (!ast_strlen_zero(vmu->ptemplate))
2983 ast_copy_string(buf, vmu->ptemplate, len);
2985 ast_copy_string(buf, "email-default", len);
2986 } else if (!strcasecmp(colname, "accountcode")) {
2987 ast_copy_string(buf, vmu->accountcode, len);
2988 } else if (!strcasecmp(colname, "pincode")) {
2989 ast_copy_string(buf, vmu->pincode, len);
2990 } else if (!strcasecmp(colname, "path")) {
2991 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2992 } else { /* Look in channel variables */
2993 struct ast_variable *var;
2996 for (var = vmu->chanvars ; var ; var = var->next)
2997 if (!strcmp(var->name, colname)) {
2998 ast_copy_string(buf, var->value, len);
3004 if(ast_test_flag(vmu, MVM_ALLOCED))
3010 /*! \brief lock directory
3012 only return failure if ast_lock_path returns 'timeout',
3013 not if the path does not exist or any other reason
3015 static int vm_lock_path(const char *path)
3017 switch (ast_lock_path(path)) {
3018 case AST_LOCK_TIMEOUT:
3025 /*! \brief Access counter file, lock directory, read and possibly write it again changed
3026 \param directory Directory to crate file in
3027 \param countername filename
3028 \param value If set to zero, we only read the variable
3029 \param operand 0 to read, 1 to set new value, 2 to change
3030 \return -1 on error, otherwise counter value
3032 static int access_counter_file(char *directory, char *countername, int value, int operand)
3034 char filename[BUFSIZ];
3035 char readbuf[BUFSIZ];
3037 int old = 0, counter = 0;
3039 /* Lock directory */
3040 if (vm_lock_path(directory)) {
3041 return -1; /* Could not lock directory */
3043 snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
3045 counterfile = fopen(filename, "r");
3047 if(fgets(readbuf, sizeof(readbuf), counterfile)) {
3048 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
3049 old = counter = atoi(readbuf);
3051 fclose(counterfile);
3055 case 0: /* Read only */
3056 ast_unlock_path(directory);
3057 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
3060 case 1: /* Set new value */
3063 case 2: /* Change value */
3065 if (counter < 0) /* Don't allow counters to fall below zero */
3070 /* Now, write the new value to the file */
3071 counterfile = fopen(filename, "w");
3073 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
3074 ast_unlock_path(directory);
3075 return -1; /* Could not open file for writing */
3077 fprintf(counterfile, "%d\n\n", counter);
3078 fclose(counterfile);
3079 ast_unlock_path(directory);
3080 ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
3084 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
3085 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3087 char *username, *domain, *countername;
3088 struct minivm_account *vmu = NULL;
3089 char userpath[BUFSIZ];
3094 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
3095 ast_log(LOG_WARNING, "Memory error!\n");
3098 if ((countername = strchr(username, ':'))) {
3099 *countername = '\0';