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. The units are whole-number decibels (dB).</para>
207 <para>his application is part of the Mini-Voicemail system, configured in minivm.conf</para>
208 <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
209 <para>If there's no user account for that address, a temporary account will be used with default options.</para>
210 <para>The recorded file name and path will be stored in MINIVM_FILENAME and the duration of the message will be stored in MINIVM_DURATION</para>
211 <para>Note: If the caller hangs up after the recording, the only way to send the message and clean up is to execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits are received and the requested extension exist in the current context.</para>
213 <variable name="MINIVM_RECORD_STATUS">
214 <para>This is the status of the record operation</para>
215 <value name="SUCCESS" />
216 <value name="USEREXIT" />
217 <value name="FAILED" />
223 <application name="MinivmGreet" language="en_US">
225 Play Mini-Voicemail prompts.
228 <parameter name="mailbox" required="true" argsep="@">
229 <argument name="username" required="true">
230 <para>Voicemail username</para>
232 <argument name="domain" required="true">
233 <para>Voicemail domain</para>
236 <parameter name="options" required="false">
239 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
242 <para>Skip the playback of instructions for leaving a message to the calling party.</para>
245 <para>Play the <literal>unavailable</literal> greeting.</para>
251 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
252 <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
253 <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary message exists for the account.</para>
255 <variable name="MINIVM_GREET_STATUS">
256 <para>This is the status of the greeting playback.</para>
257 <value name="SUCCESS" />
258 <value name="USEREXIT" />
259 <value name="FAILED" />
265 <application name="MinivmNotify" language="en_US">
267 Notify voicemail owner about new messages.
270 <parameter name="mailbox" required="true" argsep="@">
271 <argument name="username" required="true">
272 <para>Voicemail username</para>
274 <argument name="domain" required="true">
275 <para>Voicemail domain</para>
278 <parameter name="options" required="false">
280 <option name="template">
281 <para>E-mail template to use for voicemail notification</para>
287 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
288 <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user account for that address, a temporary account will be used with default options (set in minivm.conf).</para>
289 <para>If the channel variable MVM_COUNTER is set, this will be used in the message file name and available in the template for the message.</para>
290 <para>If no template is given, the default email template will be used to send email and default pager template to send paging message (if the user account is configured with a paging address.</para>
292 <variable name="MINIVM_NOTIFY_STATUS">
293 <para>This is the status of the notification attempt</para>
294 <value name="SUCCESS" />
295 <value name="FAILED" />
301 <application name="MinivmDelete" language="en_US">
303 Delete Mini-Voicemail voicemail messages
306 <parameter name="filename" required="true">
307 <para>File to delete</para>
311 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
312 <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
314 <variable name="MINIVM_DELETE_STATUS">
315 <para>This is the status of the delete operation.</para>
316 <value name="SUCCESS" />
317 <value name="FAILED" />
323 <application name="MinivmAccMess" language="en_US">
325 Record account specific messages.
328 <parameter name="mailbox" required="true" argsep="@">
329 <argument name="username" required="true">
330 <para>Voicemail username</para>
332 <argument name="domain" required="true">
333 <para>Voicemail domain</para>
336 <parameter name="options" required="false">
339 <para>Record the <literal>unavailable</literal> greeting.</para>
342 <para>Record the <literal>busy</literal> greeting.</para>
345 <para>Record the temporary greeting.</para>
348 <para>Account name.</para>
354 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
355 <para>Use this application to record account specific audio/video messages for busy, unavailable and temporary messages.</para>
356 <para>Account specific directories will be created if they do not exist.</para>
358 <variable name="MINIVM_ACCMESS_STATUS">
359 <para>This is the result of the attempt to record the specified greeting.</para>
360 <para><literal>FAILED</literal> is set if the file can't be created.</para>
361 <value name="SUCCESS" />
362 <value name="FAILED" />
368 <application name="MinivmMWI" language="en_US">
370 Send Message Waiting Notification to subscriber(s) of mailbox.
373 <parameter name="mailbox" required="true" argsep="@">
374 <argument name="username" required="true">
375 <para>Voicemail username</para>
377 <argument name="domain" required="true">
378 <para>Voicemail domain</para>
381 <parameter name="urgent" required="true">
382 <para>Number of urgent messages in mailbox.</para>
384 <parameter name="new" required="true">
385 <para>Number of new messages in mailbox.</para>
387 <parameter name="old" required="true">
388 <para>Number of old messages in mailbox.</para>
392 <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
393 <para>MinivmMWI is used to send message waiting indication to any devices whose channels have subscribed to the mailbox passed in the first parameter.</para>
406 #define MVM_REVIEW (1 << 0) /*!< Review message */
407 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
408 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
409 #define MVM_SVMAIL (1 << 3)
410 #define MVM_ENVELOPE (1 << 4)
411 #define MVM_PBXSKIP (1 << 9)
412 #define MVM_ALLOCED (1 << 13)
414 /*! \brief Default mail command to mail voicemail. Change it with the
415 mailcmd= command in voicemail.conf */
416 #define SENDMAIL "/usr/sbin/sendmail -t"
418 #define SOUND_INTRO "vm-intro"
419 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
420 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
423 #define MAX_DATETIME_FORMAT 512
424 #define MAX_NUM_CID_CONTEXTS 10
426 #define ERROR_LOCK_PATH -100
427 #define VOICEMAIL_DIR_MODE 0700
429 #define VOICEMAIL_CONFIG "minivm.conf"
430 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
432 /*! \brief Message types for notification */
433 enum mvm_messagetype {
436 /* For trunk: MVM_MESSAGE_JABBER, */
439 static char MVM_SPOOL_DIR[PATH_MAX];
441 /* Module declarations */
442 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
443 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
444 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
445 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
446 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
447 static char *app_minivm_mwi = "MinivmMWI";
452 OPT_SILENT = (1 << 0),
453 OPT_BUSY_GREETING = (1 << 1),
454 OPT_UNAVAIL_GREETING = (1 << 2),
455 OPT_TEMP_GREETING = (1 << 3),
456 OPT_NAME_GREETING = (1 << 4),
457 OPT_RECORDGAIN = (1 << 5),
458 } minivm_option_flags;
461 OPT_ARG_RECORDGAIN = 0,
462 OPT_ARG_ARRAY_SIZE = 1,
463 } minivm_option_args;
465 AST_APP_OPTIONS(minivm_app_options, {
466 AST_APP_OPTION('s', OPT_SILENT),
467 AST_APP_OPTION('b', OPT_BUSY_GREETING),
468 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
469 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
472 AST_APP_OPTIONS(minivm_accmess_options, {
473 AST_APP_OPTION('b', OPT_BUSY_GREETING),
474 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
475 AST_APP_OPTION('t', OPT_TEMP_GREETING),
476 AST_APP_OPTION('n', OPT_NAME_GREETING),
479 /*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
480 struct minivm_account {
481 char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
482 char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
484 char pincode[10]; /*!< Secret pin code, numbers only */
485 char fullname[120]; /*!< Full name, for directory app */
486 char email[80]; /*!< E-mail address - override */
487 char pager[80]; /*!< E-mail address to pager (no attachment) */
488 char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
489 char serveremail[80]; /*!< From: Mail address */
490 char externnotify[160]; /*!< Configurable notification command */
491 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
492 char zonetag[80]; /*!< Time zone */
493 char uniqueid[20]; /*!< Unique integer identifier */
494 char exit[80]; /*!< Options for exiting from voicemail() */
495 char attachfmt[80]; /*!< Format for voicemail audio file attachment */
496 char etemplate[80]; /*!< Pager template */
497 char ptemplate[80]; /*!< Voicemail format */
498 unsigned int flags; /*!< MVM_ flags */
499 struct ast_variable *chanvars; /*!< Variables for e-mail template */
500 double volgain; /*!< Volume gain for voicemails sent via e-mail */
501 AST_LIST_ENTRY(minivm_account) list;
504 /*! \brief The list of e-mail accounts */
505 static AST_LIST_HEAD_STATIC(minivm_accounts, minivm_account);
507 /*! \brief Linked list of e-mail templates in various languages
508 These are used as templates for e-mails, pager messages and jabber messages
509 \ref message_templates
511 struct minivm_template {
512 char name[80]; /*!< Template name */
513 char *body; /*!< Body of this template */
514 char fromaddress[100]; /*!< Who's sending the e-mail? */
515 char serveremail[80]; /*!< From: Mail address */
516 char subject[100]; /*!< Subject line */
517 char charset[32]; /*!< Default character set for this template */
518 char locale[20]; /*!< Locale for setlocale() */
519 char dateformat[80]; /*!< Date format to use in this attachment */
520 int attachment; /*!< Attachment of media yes/no - no for pager messages */
521 AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
524 /*! \brief The list of e-mail templates */
525 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
527 /*! \brief Options for leaving voicemail with the voicemail() application */
528 struct leave_vm_options {
530 signed char record_gain;
533 /*! \brief Structure for base64 encoding */
539 unsigned char iobuf[B64_BASEMAXINLINE];
542 /*! \brief Voicemail time zones */
544 char name[80]; /*!< Name of this time zone */
545 char timezone[80]; /*!< Timezone definition */
546 char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
547 AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
550 /*! \brief The list of e-mail time zones */
551 static AST_LIST_HEAD_STATIC(minivm_zones, minivm_zone);
553 /*! \brief Structure for gathering statistics */
554 struct minivm_stats {
555 int voicemailaccounts; /*!< Number of static accounts */
556 int timezones; /*!< Number of time zones */
557 int templates; /*!< Number of templates */
559 struct timeval reset; /*!< Time for last reset */
560 int receivedmessages; /*!< Number of received messages since reset */
561 struct timeval lastreceived; /*!< Time for last voicemail sent */
564 /*! \brief Statistics for voicemail */
565 static struct minivm_stats global_stats;
567 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
568 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
570 FILE *minivmlogfile; /*!< The minivm log file */
572 static int global_vmminmessage; /*!< Minimum duration of messages */
573 static int global_vmmaxmessage; /*!< Maximum duration of message */
574 static int global_maxsilence; /*!< Maximum silence during recording */
575 static int global_maxgreet; /*!< Maximum length of prompts */
576 static int global_silencethreshold = 128;
577 static char global_mailcmd[160]; /*!< Configurable mail cmd */
578 static char global_externnotify[160]; /*!< External notification application */
579 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
580 static char default_vmformat[80];
582 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
583 static int global_saydurationminfo;
584 static char global_charset[32]; /*!< Global charset in messages */
586 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
588 /*! \brief Default dateformat, can be overridden in configuration file */
589 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
590 #define DEFAULT_CHARSET "ISO-8859-1"
592 /* Forward declarations */
593 static char *message_template_parse_filebody(const char *filename);
594 static char *message_template_parse_emailbody(const char *body);
595 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
596 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
597 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
599 /*! \brief Create message template */
600 static struct minivm_template *message_template_create(const char *name)
602 struct minivm_template *template;
604 template = ast_calloc(1, sizeof(*template));
608 /* Set some defaults for templates */
609 ast_copy_string(template->name, name, sizeof(template->name));
610 ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
611 ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
612 ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
613 template->attachment = TRUE;
618 /*! \brief Release memory allocated by message template */
619 static void message_template_free(struct minivm_template *template)
622 ast_free(template->body);
627 /*! \brief Build message template from configuration */
628 static int message_template_build(const char *name, struct ast_variable *var)
630 struct minivm_template *template;
633 template = message_template_create(name);
635 ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
640 ast_debug(3, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
641 if (!strcasecmp(var->name, "fromaddress")) {
642 ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
643 } else if (!strcasecmp(var->name, "fromemail")) {
644 ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
645 } else if (!strcasecmp(var->name, "subject")) {
646 ast_copy_string(template->subject, var->value, sizeof(template->subject));
647 } else if (!strcasecmp(var->name, "locale")) {
648 ast_copy_string(template->locale, var->value, sizeof(template->locale));
649 } else if (!strcasecmp(var->name, "attachmedia")) {
650 template->attachment = ast_true(var->value);
651 } else if (!strcasecmp(var->name, "dateformat")) {
652 ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
653 } else if (!strcasecmp(var->name, "charset")) {
654 ast_copy_string(template->charset, var->value, sizeof(template->charset));
655 } else if (!strcasecmp(var->name, "templatefile")) {
657 ast_free(template->body);
658 template->body = message_template_parse_filebody(var->value);
659 if (!template->body) {
660 ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
663 } else if (!strcasecmp(var->name, "messagebody")) {
665 ast_free(template->body);
666 template->body = message_template_parse_emailbody(var->value);
667 if (!template->body) {
668 ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
672 ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
678 ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
680 AST_LIST_LOCK(&message_templates);
681 AST_LIST_INSERT_TAIL(&message_templates, template, list);
682 AST_LIST_UNLOCK(&message_templates);
684 global_stats.templates++;
689 /*! \brief Find named template */
690 static struct minivm_template *message_template_find(const char *name)
692 struct minivm_template *this, *res = NULL;
694 if (ast_strlen_zero(name))
697 AST_LIST_LOCK(&message_templates);
698 AST_LIST_TRAVERSE(&message_templates, this, list) {
699 if (!strcasecmp(this->name, name)) {
704 AST_LIST_UNLOCK(&message_templates);
710 /*! \brief Clear list of templates */
711 static void message_destroy_list(void)
713 struct minivm_template *this;
714 AST_LIST_LOCK(&message_templates);
715 while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list)))
716 message_template_free(this);
718 AST_LIST_UNLOCK(&message_templates);
721 /*! \brief read buffer from file (base64 conversion) */
722 static int b64_inbuf(struct b64_baseio *bio, FILE *fi)
729 if ((l = fread(bio->iobuf, 1, B64_BASEMAXINLINE,fi)) <= 0) {
743 /*! \brief read character from file to buffer (base64 conversion) */
744 static int b64_inchar(struct b64_baseio *bio, FILE *fi)
746 if (bio->iocp >= bio->iolen) {
747 if (!b64_inbuf(bio, fi))
751 return bio->iobuf[bio->iocp++];
754 /*! \brief write buffer to file (base64 conversion) */
755 static int b64_ochar(struct b64_baseio *bio, int c, FILE *so)
757 if (bio->linelength >= B64_BASELINELEN) {
758 if (fputs(EOL,so) == EOF)
764 if (putc(((unsigned char) c), so) == EOF)
772 /*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
773 static int base_encode(char *filename, FILE *so)
775 unsigned char dtable[B64_BASEMAXINLINE];
778 struct b64_baseio bio;
780 memset(&bio, 0, sizeof(bio));
781 bio.iocp = B64_BASEMAXINLINE;
783 if (!(fi = fopen(filename, "rb"))) {
784 ast_log(LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
788 for (i= 0; i<9; i++) {
792 dtable[26+i+9]= 'j'+i;
794 for (i= 0; i < 8; i++) {
796 dtable[26+i+18]= 's'+i;
798 for (i= 0; i < 10; i++) {
805 unsigned char igroup[3], ogroup[4];
808 igroup[0]= igroup[1]= igroup[2]= 0;
810 for (n= 0; n < 3; n++) {
811 if ((c = b64_inchar(&bio, fi)) == EOF) {
815 igroup[n]= (unsigned char)c;
819 ogroup[0]= dtable[igroup[0]>>2];
820 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
821 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
822 ogroup[3]= dtable[igroup[2]&0x3F];
832 b64_ochar(&bio, ogroup[i], so);
836 /* Put end of line - line feed */
837 if (fputs(EOL, so) == EOF)
845 static int get_date(char *s, int len)
848 struct timeval now = ast_tvnow();
850 ast_localtime(&now, &tm, NULL);
851 return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
855 /*! \brief Free user structure - if it's allocated */
856 static void free_user(struct minivm_account *vmu)
859 ast_variables_destroy(vmu->chanvars);
865 /*! \brief Prepare for voicemail template by adding channel variables
868 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)
871 struct ast_variable *var;
874 ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
878 for (var = vmu->chanvars ; var ; var = var->next) {
879 pbx_builtin_setvar_helper(channel, var->name, var->value);
882 /* Prepare variables for substition in email body and subject */
883 pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
884 pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
885 pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
886 pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
887 pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
888 pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
889 pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
890 pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
891 if (!ast_strlen_zero(counter))
892 pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
895 /*! \brief Set default values for Mini-Voicemail users */
896 static void populate_defaults(struct minivm_account *vmu)
898 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
899 ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
900 vmu->volgain = global_volgain;
903 /*! \brief Fix quote of mail headers for non-ascii characters */
904 static char *mailheader_quote(const char *from, char *to, size_t len)
908 for (; ptr < to + len - 1; from++) {
911 else if (*from == '\0')
915 if (ptr < to + len - 1)
922 /*! \brief Allocate new vm user and set default values */
923 static struct minivm_account *mvm_user_alloc(void)
925 struct minivm_account *new;
927 new = ast_calloc(1, sizeof(*new));
930 populate_defaults(new);
936 /*! \brief Clear list of users */
937 static void vmaccounts_destroy_list(void)
939 struct minivm_account *this;
940 AST_LIST_LOCK(&minivm_accounts);
941 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
943 AST_LIST_UNLOCK(&minivm_accounts);
947 /*! \brief Find user from static memory object list */
948 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
950 struct minivm_account *vmu = NULL, *cur;
953 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
954 ast_log(LOG_NOTICE, "No username or domain? \n");
957 ast_debug(3, "-_-_-_- Looking for voicemail user %s in domain %s\n", username, domain);
959 AST_LIST_LOCK(&minivm_accounts);
960 AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
961 /* Is this the voicemail account we're looking for? */
962 if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
965 AST_LIST_UNLOCK(&minivm_accounts);
968 ast_debug(3, "-_-_- Found account for %s@%s\n", username, domain);
972 vmu = find_user_realtime(domain, username);
974 if (createtemp && !vmu) {
975 /* Create a temporary user, send e-mail and be gone */
976 vmu = mvm_user_alloc();
977 ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
979 ast_copy_string(vmu->username, username, sizeof(vmu->username));
980 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
981 ast_debug(1, "--- Created temporary account\n");
988 /*! \brief Find user in realtime storage
989 Returns pointer to minivm_account structure
991 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
993 struct ast_variable *var;
994 struct minivm_account *retval;
995 char name[MAXHOSTNAMELEN];
997 retval = mvm_user_alloc();
1002 ast_copy_string(retval->username, username, sizeof(retval->username));
1004 populate_defaults(retval);
1005 var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
1012 snprintf(name, sizeof(name), "%s@%s", username, domain);
1013 create_vmaccount(name, var, TRUE);
1015 ast_variables_destroy(var);
1019 /*! \brief Send voicemail with audio file as an attachment */
1020 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)
1024 char email[256] = "";
1028 char fname[PATH_MAX];
1030 char tmp[80] = "/tmp/astmail-XXXXXX";
1031 char tmp2[PATH_MAX];
1034 struct minivm_zone *the_zone = NULL;
1036 struct ast_channel *ast;
1037 char *finalfilename;
1038 char *passdata = NULL;
1039 char *passdata2 = NULL;
1043 if (type == MVM_MESSAGE_EMAIL) {
1044 if (vmu && !ast_strlen_zero(vmu->email)) {
1045 ast_copy_string(email, vmu->email, sizeof(email));
1046 } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
1047 snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1048 } else if (type == MVM_MESSAGE_PAGE) {
1049 ast_copy_string(email, vmu->pager, sizeof(email));
1052 if (ast_strlen_zero(email)) {
1053 ast_log(LOG_WARNING, "No address to send message to.\n");
1057 ast_debug(3, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1059 if (!strcmp(format, "wav49"))
1063 /* If we have a gain option, process it now with sox */
1064 if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1065 char newtmp[PATH_MAX];
1066 char tmpcmd[PATH_MAX];
1069 ast_copy_string(newtmp, "/tmp/XXXXXX", sizeof(newtmp));
1070 ast_debug(3, "newtmp: %s\n", newtmp);
1071 tmpfd = mkstemp(newtmp);
1072 snprintf(tmpcmd, sizeof(tmpcmd), "sox -v %.4f %s.%s %s.%s", vmu->volgain, filename, format, newtmp, format);
1073 ast_safe_system(tmpcmd);
1074 finalfilename = newtmp;
1075 ast_debug(3, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1077 finalfilename = ast_strdupa(filename);
1080 /* Create file name */
1081 snprintf(fname, sizeof(fname), "%s.%s", finalfilename, format);
1083 if (template->attachment)
1084 ast_debug(1, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename, format, attach_user_voicemail);
1086 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1090 p = fdopen(pfd, "w");
1095 ast_debug(1, "-_-_- Opening temp file for e-mail: %s\n", tmp);
1098 ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1101 /* Allocate channel used for chanvar substitution */
1102 ast = ast_channel_alloc(0, AST_STATE_DOWN, 0, 0, "", "", "", 0, 0);
1105 snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1107 /* Does this user have a timezone specified? */
1108 if (!ast_strlen_zero(vmu->zonetag)) {
1109 /* Find the zone in the list */
1110 struct minivm_zone *z;
1111 AST_LIST_LOCK(&minivm_zones);
1112 AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1113 if (strcmp(z->name, vmu->zonetag))
1117 AST_LIST_UNLOCK(&minivm_zones);
1121 ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1122 ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1124 /* Start printing the email to the temporary file */
1125 fprintf(p, "Date: %s\n", date);
1127 /* Set date format for voicemail mail */
1128 ast_strftime(date, sizeof(date), template->dateformat, &tm);
1131 /* Populate channel with channel variables for substitution */
1132 prep_email_sub_vars(ast, vmu, cidnum, cidname, dur, date, counter);
1134 /* Find email address to use */
1135 /* If there's a server e-mail adress in the account, user that, othterwise template */
1136 fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1138 /* Find name to user for server e-mail */
1139 fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1141 /* If needed, add hostname as domain */
1142 if (ast_strlen_zero(fromemail))
1143 fromemail = "asterisk";
1145 if (strchr(fromemail, '@'))
1146 ast_copy_string(who, fromemail, sizeof(who));
1148 char host[MAXHOSTNAMELEN];
1149 gethostname(host, sizeof(host)-1);
1150 snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1153 if (ast_strlen_zero(fromaddress)) {
1154 fprintf(p, "From: Asterisk PBX <%s>\n", who);
1156 /* Allocate a buffer big enough for variable substitution */
1157 int vmlen = strlen(fromaddress) * 3 + 200;
1159 ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress);
1160 if ((passdata = alloca(vmlen))) {
1161 pbx_substitute_variables_helper(ast, fromaddress, passdata, vmlen);
1162 len_passdata = strlen(passdata) * 2 + 3;
1163 passdata2 = alloca(len_passdata);
1164 fprintf(p, "From: %s <%s>\n", mailheader_quote(passdata, passdata2, len_passdata), who);
1166 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1171 ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata) ? "-default-" : passdata);
1173 fprintf(p, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu->username, (int)getpid(), who);
1174 len_passdata = strlen(vmu->fullname) * 2 + 3;
1175 passdata2 = alloca(len_passdata);
1176 if (!ast_strlen_zero(vmu->email))
1177 fprintf(p, "To: %s <%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->email);
1179 fprintf(p, "To: %s <%s@%s>\n", mailheader_quote(vmu->fullname, passdata2, len_passdata), vmu->username, vmu->domain);
1181 if (!ast_strlen_zero(template->subject)) {
1183 int vmlen = strlen(template->subject) * 3 + 200;
1184 if ((pass_data = alloca(vmlen))) {
1185 pbx_substitute_variables_helper(ast, template->subject, pass_data, vmlen);
1186 fprintf(p, "Subject: %s\n", pass_data);
1188 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1193 ast_debug(4, "-_-_- Subject now: %s\n", pass_data);
1196 fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1197 ast_debug(1, "-_-_- Using default subject for this email \n");
1201 if (option_debug > 2)
1202 fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1203 fprintf(p, "MIME-Version: 1.0\n");
1205 /* Something unique. */
1206 snprintf(bound, sizeof(bound), "voicemail_%s%d%d", vmu->username, (int)getpid(), (unsigned int)rand());
1208 fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1210 fprintf(p, "--%s\n", bound);
1211 fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset);
1212 if (!ast_strlen_zero(template->body)) {
1214 int vmlen = strlen(template->body)*3 + 200;
1215 if ((pass_data = alloca(vmlen))) {
1216 pbx_substitute_variables_helper(ast, template->body, pass_data, vmlen);
1217 ast_debug(3, "Message now: %s\n-----\n", pass_data);
1218 fprintf(p, "%s\n", pass_data);
1220 ast_log(LOG_WARNING, "Cannot allocate workspace for variable substitution\n");
1222 fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1224 "in mailbox %s from %s, on %s so you might\n"
1225 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1226 dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1227 ast_debug(3, "Using default message body (no template)\n-----\n");
1229 /* Eww. We want formats to tell us their own MIME type */
1230 if (template->attachment) {
1231 char *ctype = "audio/x-";
1232 ast_debug(3, "-_-_- Attaching file to message: %s\n", fname);
1233 if (!strcasecmp(format, "ogg"))
1234 ctype = "application/";
1236 fprintf(p, "--%s\n", bound);
1237 fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1238 fprintf(p, "Content-Transfer-Encoding: base64\n");
1239 fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1240 fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1242 base_encode(fname, p);
1243 fprintf(p, "\n\n--%s--\n.\n", bound);
1246 snprintf(tmp2, sizeof(tmp2), "( %s < %s ; rm -f %s ) &", global_mailcmd, tmp, tmp);
1247 ast_safe_system(tmp2);
1248 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu->email, global_mailcmd, template->attachment ? "(media attachment)" : "");
1249 ast_debug(3, "-_-_- Actual command used: %s\n", tmp2);
1251 ast_channel_free(ast);
1255 /*! \brief Create directory based on components */
1256 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1258 return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1261 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1262 * \param dest String. base directory.
1263 * \param len Int. Length base directory string.
1264 * \param domain String. Ignored if is null or empty string.
1265 * \param username String. Ignored if is null or empty string.
1266 * \param folder String. Ignored if is null or empty string.
1267 * \return 0 on failure, 1 on success.
1269 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1271 struct stat filestat;
1272 make_dir(dest, len, domain, username, folder ? folder : "");
1273 if (stat(dest, &filestat)== -1)
1279 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1280 * \param dest String. base directory.
1281 * \param len Length of directory string
1282 * \param domain String. Ignored if is null or empty string.
1283 * \param folder String. Ignored if is null or empty string.
1284 * \param username String. Ignored if is null or empty string.
1285 * \return -1 on failure, 0 on success.
1287 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1290 make_dir(dest, len, domain, username, folder);
1291 if ((res = ast_mkdir(dest, 0777))) {
1292 ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1295 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1300 /*! \brief Play intro message before recording voicemail
1302 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1307 ast_debug(2, "-_-_- Still preparing to play message ...\n");
1309 snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1311 if (ast_fileexists(fn, NULL, NULL) > 0) {
1312 res = ast_streamfile(chan, fn, chan->language);
1315 res = ast_waitstream(chan, ecodes);
1319 int numericusername = 1;
1322 ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n");
1325 ast_debug(2, "-_-_- Numeric? Checking %c\n", *i);
1327 numericusername = FALSE;
1333 if (numericusername) {
1334 if(ast_streamfile(chan, "vm-theperson", chan->language))
1336 if ((res = ast_waitstream(chan, ecodes)))
1339 res = ast_say_digit_str(chan, username, ecodes, chan->language);
1343 if(ast_streamfile(chan, "vm-theextensionis", chan->language))
1345 if ((res = ast_waitstream(chan, ecodes)))
1350 res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", chan->language);
1353 res = ast_waitstream(chan, ecodes);
1357 /*! \brief Delete media files and attribute file */
1358 static int vm_delete(char *file)
1362 ast_debug(1, "-_-_- Deleting voicemail file %s\n", file);
1364 res = unlink(file); /* Remove the meta data file */
1365 res |= ast_filedelete(file, NULL); /* remove the media file */
1370 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1371 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1372 int outsidecaller, struct minivm_account *vmu, int *duration, const char *unlockdir,
1373 signed char record_gain)
1376 int max_attempts = 3;
1379 int message_exists = 0;
1380 signed char zero_gain = 0;
1381 char *acceptdtmf = "#";
1382 char *canceldtmf = "";
1384 /* Note that urgent and private are for flagging messages as such in the future */
1386 /* barf if no pointer passed to store duration in */
1387 if (duration == NULL) {
1388 ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1392 cmd = '3'; /* Want to start by recording */
1394 while ((cmd >= 0) && (cmd != 't')) {
1398 ast_verb(3, "Reviewing the message\n");
1399 ast_streamfile(chan, recordfile, chan->language);
1400 cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1406 ast_verb(3, "Re-recording the message\n");
1408 ast_verb(3, "Recording the message\n");
1409 if (recorded && outsidecaller)
1410 cmd = ast_play_and_wait(chan, "beep");
1412 /* 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 */
1414 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1415 if (ast_test_flag(vmu, MVM_OPERATOR))
1417 cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf);
1419 ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1420 if (cmd == -1) /* User has hung up, no options to give */
1424 else if (cmd == '*')
1427 /* If all is well, a message exists */
1440 cmd = ast_play_and_wait(chan, "vm-sorry");
1443 if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1444 cmd = ast_play_and_wait(chan, "vm-sorry");
1447 if (message_exists || recorded) {
1448 cmd = ast_play_and_wait(chan, "vm-saveoper");
1450 cmd = ast_waitfordigit(chan, 3000);
1452 ast_play_and_wait(chan, "vm-msgsaved");
1455 ast_play_and_wait(chan, "vm-deleted");
1456 vm_delete(recordfile);
1462 /* If the caller is an ouside caller, and the review option is enabled,
1463 allow them to review the message, but let the owner of the box review
1465 if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1467 if (message_exists) {
1468 cmd = ast_play_and_wait(chan, "vm-review");
1470 cmd = ast_play_and_wait(chan, "vm-torerecord");
1472 cmd = ast_waitfordigit(chan, 600);
1475 if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1476 cmd = ast_play_and_wait(chan, "vm-reachoper");
1478 cmd = ast_waitfordigit(chan, 600);
1481 cmd = ast_waitfordigit(chan, 6000);
1485 if (attempts > max_attempts) {
1491 ast_play_and_wait(chan, "vm-goodbye");
1497 /*! \brief Run external notification for voicemail message */
1498 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1500 char arguments[BUFSIZ];
1502 if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify))
1505 snprintf(arguments, sizeof(arguments), "%s %s@%s %s %s&",
1506 ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify,
1507 vmu->username, vmu->domain,
1508 chan->cid.cid_name, chan->cid.cid_num);
1510 ast_debug(1, "Executing: %s\n", arguments);
1511 ast_safe_system(arguments);
1514 /*! \brief Send message to voicemail account owner */
1515 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)
1518 struct minivm_template *etemplate;
1519 char *messageformat;
1521 char oldlocale[100];
1522 const char *counter;
1524 if (!ast_strlen_zero(vmu->attachfmt)) {
1525 if (strstr(format, vmu->attachfmt)) {
1526 format = vmu->attachfmt;
1528 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);
1531 etemplate = message_template_find(vmu->etemplate);
1533 etemplate = message_template_find(templatename);
1535 etemplate = message_template_find("email-default");
1537 /* Attach only the first format */
1538 stringp = messageformat = ast_strdupa(format);
1539 strsep(&stringp, "|");
1541 if (!ast_strlen_zero(etemplate->locale)) {
1543 ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1544 ast_debug(2, "-_-_- Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1545 new_locale = setlocale(LC_TIME, etemplate->locale);
1546 if (new_locale == NULL) {
1547 ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1553 /* Read counter if available */
1554 ast_channel_lock(chan);
1555 if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1556 counter = ast_strdupa(counter);
1558 ast_channel_unlock(chan);
1560 if (ast_strlen_zero(counter)) {
1561 ast_debug(2, "-_-_- MVM_COUNTER not found\n");
1563 ast_debug(2, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter);
1566 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1568 if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1569 /* Find template for paging */
1570 etemplate = message_template_find(vmu->ptemplate);
1572 etemplate = message_template_find("pager-default");
1573 if (etemplate->locale) {
1574 ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1575 setlocale(LC_TIME, etemplate->locale);
1578 res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1581 manager_event(EVENT_FLAG_CALL, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu->username, vmu->domain, counter);
1583 run_externnotify(chan, vmu); /* Run external notification */
1585 if (etemplate->locale)
1586 setlocale(LC_TIME, oldlocale); /* Rest to old locale */
1591 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1592 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1594 char tmptxtfile[PATH_MAX];
1597 int res = 0, txtdes;
1601 char tmpdir[PATH_MAX];
1602 char ext_context[256] = "";
1606 struct minivm_account *vmu;
1609 ast_copy_string(tmp, username, sizeof(tmp));
1611 domain = strchr(tmp, '@');
1617 if (!(vmu = find_account(domain, username, TRUE))) {
1618 /* We could not find user, let's exit */
1619 ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1620 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1624 /* Setup pre-file if appropriate */
1625 if (strcmp(vmu->domain, "localhost"))
1626 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1628 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1630 /* The meat of recording the message... All the announcements and beeps have been played*/
1631 if (ast_strlen_zero(vmu->attachfmt))
1632 ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1634 ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1636 if (ast_strlen_zero(fmt)) {
1637 ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1638 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1643 userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1645 /* If we have no user directory, use generic temporary directory */
1647 create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1648 ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1652 snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1655 /* XXX This file needs to be in temp directory */
1656 txtdes = mkstemp(tmptxtfile);
1658 ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1659 res = ast_streamfile(chan, "vm-mailboxfull", chan->language);
1661 res = ast_waitstream(chan, "");
1662 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1667 /* Unless we're *really* silent, try to send the beep */
1668 res = ast_streamfile(chan, "beep", chan->language);
1670 res = ast_waitstream(chan, "");
1673 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1674 /* Store information */
1675 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1677 res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, NULL, options->record_gain);
1679 txt = fdopen(txtdes, "w+");
1681 ast_log(LOG_WARNING, "Error opening text file for output\n");
1684 struct timeval now = ast_tvnow();
1686 char logbuf[BUFSIZ];
1687 get_date(date, sizeof(date));
1688 ast_localtime(&now, &tm, NULL);
1689 ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1691 snprintf(logbuf, sizeof(logbuf),
1692 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1693 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1700 ast_callerid_merge(callerid, sizeof(callerid), chan->cid.cid_name, chan->cid.cid_num, "Unknown"),
1704 duration < global_vmminmessage ? "IGNORED" : "OK",
1707 fprintf(txt, "%s", logbuf);
1708 if (minivmlogfile) {
1709 ast_mutex_lock(&minivmloglock);
1710 fprintf(minivmlogfile, "%s", logbuf);
1711 ast_mutex_unlock(&minivmloglock);
1714 if (duration < global_vmminmessage) {
1715 ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration, global_vmminmessage);
1717 ast_filedelete(tmptxtfile, NULL);
1719 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1722 fclose(txt); /* Close log file */
1723 if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1724 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1726 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1727 if(ast_test_flag(vmu, MVM_ALLOCED))
1732 /* Set channel variables for the notify application */
1733 pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1734 snprintf(timebuf, sizeof(timebuf), "%d", duration);
1735 pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1736 pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1739 global_stats.lastreceived = ast_tvnow();
1740 global_stats.receivedmessages++;
1741 // /* Go ahead and delete audio files from system, they're not needed any more */
1742 // if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1743 // ast_filedelete(tmptxtfile, NULL);
1744 // /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1745 // ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1751 if(ast_test_flag(vmu, MVM_ALLOCED))
1754 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1758 /*! \brief Queue a message waiting event */
1759 static void queue_mwi_event(const char *mbx, const char *ctx, int urgent, int new, int old)
1761 struct ast_event *event;
1762 char *mailbox, *context;
1764 mailbox = ast_strdupa(mbx);
1765 context = ast_strdupa(ctx);
1766 if (ast_strlen_zero(context)) {
1767 context = "default";
1770 if (!(event = ast_event_new(AST_EVENT_MWI,
1771 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR, mailbox,
1772 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR, context,
1773 AST_EVENT_IE_NEWMSGS, AST_EVENT_IE_PLTYPE_UINT, (new+urgent),
1774 AST_EVENT_IE_OLDMSGS, AST_EVENT_IE_PLTYPE_UINT, old,
1775 AST_EVENT_IE_END))) {
1779 ast_event_queue_and_cache(event,
1780 AST_EVENT_IE_MAILBOX, AST_EVENT_IE_PLTYPE_STR,
1781 AST_EVENT_IE_CONTEXT, AST_EVENT_IE_PLTYPE_STR,
1785 /*! \brief Send MWI using interal Asterisk event subsystem */
1786 static int minivm_mwi_exec(struct ast_channel *chan, void *data)
1795 if (ast_strlen_zero(data)) {
1796 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1799 tmpptr = ast_strdupa((char *)data);
1801 ast_log(LOG_ERROR, "Out of memory\n");
1804 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1806 ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
1809 ast_copy_string(tmp, argv[0], sizeof(tmp));
1811 domain = strchr(tmp, '@');
1816 if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
1817 ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
1820 queue_mwi_event(mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
1826 /*! \brief Notify voicemail account owners - either generic template or user specific */
1827 static int minivm_notify_exec(struct ast_channel *chan, void *data)
1835 struct minivm_account *vmu;
1836 char *username = argv[0];
1837 const char *template = "";
1838 const char *filename;
1840 const char *duration_string;
1842 if (ast_strlen_zero(data)) {
1843 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1846 tmpptr = ast_strdupa((char *)data);
1848 ast_log(LOG_ERROR, "Out of memory\n");
1851 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1853 if (argc == 2 && !ast_strlen_zero(argv[1]))
1856 ast_copy_string(tmp, argv[0], sizeof(tmp));
1858 domain = strchr(tmp, '@');
1863 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
1864 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
1868 if(!(vmu = find_account(domain, username, TRUE))) {
1869 /* We could not find user, let's exit */
1870 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
1871 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
1875 ast_channel_lock(chan);
1876 if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
1877 filename = ast_strdupa(filename);
1879 ast_channel_unlock(chan);
1880 /* Notify of new message to e-mail and pager */
1881 if (!ast_strlen_zero(filename)) {
1882 ast_channel_lock(chan);
1883 if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
1884 format = ast_strdupa(format);
1886 if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
1887 duration_string = ast_strdupa(duration_string);
1889 ast_channel_unlock(chan);
1890 res = notify_new_message(chan, template, vmu, filename, atoi(duration_string), format, chan->cid.cid_num, chan->cid.cid_name);
1893 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
1896 if(ast_test_flag(vmu, MVM_ALLOCED))
1899 /* Ok, we're ready to rock and roll. Return to dialplan */
1905 /*! \brief Dialplan function to record voicemail */
1906 static int minivm_record_exec(struct ast_channel *chan, void *data)
1910 struct leave_vm_options leave_options;
1913 struct ast_flags flags = { 0 };
1914 char *opts[OPT_ARG_ARRAY_SIZE];
1916 memset(&leave_options, 0, sizeof(leave_options));
1918 /* Answer channel if it's not already answered */
1919 if (chan->_state != AST_STATE_UP)
1922 if (ast_strlen_zero(data)) {
1923 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1926 tmp = ast_strdupa((char *)data);
1928 ast_log(LOG_ERROR, "Out of memory\n");
1931 argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
1933 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
1936 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
1937 if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
1940 if (sscanf(opts[OPT_ARG_RECORDGAIN], "%d", &gain) != 1) {
1941 ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
1944 leave_options.record_gain = (signed char) gain;
1948 /* Now run the appliation and good luck to you! */
1949 res = leave_voicemail(chan, argv[0], &leave_options);
1951 if (res == ERROR_LOCK_PATH) {
1952 ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
1953 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "FAILED");
1956 pbx_builtin_setvar_helper(chan, "MINIVM_RECORD_STATUS", "SUCCESS");
1961 /*! \brief Play voicemail prompts - either generic or user specific */
1962 static int minivm_greet_exec(struct ast_channel *chan, void *data)
1964 struct leave_vm_options leave_options = { 0, '\0'};
1967 struct ast_flags flags = { 0 };
1968 char *opts[OPT_ARG_ARRAY_SIZE];
1974 char dest[PATH_MAX];
1975 char prefile[PATH_MAX];
1976 char tempfile[PATH_MAX] = "";
1977 char ext_context[256] = "";
1979 char ecodes[16] = "#";
1981 struct minivm_account *vmu;
1982 char *username = argv[0];
1984 if (ast_strlen_zero(data)) {
1985 ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1988 tmpptr = ast_strdupa((char *)data);
1990 ast_log(LOG_ERROR, "Out of memory\n");
1993 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1996 if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
1998 ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2001 ast_copy_string(tmp, argv[0], sizeof(tmp));
2003 domain = strchr(tmp, '@');
2008 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2009 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
2012 ast_debug(1, "-_-_- Trying to find configuration for user %s in domain %s\n", username, domain);
2014 if (!(vmu = find_account(domain, username, TRUE))) {
2015 ast_log(LOG_ERROR, "Could not allocate memory. \n");
2019 /* Answer channel if it's not already answered */
2020 if (chan->_state != AST_STATE_UP)
2023 /* Setup pre-file if appropriate */
2024 if (strcmp(vmu->domain, "localhost"))
2025 snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2027 ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2029 if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2030 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2032 snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2033 } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2034 res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2036 snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2038 /* Check for temporary greeting - it overrides busy and unavail */
2039 snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2040 if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2041 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2042 ast_copy_string(prefile, tempfile, sizeof(prefile));
2044 ast_debug(2, "-_-_- Preparing to play message ...\n");
2046 /* Check current or macro-calling context for special extensions */
2047 if (ast_test_flag(vmu, MVM_OPERATOR)) {
2048 if (!ast_strlen_zero(vmu->exit)) {
2049 if (ast_exists_extension(chan, vmu->exit, "o", 1, chan->cid.cid_num)) {
2050 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2053 } else if (ast_exists_extension(chan, chan->context, "o", 1, chan->cid.cid_num)) {
2054 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2057 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "o", 1, chan->cid.cid_num)) {
2058 strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2063 if (!ast_strlen_zero(vmu->exit)) {
2064 if (ast_exists_extension(chan, vmu->exit, "a", 1, chan->cid.cid_num))
2065 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2066 } else if (ast_exists_extension(chan, chan->context, "a", 1, chan->cid.cid_num))
2067 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2068 else if (!ast_strlen_zero(chan->macrocontext) && ast_exists_extension(chan, chan->macrocontext, "a", 1, chan->cid.cid_num)) {
2069 strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2073 res = 0; /* Reset */
2074 /* Play the beginning intro if desired */
2075 if (!ast_strlen_zero(prefile)) {
2076 if (ast_streamfile(chan, prefile, chan->language) > -1)
2077 res = ast_waitstream(chan, ecodes);
2079 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2080 res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2083 ast_debug(2, "Hang up during prefile playback\n");
2084 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
2085 if(ast_test_flag(vmu, MVM_ALLOCED))
2090 /* On a '#' we skip the instructions */
2091 ast_set_flag(&leave_options, OPT_SILENT);
2094 if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2095 res = ast_streamfile(chan, SOUND_INTRO, chan->language);
2097 res = ast_waitstream(chan, ecodes);
2099 ast_set_flag(&leave_options, OPT_SILENT);
2104 ast_stopstream(chan);
2105 /* Check for a '*' here in case the caller wants to escape from voicemail to something
2106 other than the operator -- an automated attendant or mailbox login for example */
2108 chan->exten[0] = 'a';
2109 chan->exten[1] = '\0';
2110 if (!ast_strlen_zero(vmu->exit)) {
2111 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2112 } else if (ausemacro && !ast_strlen_zero(chan->macrocontext)) {
2113 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2116 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
2118 } else if (res == '0') { /* Check for a '0' here */
2119 if(ouseexten || ousemacro) {
2120 chan->exten[0] = 'o';
2121 chan->exten[1] = '\0';
2122 if (!ast_strlen_zero(vmu->exit)) {
2123 ast_copy_string(chan->context, vmu->exit, sizeof(chan->context));
2124 } else if (ousemacro && !ast_strlen_zero(chan->macrocontext)) {
2125 ast_copy_string(chan->context, chan->macrocontext, sizeof(chan->context));
2127 ast_play_and_wait(chan, "transfer");
2129 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "USEREXIT");
2132 } else if (res < 0) {
2133 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "FAILED");
2136 pbx_builtin_setvar_helper(chan, "MINIVM_GREET_STATUS", "SUCCESS");
2138 if(ast_test_flag(vmu, MVM_ALLOCED))
2142 /* Ok, we're ready to rock and roll. Return to dialplan */
2147 /*! \brief Dialplan application to delete voicemail */
2148 static int minivm_delete_exec(struct ast_channel *chan, void *data)
2151 char filename[BUFSIZ];
2153 if (!ast_strlen_zero(data)) {
2154 ast_copy_string(filename, (char *) data, sizeof(filename));
2156 ast_channel_lock(chan);
2157 ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2158 ast_channel_unlock(chan);
2161 if (ast_strlen_zero(filename)) {
2162 ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2166 /* Go ahead and delete audio files from system, they're not needed any more */
2167 /* We should look for both audio and text files here */
2168 if (ast_fileexists(filename, NULL, NULL) > 0) {
2169 res = vm_delete(filename);
2171 ast_debug(2, "-_-_- Can't delete file: %s\n", filename);
2172 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2174 ast_debug(2, "-_-_- Deleted voicemail file :: %s \n", filename);
2175 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "SUCCESS");
2178 ast_debug(2, "-_-_- Filename does not exist: %s\n", filename);
2179 pbx_builtin_setvar_helper(chan, "MINIVM_DELETE_STATUS", "FAILED");
2185 /*! \brief Record specific messages for voicemail account */
2186 static int minivm_accmess_exec(struct ast_channel *chan, void *data)
2191 char filename[PATH_MAX];
2194 char *tmpptr = NULL;
2195 struct minivm_account *vmu;
2196 char *username = argv[0];
2197 struct ast_flags flags = { 0 };
2198 char *opts[OPT_ARG_ARRAY_SIZE];
2200 char *message = NULL;
2201 char *prompt = NULL;
2205 if (ast_strlen_zero(data)) {
2206 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2209 tmpptr = ast_strdupa((char *)data);
2212 ast_log(LOG_ERROR, "Out of memory\n");
2215 argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2219 ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2222 if (!error && strlen(argv[1]) > 1) {
2223 ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2227 if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2228 ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2235 ast_copy_string(tmp, argv[0], sizeof(tmp));
2237 domain = strchr(tmp, '@');
2242 if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2243 ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2247 if(!(vmu = find_account(domain, username, TRUE))) {
2248 /* We could not find user, let's exit */
2249 ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2250 pbx_builtin_setvar_helper(chan, "MINIVM_NOTIFY_STATUS", "FAILED");
2254 /* Answer channel if it's not already answered */
2255 if (chan->_state != AST_STATE_UP)
2258 /* Here's where the action is */
2259 if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2261 prompt = "vm-rec-busy";
2262 } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2263 message = "unavailable";
2264 prompt = "vm-rec-unavail";
2265 } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2267 prompt = "vm-temp-greeting";
2268 } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2270 prompt = "vm-rec-name";
2272 snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2273 /* Maybe we should check the result of play_record_review ? */
2274 cmd = play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, FALSE);
2276 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2278 if(ast_test_flag(vmu, MVM_ALLOCED))
2282 /* Ok, we're ready to rock and roll. Return to dialplan */
2287 /*! \brief Append new mailbox to mailbox list from configuration file */
2288 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2290 struct minivm_account *vmu;
2293 char accbuf[BUFSIZ];
2295 ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2297 ast_copy_string(accbuf, name, sizeof(accbuf));
2299 domain = strchr(accbuf, '@');
2304 if (ast_strlen_zero(domain)) {
2305 ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2309 ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2311 /* Allocate user account */
2312 vmu = ast_calloc(1, sizeof(*vmu));
2316 ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2317 ast_copy_string(vmu->username, username, sizeof(vmu->username));
2319 populate_defaults(vmu);
2321 ast_debug(3, "...Configuring account %s\n", name);
2324 ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2325 if (!strcasecmp(var->name, "serveremail")) {
2326 ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2327 } else if (!strcasecmp(var->name, "email")) {
2328 ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2329 } else if (!strcasecmp(var->name, "accountcode")) {
2330 ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2331 } else if (!strcasecmp(var->name, "pincode")) {
2332 ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2333 } else if (!strcasecmp(var->name, "domain")) {
2334 ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2335 } else if (!strcasecmp(var->name, "language")) {
2336 ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2337 } else if (!strcasecmp(var->name, "timezone")) {
2338 ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2339 } else if (!strcasecmp(var->name, "externnotify")) {
2340 ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2341 } else if (!strcasecmp(var->name, "etemplate")) {
2342 ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2343 } else if (!strcasecmp(var->name, "ptemplate")) {
2344 ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2345 } else if (!strcasecmp(var->name, "fullname")) {
2346 ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2347 } else if (!strcasecmp(var->name, "setvar")) {
2349 char *varname = ast_strdupa(var->value);
2350 struct ast_variable *tmpvar;
2352 if (varname && (varval = strchr(varname, '='))) {
2355 if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2356 tmpvar->next = vmu->chanvars;
2357 vmu->chanvars = tmpvar;
2360 } else if (!strcasecmp(var->name, "pager")) {
2361 ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2362 } else if (!strcasecmp(var->name, "volgain")) {
2363 sscanf(var->value, "%lf", &vmu->volgain);
2365 ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2369 ast_debug(3, "...Linking account %s\n", name);
2371 AST_LIST_LOCK(&minivm_accounts);
2372 AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2373 AST_LIST_UNLOCK(&minivm_accounts);
2375 global_stats.voicemailaccounts++;
2377 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)" : "");
2381 /*! \brief Free Mini Voicemail timezone */
2382 static void free_zone(struct minivm_zone *z)
2387 /*! \brief Clear list of timezones */
2388 static void timezone_destroy_list(void)
2390 struct minivm_zone *this;
2392 AST_LIST_LOCK(&minivm_zones);
2393 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2396 AST_LIST_UNLOCK(&minivm_zones);
2399 /*! \brief Add time zone to memory list */
2400 static int timezone_add(const char *zonename, const char *config)
2402 struct minivm_zone *newzone;
2403 char *msg_format, *timezone_str;
2405 newzone = ast_calloc(1, sizeof(*newzone));
2406 if (newzone == NULL)
2409 msg_format = ast_strdupa(config);
2410 if (msg_format == NULL) {
2411 ast_log(LOG_WARNING, "Out of memory.\n");
2416 timezone_str = strsep(&msg_format, "|");
2418 ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2423 ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2424 ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2425 ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2427 AST_LIST_LOCK(&minivm_zones);
2428 AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2429 AST_LIST_UNLOCK(&minivm_zones);
2431 global_stats.timezones++;
2436 /*! \brief Read message template from file */
2437 static char *message_template_parse_filebody(const char *filename) {
2438 char buf[BUFSIZ * 6];
2439 char readbuf[BUFSIZ];
2440 char filenamebuf[BUFSIZ];
2446 if (ast_strlen_zero(filename))
2448 if (*filename == '/')
2449 ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2451 snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2453 if (!(fi = fopen(filenamebuf, "r"))) {
2454 ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2458 while (fgets(readbuf, sizeof(readbuf), fi)) {
2460 if (writepos != buf) {
2461 *writepos = '\n'; /* Replace EOL with new line */
2464 ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2465 writepos += strlen(readbuf) - 1;
2468 messagebody = ast_calloc(1, strlen(buf + 1));
2469 ast_copy_string(messagebody, buf, strlen(buf) + 1);
2470 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2471 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2476 /*! \brief Parse emailbody template from configuration file */
2477 static char *message_template_parse_emailbody(const char *configuration)
2479 char *tmpread, *tmpwrite;
2480 char *emailbody = ast_strdup(configuration);
2482 /* substitute strings \t and \n into the apropriate characters */
2483 tmpread = tmpwrite = emailbody;
2484 while ((tmpwrite = strchr(tmpread,'\\'))) {
2485 int len = strlen("\n");
2486 switch (tmpwrite[1]) {
2488 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2489 strncpy(tmpwrite, "\n", len);
2492 memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2493 strncpy(tmpwrite, "\t", len);
2496 ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2498 tmpread = tmpwrite + len;
2503 /*! \brief Apply general configuration options */
2504 static int apply_general_options(struct ast_variable *var)
2510 if (!strcmp(var->name, "mailcmd")) {
2511 ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2512 } else if (!strcmp(var->name, "maxgreet")) {
2513 global_maxgreet = atoi(var->value);
2514 } else if (!strcmp(var->name, "maxsilence")) {
2515 global_maxsilence = atoi(var->value);
2516 if (global_maxsilence > 0)
2517 global_maxsilence *= 1000;
2518 } else if (!strcmp(var->name, "logfile")) {
2519 if (!ast_strlen_zero(var->value) ) {
2520 if(*(var->value) == '/')
2521 ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2523 snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2525 } else if (!strcmp(var->name, "externnotify")) {
2526 /* External voicemail notify application */
2527 ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2528 } else if (!strcmp(var->name, "silencetreshold")) {
2529 /* Silence treshold */
2530 global_silencethreshold = atoi(var->value);
2531 } else if (!strcmp(var->name, "maxmessage")) {
2533 if (sscanf(var->value, "%d", &x) == 1) {
2534 global_vmmaxmessage = x;
2537 ast_log(LOG_WARNING, "Invalid max message time length\n");
2539 } else if (!strcmp(var->name, "minmessage")) {
2541 if (sscanf(var->value, "%d", &x) == 1) {
2542 global_vmminmessage = x;
2543 if (global_maxsilence <= global_vmminmessage)
2544 ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2547 ast_log(LOG_WARNING, "Invalid min message time length\n");
2549 } else if (!strcmp(var->name, "format")) {
2550 ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2551 } else if (!strcmp(var->name, "review")) {
2552 ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2553 } else if (!strcmp(var->name, "operator")) {
2554 ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2561 /*! \brief Load minivoicemail configuration */
2562 static int load_config(int reload)
2564 struct ast_config *cfg;
2565 struct ast_variable *var;
2567 const char *chanvar;
2569 struct minivm_template *template;
2570 struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2572 cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2573 if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2575 } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2576 ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
2580 ast_mutex_lock(&minivmlock);
2582 /* Destroy lists to reconfigure */
2583 message_destroy_list(); /* Destroy list of voicemail message templates */
2584 timezone_destroy_list(); /* Destroy list of timezones */
2585 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2586 ast_debug(2, "Destroyed memory objects...\n");
2588 /* First, set some default settings */
2589 global_externnotify[0] = '\0';
2590 global_logfile[0] = '\0';
2591 global_vmmaxmessage = 2000;
2592 global_maxgreet = 2000;
2593 global_vmminmessage = 0;
2594 strcpy(global_mailcmd, SENDMAIL);
2595 global_maxsilence = 0;
2596 global_saydurationminfo = 2;
2597 ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2598 ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2599 ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2600 strcpy(global_charset, "ISO-8859-1");
2601 /* Reset statistics */
2602 memset(&global_stats, 0, sizeof(global_stats));
2603 global_stats.reset = ast_tvnow();
2605 global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2607 /* Make sure we could load configuration file */
2609 ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2610 ast_mutex_unlock(&minivmlock);
2614 ast_debug(2, "-_-_- Loaded configuration file, now parsing\n");
2616 /* General settings */
2618 cat = ast_category_browse(cfg, NULL);
2620 ast_debug(3, "-_-_- Found configuration section [%s]\n", cat);
2621 if (!strcasecmp(cat, "general")) {
2622 /* Nothing right now */
2623 error += apply_general_options(ast_variable_browse(cfg, cat));
2624 } else if (!strncasecmp(cat, "template-", 9)) {
2626 char *name = cat + 9;
2628 /* Now build and link template to list */
2629 error += message_template_build(name, ast_variable_browse(cfg, cat));
2631 var = ast_variable_browse(cfg, cat);
2632 if (!strcasecmp(cat, "zonemessages")) {
2633 /* Timezones in this context */
2635 timezone_add(var->name, var->value);
2639 /* Create mailbox from this */
2640 error += create_vmaccount(cat, var, FALSE);
2643 /* Find next section in configuration file */
2644 cat = ast_category_browse(cfg, cat);
2647 /* Configure the default email template */
2648 message_template_build("email-default", NULL);
2649 template = message_template_find("email-default");
2651 /* Load date format config for voicemail mail */
2652 if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2653 ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2654 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2655 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2656 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2657 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2658 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2659 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2660 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2661 ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2662 if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2663 template->body = message_template_parse_emailbody(chanvar);
2664 template->attachment = TRUE;
2666 message_template_build("pager-default", NULL);
2667 template = message_template_find("pager-default");
2668 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2669 ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2670 if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2671 ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2672 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2673 ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2674 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2675 ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2676 if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2677 template->body = message_template_parse_emailbody(chanvar);
2678 template->attachment = FALSE;
2681 ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2683 ast_mutex_unlock(&minivmlock);
2684 ast_config_destroy(cfg);
2686 /* Close log file if it's open and disabled */
2688 fclose(minivmlogfile);
2690 /* Open log file if it's enabled */
2691 if(!ast_strlen_zero(global_logfile)) {
2692 minivmlogfile = fopen(global_logfile, "a");
2694 ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2696 ast_debug(3, "-_-_- Opened log file %s \n", global_logfile);
2702 /*! \brief CLI routine for listing templates */
2703 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2705 struct minivm_template *this;
2706 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2711 e->command = "minivm list templates";
2713 "Usage: minivm list templates\n"
2714 " Lists message templates for e-mail, paging and IM\n";
2721 return CLI_SHOWUSAGE;
2723 AST_LIST_LOCK(&message_templates);
2724 if (AST_LIST_EMPTY(&message_templates)) {
2725 ast_cli(a->fd, "There are no message templates defined\n");
2726 AST_LIST_UNLOCK(&message_templates);
2729 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2730 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2731 AST_LIST_TRAVERSE(&message_templates, this, list) {
2732 ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
2733 this->charset ? this->charset : "-",
2734 this->locale ? this->locale : "-",
2735 this->attachment ? "Yes" : "No",
2736 this->subject ? this->subject : "-");
2739 AST_LIST_UNLOCK(&message_templates);
2740 ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2744 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2748 struct minivm_account *vmu;
2749 const char *domain = "";
2751 /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2755 return (state == 0) ? ast_strdup("for") : NULL;
2756 wordlen = strlen(word);
2757 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2758 if (!strncasecmp(word, vmu->domain, wordlen)) {
2759 if (domain && strcmp(domain, vmu->domain) && ++which > state)
2760 return ast_strdup(vmu->domain);
2761 /* ignore repeated domains ? */
2762 domain = vmu->domain;
2768 /*! \brief CLI command to list voicemail accounts */
2769 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2771 struct minivm_account *vmu;
2772 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
2777 e->command = "minivm list accounts";
2779 "Usage: minivm list accounts\n"
2780 " Lists all mailboxes currently set up\n";
2783 return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
2786 if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
2787 return CLI_SHOWUSAGE;
2788 if ((a->argc == 5) && strcmp(a->argv[3],"for"))
2789 return CLI_SHOWUSAGE;
2791 AST_LIST_LOCK(&minivm_accounts);
2792 if (AST_LIST_EMPTY(&minivm_accounts)) {
2793 ast_cli(a->fd, "There are no voicemail users currently defined\n");
2794 AST_LIST_UNLOCK(&minivm_accounts);
2797 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2798 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
2799 AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2801 if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
2803 snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2804 ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, vmu->etemplate ? vmu->etemplate : "-",
2805 vmu->ptemplate ? vmu->ptemplate : "-",
2806 vmu->zonetag ? vmu->zonetag : "-",
2807 vmu->attachfmt ? vmu->attachfmt : "-",
2811 AST_LIST_UNLOCK(&minivm_accounts);
2812 ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
2816 /*! \brief Show a list of voicemail zones in the CLI */
2817 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2819 struct minivm_zone *zone;
2820 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
2821 char *res = CLI_SUCCESS;
2825 e->command = "minivm list zones";
2827 "Usage: minivm list zones\n"
2828 " Lists zone message formats\n";
2834 if (a->argc != e->args)
2835 return CLI_SHOWUSAGE;
2837 AST_LIST_LOCK(&minivm_zones);
2838 if (!AST_LIST_EMPTY(&minivm_zones)) {
2839 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
2840 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
2841 AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2842 ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
2845 ast_cli(a->fd, "There are no voicemail zones currently defined\n");
2848 AST_LIST_UNLOCK(&minivm_zones);
2853 /*! \brief CLI Show settings */
2854 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2858 e->command = "minivm show settings";
2860 "Usage: minivm show settings\n"
2861 " Display Mini-Voicemail general settings\n";
2867 ast_cli(a->fd, "* Mini-Voicemail general settings\n");
2868 ast_cli(a->fd, " -------------------------------\n");
2869 ast_cli(a->fd, "\n");
2870 ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd);
2871 ast_cli(a->fd, " Max silence: %d\n", global_maxsilence);
2872 ast_cli(a->fd, " Silence threshold: %d\n", global_silencethreshold);
2873 ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage);
2874 ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage);
2875 ast_cli(a->fd, " Default format: %s\n", default_vmformat);
2876 ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify);
2877 ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
2878 ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
2879 ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
2881 ast_cli(a->fd, "\n");
2885 /*! \brief Show stats */
2886 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2888 struct ast_tm timebuf;
2894 e->command = "minivm show stats";
2896 "Usage: minivm show stats\n"
2897 " Display Mini-Voicemail counters\n";
2903 ast_cli(a->fd, "* Mini-Voicemail statistics\n");
2904 ast_cli(a->fd, " -------------------------\n");
2905 ast_cli(a->fd, "\n");
2906 ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts);
2907 ast_cli(a->fd, " Templates: %5d\n", global_stats.templates);
2908 ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones);
2909 if (global_stats.receivedmessages == 0) {
2910 ast_cli(a->fd, " Received messages since last reset: <none>\n");
2912 ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
2913 ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
2914 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
2915 ast_cli(a->fd, " Last received voicemail: %s\n", buf);
2917 ast_localtime(&global_stats.reset, &timebuf, NULL);
2918 ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
2919 ast_cli(a->fd, " Last reset: %s\n", buf);
2921 ast_cli(a->fd, "\n");
2925 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
2926 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
2928 struct minivm_account *vmu;
2929 char *username, *domain, *colname;
2931 if (!(username = ast_strdupa(data))) {
2932 ast_log(LOG_ERROR, "Memory Error!\n");
2936 if ((colname = strchr(username, ':'))) {
2942 if ((domain = strchr(username, '@'))) {
2946 if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
2947 ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
2951 if (!(vmu = find_account(domain, username, TRUE)))
2954 if (!strcasecmp(colname, "hasaccount")) {
2955 ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
2956 } else if (!strcasecmp(colname, "fullname")) {
2957 ast_copy_string(buf, vmu->fullname, len);
2958 } else if (!strcasecmp(colname, "email")) {
2959 if (!ast_strlen_zero(vmu->email))
2960 ast_copy_string(buf, vmu->email, len);
2962 snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
2963 } else if (!strcasecmp(colname, "pager")) {
2964 ast_copy_string(buf, vmu->pager, len);
2965 } else if (!strcasecmp(colname, "etemplate")) {
2966 if (!ast_strlen_zero(vmu->etemplate))
2967 ast_copy_string(buf, vmu->etemplate, len);
2969 ast_copy_string(buf, "email-default", len);
2970 } else if (!strcasecmp(colname, "language")) {
2971 ast_copy_string(buf, vmu->language, len);
2972 } else if (!strcasecmp(colname, "timezone")) {
2973 ast_copy_string(buf, vmu->zonetag, len);
2974 } else if (!strcasecmp(colname, "ptemplate")) {
2975 if (!ast_strlen_zero(vmu->ptemplate))
2976 ast_copy_string(buf, vmu->ptemplate, len);
2978 ast_copy_string(buf, "email-default", len);
2979 } else if (!strcasecmp(colname, "accountcode")) {
2980 ast_copy_string(buf, vmu->accountcode, len);
2981 } else if (!strcasecmp(colname, "pincode")) {
2982 ast_copy_string(buf, vmu->pincode, len);
2983 } else if (!strcasecmp(colname, "path")) {
2984 check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
2985 } else { /* Look in channel variables */
2986 struct ast_variable *var;
2989 for (var = vmu->chanvars ; var ; var = var->next)
2990 if (!strcmp(var->name, colname)) {
2991 ast_copy_string(buf, var->value, len);
2997 if(ast_test_flag(vmu, MVM_ALLOCED))
3003 /*! \brief lock directory
3005 only return failure if ast_lock_path returns 'timeout',
3006 not if the path does not exist or any other reason
3008 static int vm_lock_path(const char *path)
3010 switch (ast_lock_path(path)) {
3011 case AST_LOCK_TIMEOUT:
3018 /*! \brief Access counter file, lock directory, read and possibly write it again changed
3019 \param directory Directory to crate file in
3020 \param countername filename
3021 \param value If set to zero, we only read the variable
3022 \param operand 0 to read, 1 to set new value, 2 to change
3023 \return -1 on error, otherwise counter value
3025 static int access_counter_file(char *directory, char *countername, int value, int operand)
3027 char filename[BUFSIZ];
3028 char readbuf[BUFSIZ];
3030 int old = 0, counter = 0;
3032 /* Lock directory */
3033 if (vm_lock_path(directory)) {
3034 return -1; /* Could not lock directory */
3036 snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
3038 counterfile = fopen(filename, "r");
3040 if(fgets(readbuf, sizeof(readbuf), counterfile)) {
3041 ast_debug(3, "Read this string from counter file: %s\n", readbuf);
3042 old = counter = atoi(readbuf);
3044 fclose(counterfile);
3048 case 0: /* Read only */
3049 ast_unlock_path(directory);
3050 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
3053 case 1: /* Set new value */
3056 case 2: /* Change value */
3058 if (counter < 0) /* Don't allow counters to fall below zero */
3063 /* Now, write the new value to the file */
3064 counterfile = fopen(filename, "w");
3066 ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
3067 ast_unlock_path(directory);
3068 return -1; /* Could not open file for writing */
3070 fprintf(counterfile, "%d\n\n", counter);
3071 fclose(counterfile);
3072 ast_unlock_path(directory);
3073 ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
3077 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
3078 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3080 char *username, *domain, *countername;
3081 struct minivm_account *vmu = NULL;
3082 char userpath[BUFSIZ];
3087 if (!(username = ast_strdupa(data))) { /* Copy indata to local buffer */
3088 ast_log(LOG_WARNING, "Memory error!\n");
3091 if ((countername = strchr(username, ':'))) {
3092 *countername = '\0';
3096 if ((domain = strchr(username, '@'))) {
3101 /* If we have neither username nor domain now, let's give up */
3102 if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3103 ast_log(LOG_ERROR, "No account given\n");