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"
182 #define MVM_REVIEW (1 << 0) /*!< Review message */
183 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
184 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
185 #define MVM_SVMAIL (1 << 3)
186 #define MVM_ENVELOPE (1 << 4)
187 #define MVM_PBXSKIP (1 << 9)
188 #define MVM_ALLOCED (1 << 13)
190 /*! \brief Default mail command to mail voicemail. Change it with the
191 mailcmd= command in voicemail.conf */
192 #define SENDMAIL "/usr/sbin/sendmail -t"
194 #define SOUND_INTRO "vm-intro"
195 #define B64_BASEMAXINLINE 256 /*!< Buffer size for Base 64 attachment encoding */
196 #define B64_BASELINELEN 72 /*!< Line length for Base 64 endoded messages */
199 #define MAX_DATETIME_FORMAT 512
200 #define MAX_NUM_CID_CONTEXTS 10
202 #define ERROR_LOCK_PATH -100
203 #define VOICEMAIL_DIR_MODE 0700
205 #define VOICEMAIL_CONFIG "minivm.conf"
206 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
208 /*! \brief Message types for notification */
209 enum mvm_messagetype
{
212 /* For trunk: MVM_MESSAGE_JABBER, */
215 static char MVM_SPOOL_DIR
[PATH_MAX
];
217 /* Module declarations */
218 static char *app_minivm_record
= "MinivmRecord"; /* Leave a message */
219 static char *app_minivm_greet
= "MinivmGreet"; /* Play voicemail prompts */
220 static char *app_minivm_notify
= "MinivmNotify"; /* Notify about voicemail by using one of several methods */
221 static char *app_minivm_delete
= "MinivmDelete"; /* Notify about voicemail by using one of several methods */
222 static char *app_minivm_accmess
= "MinivmAccMess"; /* Record personal voicemail messages */
224 static char *synopsis_minivm_record
= "Receive Mini-Voicemail and forward via e-mail";
225 static char *descrip_minivm_record
=
226 " MinivmRecord(username@domain[,options]):\n"
227 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
228 "MiniVM records audio file in configured format and forwards message to e-mail and pager.\n"
229 "If there's no user account for that address, a temporary account will\n"
230 "be used with default options.\n"
231 "The recorded file name and path will be stored in MINIVM_FILENAME and the \n"
232 "duration of the message will be stored in MINIVM_DURATION\n"
233 "\nNote: If the caller hangs up after the recording, the only way to send\n"
234 "the message and clean up is to execute in the \"h\" extension.\n"
235 "\nThe application will exit if any of the following DTMF digits are \n"
236 "received and the requested extension exist in the current context.\n"
237 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
238 " * - Jump to the 'a' extension in the current dialplan context.\n"
240 "Result is given in channel variable MINIVM_RECORD_STATUS\n"
241 " The possible values are: SUCCESS | USEREXIT | FAILED\n\n"
243 " g(#) - Use the specified amount of gain when recording the voicemail\n"
244 " message. The units are whole-number decibels (dB).\n"
247 static char *synopsis_minivm_greet
= "Play Mini-Voicemail prompts";
248 static char *descrip_minivm_greet
=
249 " MinivmGreet(username@domain[,options]):\n"
250 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
251 "MinivmGreet() plays default prompts or user specific prompts for an account.\n"
252 "Busy and unavailable messages can be choosen, but will be overridden if a temporary\n"
253 "message exists for the account.\n"
255 "Result is given in channel variable MINIVM_GREET_STATUS\n"
256 " The possible values are: SUCCESS | USEREXIT | FAILED\n\n"
258 " b - Play the 'busy' greeting to the calling party.\n"
259 " s - Skip the playback of instructions for leaving a message to the\n"
261 " u - Play the 'unavailable greeting.\n"
264 static char *synopsis_minivm_notify
= "Notify voicemail owner about new messages.";
265 static char *descrip_minivm_notify
=
266 " MinivmNotify(username@domain[,template]):\n"
267 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
268 "MiniVMnotify forwards messages about new voicemail to e-mail and pager.\n"
269 "If there's no user account for that address, a temporary account will\n"
270 "be used with default options (set in minivm.conf).\n"
271 "The recorded file name and path will be read from MVM_FILENAME and the \n"
272 "duration of the message will be accessed from MVM_DURATION (set by MinivmRecord() )\n"
273 "If the channel variable MVM_COUNTER is set, this will be used in the\n"
274 "message file name and available in the template for the message.\n"
275 "If not template is given, the default email template will be used to send email and\n"
276 "default pager template to send paging message (if the user account is configured with\n"
277 "a paging address.\n"
279 "Result is given in channel variable MINIVM_NOTIFY_STATUS\n"
280 " The possible values are: SUCCESS | FAILED\n"
283 static char *synopsis_minivm_delete
= "Delete Mini-Voicemail voicemail messages";
284 static char *descrip_minivm_delete
=
285 " MinivmDelete(filename):\n"
286 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
287 "It deletes voicemail file set in MVM_FILENAME or given filename.\n"
289 "Result is given in channel variable MINIVM_DELETE_STATUS\n"
290 " The possible values are: SUCCESS | FAILED\n"
291 " FAILED is set if the file does not exist or can't be deleted.\n"
294 static char *synopsis_minivm_accmess
= "Record account specific messages";
295 static char *descrip_minivm_accmess
=
296 " MinivmAccmess(username@domain,option):\n"
297 "This application is part of the Mini-Voicemail system, configured in minivm.conf.\n"
298 "Use this application to record account specific audio/video messages for\n"
299 "busy, unavailable and temporary messages.\n"
300 "Account specific directories will be created if they do not exist.\n"
301 "\nThe option selects message to be recorded:\n"
304 " t Temporary (overrides busy and unavailable)\n"
307 "Result is given in channel variable MINIVM_ACCMESS_STATUS\n"
308 " The possible values are: SUCCESS | FAILED\n"
309 " FAILED is set if the file can't be created.\n"
313 OPT_SILENT
= (1 << 0),
314 OPT_BUSY_GREETING
= (1 << 1),
315 OPT_UNAVAIL_GREETING
= (1 << 2),
316 OPT_TEMP_GREETING
= (1 << 3),
317 OPT_NAME_GREETING
= (1 << 4),
318 OPT_RECORDGAIN
= (1 << 5),
319 } minivm_option_flags
;
322 OPT_ARG_RECORDGAIN
= 0,
323 OPT_ARG_ARRAY_SIZE
= 1,
324 } minivm_option_args
;
326 AST_APP_OPTIONS(minivm_app_options
, {
327 AST_APP_OPTION('s', OPT_SILENT
),
328 AST_APP_OPTION('b', OPT_BUSY_GREETING
),
329 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING
),
330 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN
, OPT_ARG_RECORDGAIN
),
333 AST_APP_OPTIONS(minivm_accmess_options
, {
334 AST_APP_OPTION('b', OPT_BUSY_GREETING
),
335 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING
),
336 AST_APP_OPTION('t', OPT_TEMP_GREETING
),
337 AST_APP_OPTION('n', OPT_NAME_GREETING
),
340 /*! \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
341 struct minivm_account
{
342 char username
[AST_MAX_CONTEXT
]; /*!< Mailbox username */
343 char domain
[AST_MAX_CONTEXT
]; /*!< Voicemail domain */
345 char pincode
[10]; /*!< Secret pin code, numbers only */
346 char fullname
[120]; /*!< Full name, for directory app */
347 char email
[80]; /*!< E-mail address - override */
348 char pager
[80]; /*!< E-mail address to pager (no attachment) */
349 char accountcode
[AST_MAX_ACCOUNT_CODE
]; /*!< Voicemail account account code */
350 char serveremail
[80]; /*!< From: Mail address */
351 char externnotify
[160]; /*!< Configurable notification command */
352 char language
[MAX_LANGUAGE
]; /*!< Config: Language setting */
353 char zonetag
[80]; /*!< Time zone */
354 char uniqueid
[20]; /*!< Unique integer identifier */
355 char exit
[80]; /*!< Options for exiting from voicemail() */
356 char attachfmt
[80]; /*!< Format for voicemail audio file attachment */
357 char etemplate
[80]; /*!< Pager template */
358 char ptemplate
[80]; /*!< Voicemail format */
359 unsigned int flags
; /*!< MVM_ flags */
360 struct ast_variable
*chanvars
; /*!< Variables for e-mail template */
361 double volgain
; /*!< Volume gain for voicemails sent via e-mail */
362 AST_LIST_ENTRY(minivm_account
) list
;
365 /*! \brief The list of e-mail accounts */
366 static AST_LIST_HEAD_STATIC(minivm_accounts
, minivm_account
);
368 /*! \brief Linked list of e-mail templates in various languages
369 These are used as templates for e-mails, pager messages and jabber messages
370 \ref message_templates
372 struct minivm_template
{
373 char name
[80]; /*!< Template name */
374 char *body
; /*!< Body of this template */
375 char fromaddress
[100]; /*!< Who's sending the e-mail? */
376 char serveremail
[80]; /*!< From: Mail address */
377 char subject
[100]; /*!< Subject line */
378 char charset
[32]; /*!< Default character set for this template */
379 char locale
[20]; /*!< Locale for setlocale() */
380 char dateformat
[80]; /*!< Date format to use in this attachment */
381 int attachment
; /*!< Attachment of media yes/no - no for pager messages */
382 AST_LIST_ENTRY(minivm_template
) list
; /*!< List mechanics */
385 /*! \brief The list of e-mail templates */
386 static AST_LIST_HEAD_STATIC(message_templates
, minivm_template
);
388 /*! \brief Options for leaving voicemail with the voicemail() application */
389 struct leave_vm_options
{
391 signed char record_gain
;
394 /*! \brief Structure for base64 encoding */
400 unsigned char iobuf
[B64_BASEMAXINLINE
];
403 /*! \brief Voicemail time zones */
405 char name
[80]; /*!< Name of this time zone */
406 char timezone
[80]; /*!< Timezone definition */
407 char msg_format
[BUFSIZ
]; /*!< Not used in minivm ...yet */
408 AST_LIST_ENTRY(minivm_zone
) list
; /*!< List mechanics */
411 /*! \brief The list of e-mail time zones */
412 static AST_LIST_HEAD_STATIC(minivm_zones
, minivm_zone
);
414 /*! \brief Structure for gathering statistics */
415 struct minivm_stats
{
416 int voicemailaccounts
; /*!< Number of static accounts */
417 int timezones
; /*!< Number of time zones */
418 int templates
; /*!< Number of templates */
420 struct timeval reset
; /*!< Time for last reset */
421 int receivedmessages
; /*!< Number of received messages since reset */
422 struct timeval lastreceived
; /*!< Time for last voicemail sent */
425 /*! \brief Statistics for voicemail */
426 static struct minivm_stats global_stats
;
428 AST_MUTEX_DEFINE_STATIC(minivmlock
); /*!< Lock to protect voicemail system */
429 AST_MUTEX_DEFINE_STATIC(minivmloglock
); /*!< Lock to protect voicemail system log file */
431 FILE *minivmlogfile
; /*!< The minivm log file */
433 static int global_vmminmessage
; /*!< Minimum duration of messages */
434 static int global_vmmaxmessage
; /*!< Maximum duration of message */
435 static int global_maxsilence
; /*!< Maximum silence during recording */
436 static int global_maxgreet
; /*!< Maximum length of prompts */
437 static int global_silencethreshold
= 128;
438 static char global_mailcmd
[160]; /*!< Configurable mail cmd */
439 static char global_externnotify
[160]; /*!< External notification application */
440 static char global_logfile
[PATH_MAX
]; /*!< Global log file for messages */
441 static char default_vmformat
[80];
443 static struct ast_flags globalflags
= {0}; /*!< Global voicemail flags */
444 static int global_saydurationminfo
;
445 static char global_charset
[32]; /*!< Global charset in messages */
447 static double global_volgain
; /*!< Volume gain for voicmemail via e-mail */
449 /*! \brief Default dateformat, can be overridden in configuration file */
450 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
451 #define DEFAULT_CHARSET "ISO-8859-1"
453 /* Forward declarations */
454 static char *message_template_parse_filebody(const char *filename
);
455 static char *message_template_parse_emailbody(const char *body
);
456 static int create_vmaccount(char *name
, struct ast_variable
*var
, int realtime
);
457 static struct minivm_account
*find_user_realtime(const char *domain
, const char *username
);
458 static char *handle_minivm_reload(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
);
460 /*! \brief Create message template */
461 static struct minivm_template
*message_template_create(const char *name
)
463 struct minivm_template
*template;
465 template = ast_calloc(1, sizeof(*template));
469 /* Set some defaults for templates */
470 ast_copy_string(template->name
, name
, sizeof(template->name
));
471 ast_copy_string(template->dateformat
, DEFAULT_DATEFORMAT
, sizeof(template->dateformat
));
472 ast_copy_string(template->charset
, DEFAULT_CHARSET
, sizeof(template->charset
));
473 ast_copy_string(template->subject
, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject
));
474 template->attachment
= TRUE
;
479 /*! \brief Release memory allocated by message template */
480 static void message_template_free(struct minivm_template
*template)
483 ast_free(template->body
);
488 /*! \brief Build message template from configuration */
489 static int message_template_build(const char *name
, struct ast_variable
*var
)
491 struct minivm_template
*template;
494 template = message_template_create(name
);
496 ast_log(LOG_ERROR
, "Out of memory, can't allocate message template object %s.\n", name
);
501 ast_debug(3, "-_-_- Configuring template option %s = \"%s\" for template %s\n", var
->name
, var
->value
, name
);
502 if (!strcasecmp(var
->name
, "fromaddress")) {
503 ast_copy_string(template->fromaddress
, var
->value
, sizeof(template->fromaddress
));
504 } else if (!strcasecmp(var
->name
, "fromemail")) {
505 ast_copy_string(template->serveremail
, var
->value
, sizeof(template->serveremail
));
506 } else if (!strcasecmp(var
->name
, "subject")) {
507 ast_copy_string(template->subject
, var
->value
, sizeof(template->subject
));
508 } else if (!strcasecmp(var
->name
, "locale")) {
509 ast_copy_string(template->locale
, var
->value
, sizeof(template->locale
));
510 } else if (!strcasecmp(var
->name
, "attachmedia")) {
511 template->attachment
= ast_true(var
->value
);
512 } else if (!strcasecmp(var
->name
, "dateformat")) {
513 ast_copy_string(template->dateformat
, var
->value
, sizeof(template->dateformat
));
514 } else if (!strcasecmp(var
->name
, "charset")) {
515 ast_copy_string(template->charset
, var
->value
, sizeof(template->charset
));
516 } else if (!strcasecmp(var
->name
, "templatefile")) {
518 ast_free(template->body
);
519 template->body
= message_template_parse_filebody(var
->value
);
520 if (!template->body
) {
521 ast_log(LOG_ERROR
, "Error reading message body definition file %s\n", var
->value
);
524 } else if (!strcasecmp(var
->name
, "messagebody")) {
526 ast_free(template->body
);
527 template->body
= message_template_parse_emailbody(var
->value
);
528 if (!template->body
) {
529 ast_log(LOG_ERROR
, "Error parsing message body definition:\n %s\n", var
->value
);
533 ast_log(LOG_ERROR
, "Unknown message template configuration option \"%s=%s\"\n", var
->name
, var
->value
);
539 ast_log(LOG_ERROR
, "-- %d errors found parsing message template definition %s\n", error
, name
);
541 AST_LIST_LOCK(&message_templates
);
542 AST_LIST_INSERT_TAIL(&message_templates
, template, list
);
543 AST_LIST_UNLOCK(&message_templates
);
545 global_stats
.templates
++;
550 /*! \brief Find named template */
551 static struct minivm_template
*message_template_find(const char *name
)
553 struct minivm_template
*this, *res
= NULL
;
555 if (ast_strlen_zero(name
))
558 AST_LIST_LOCK(&message_templates
);
559 AST_LIST_TRAVERSE(&message_templates
, this, list
) {
560 if (!strcasecmp(this->name
, name
)) {
565 AST_LIST_UNLOCK(&message_templates
);
571 /*! \brief Clear list of templates */
572 static void message_destroy_list(void)
574 struct minivm_template
*this;
575 AST_LIST_LOCK(&message_templates
);
576 while ((this = AST_LIST_REMOVE_HEAD(&message_templates
, list
)))
577 message_template_free(this);
579 AST_LIST_UNLOCK(&message_templates
);
582 /*! \brief read buffer from file (base64 conversion) */
583 static int b64_inbuf(struct b64_baseio
*bio
, FILE *fi
)
590 if ((l
= fread(bio
->iobuf
, 1, B64_BASEMAXINLINE
,fi
)) <= 0) {
604 /*! \brief read character from file to buffer (base64 conversion) */
605 static int b64_inchar(struct b64_baseio
*bio
, FILE *fi
)
607 if (bio
->iocp
>= bio
->iolen
) {
608 if (!b64_inbuf(bio
, fi
))
612 return bio
->iobuf
[bio
->iocp
++];
615 /*! \brief write buffer to file (base64 conversion) */
616 static int b64_ochar(struct b64_baseio
*bio
, int c
, FILE *so
)
618 if (bio
->linelength
>= B64_BASELINELEN
) {
619 if (fputs(EOL
,so
) == EOF
)
625 if (putc(((unsigned char) c
), so
) == EOF
)
633 /*! \brief Encode file to base64 encoding for email attachment (base64 conversion) */
634 static int base_encode(char *filename
, FILE *so
)
636 unsigned char dtable
[B64_BASEMAXINLINE
];
639 struct b64_baseio bio
;
641 memset(&bio
, 0, sizeof(bio
));
642 bio
.iocp
= B64_BASEMAXINLINE
;
644 if (!(fi
= fopen(filename
, "rb"))) {
645 ast_log(LOG_WARNING
, "Failed to open file: %s: %s\n", filename
, strerror(errno
));
649 for (i
= 0; i
<9; i
++) {
653 dtable
[26+i
+9]= 'j'+i
;
655 for (i
= 0; i
< 8; i
++) {
657 dtable
[26+i
+18]= 's'+i
;
659 for (i
= 0; i
< 10; i
++) {
666 unsigned char igroup
[3], ogroup
[4];
669 igroup
[0]= igroup
[1]= igroup
[2]= 0;
671 for (n
= 0; n
< 3; n
++) {
672 if ((c
= b64_inchar(&bio
, fi
)) == EOF
) {
676 igroup
[n
]= (unsigned char)c
;
680 ogroup
[0]= dtable
[igroup
[0]>>2];
681 ogroup
[1]= dtable
[((igroup
[0]&3)<<4) | (igroup
[1]>>4)];
682 ogroup
[2]= dtable
[((igroup
[1]&0xF)<<2) | (igroup
[2]>>6)];
683 ogroup
[3]= dtable
[igroup
[2]&0x3F];
693 b64_ochar(&bio
, ogroup
[i
], so
);
697 /* Put end of line - line feed */
698 if (fputs(EOL
, so
) == EOF
)
706 static int get_date(char *s
, int len
)
709 struct timeval tv
= ast_tvnow();
711 ast_localtime(&tv
, &tm
, NULL
);
712 return ast_strftime(s
, len
, "%a %b %e %r %Z %Y", &tm
);
716 /*! \brief Free user structure - if it's allocated */
717 static void free_user(struct minivm_account
*vmu
)
720 ast_variables_destroy(vmu
->chanvars
);
726 /*! \brief Prepare for voicemail template by adding channel variables
729 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
)
732 struct ast_variable
*var
;
735 ast_log(LOG_ERROR
, "No allocated channel, giving up...\n");
739 for (var
= vmu
->chanvars
; var
; var
= var
->next
) {
740 pbx_builtin_setvar_helper(channel
, var
->name
, var
->value
);
743 /* Prepare variables for substition in email body and subject */
744 pbx_builtin_setvar_helper(channel
, "MVM_NAME", vmu
->fullname
);
745 pbx_builtin_setvar_helper(channel
, "MVM_DUR", dur
);
746 pbx_builtin_setvar_helper(channel
, "MVM_DOMAIN", vmu
->domain
);
747 pbx_builtin_setvar_helper(channel
, "MVM_USERNAME", vmu
->username
);
748 pbx_builtin_setvar_helper(channel
, "MVM_CALLERID", ast_callerid_merge(callerid
, sizeof(callerid
), cidname
, cidnum
, "Unknown Caller"));
749 pbx_builtin_setvar_helper(channel
, "MVM_CIDNAME", (cidname
? cidname
: "an unknown caller"));
750 pbx_builtin_setvar_helper(channel
, "MVM_CIDNUM", (cidnum
? cidnum
: "an unknown caller"));
751 pbx_builtin_setvar_helper(channel
, "MVM_DATE", date
);
752 if (!ast_strlen_zero(counter
))
753 pbx_builtin_setvar_helper(channel
, "MVM_COUNTER", counter
);
756 /*! \brief Set default values for Mini-Voicemail users */
757 static void populate_defaults(struct minivm_account
*vmu
)
759 ast_copy_flags(vmu
, (&globalflags
), AST_FLAGS_ALL
);
760 ast_copy_string(vmu
->attachfmt
, default_vmformat
, sizeof(vmu
->attachfmt
));
761 vmu
->volgain
= global_volgain
;
764 /*! \brief Fix quote of mail headers for non-ascii characters */
765 static char *mailheader_quote(const char *from
, char *to
, size_t len
)
769 for (; ptr
< to
+ len
- 1; from
++) {
772 else if (*from
== '\0')
776 if (ptr
< to
+ len
- 1)
783 /*! \brief Allocate new vm user and set default values */
784 static struct minivm_account
*mvm_user_alloc(void)
786 struct minivm_account
*new;
788 new = ast_calloc(1, sizeof(*new));
791 populate_defaults(new);
797 /*! \brief Clear list of users */
798 static void vmaccounts_destroy_list(void)
800 struct minivm_account
*this;
801 AST_LIST_LOCK(&minivm_accounts
);
802 while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts
, list
)))
804 AST_LIST_UNLOCK(&minivm_accounts
);
808 /*! \brief Find user from static memory object list */
809 static struct minivm_account
*find_account(const char *domain
, const char *username
, int createtemp
)
811 struct minivm_account
*vmu
= NULL
, *cur
;
814 if (ast_strlen_zero(domain
) || ast_strlen_zero(username
)) {
815 ast_log(LOG_NOTICE
, "No username or domain? \n");
818 ast_debug(3, "-_-_-_- Looking for voicemail user %s in domain %s\n", username
, domain
);
820 AST_LIST_LOCK(&minivm_accounts
);
821 AST_LIST_TRAVERSE(&minivm_accounts
, cur
, list
) {
822 /* Is this the voicemail account we're looking for? */
823 if (!strcasecmp(domain
, cur
->domain
) && !strcasecmp(username
, cur
->username
))
826 AST_LIST_UNLOCK(&minivm_accounts
);
829 ast_debug(3, "-_-_- Found account for %s@%s\n", username
, domain
);
833 vmu
= find_user_realtime(domain
, username
);
835 if (createtemp
&& !vmu
) {
836 /* Create a temporary user, send e-mail and be gone */
837 vmu
= mvm_user_alloc();
838 ast_set2_flag(vmu
, TRUE
, MVM_ALLOCED
);
840 ast_copy_string(vmu
->username
, username
, sizeof(vmu
->username
));
841 ast_copy_string(vmu
->domain
, domain
, sizeof(vmu
->domain
));
842 ast_debug(1, "--- Created temporary account\n");
849 /*! \brief Find user in realtime storage
850 Returns pointer to minivm_account structure
852 static struct minivm_account
*find_user_realtime(const char *domain
, const char *username
)
854 struct ast_variable
*var
;
855 struct minivm_account
*retval
;
856 char name
[MAXHOSTNAMELEN
];
858 retval
= mvm_user_alloc();
863 ast_copy_string(retval
->username
, username
, sizeof(retval
->username
));
865 populate_defaults(retval
);
866 var
= ast_load_realtime("minivm", "username", username
, "domain", domain
, NULL
);
873 snprintf(name
, sizeof(name
), "%s@%s", username
, domain
);
874 create_vmaccount(name
, var
, TRUE
);
876 ast_variables_destroy(var
);
880 /*! \brief Send voicemail with audio file as an attachment */
881 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
)
885 char email
[256] = "";
889 char fname
[PATH_MAX
];
891 char tmp
[80] = "/tmp/astmail-XXXXXX";
895 struct minivm_zone
*the_zone
= NULL
;
897 struct ast_channel
*ast
;
899 char *passdata
= NULL
;
900 char *passdata2
= NULL
;
904 if (type
== MVM_MESSAGE_EMAIL
) {
905 if (vmu
&& !ast_strlen_zero(vmu
->email
)) {
906 ast_copy_string(email
, vmu
->email
, sizeof(email
));
907 } else if (!ast_strlen_zero(vmu
->username
) && !ast_strlen_zero(vmu
->domain
))
908 snprintf(email
, sizeof(email
), "%s@%s", vmu
->username
, vmu
->domain
);
909 } else if (type
== MVM_MESSAGE_PAGE
) {
910 ast_copy_string(email
, vmu
->pager
, sizeof(email
));
913 if (ast_strlen_zero(email
)) {
914 ast_log(LOG_WARNING
, "No address to send message to.\n");
918 ast_debug(3, "-_-_- Sending mail to %s@%s - Using template %s\n", vmu
->username
, vmu
->domain
, template->name
);
920 if (!strcmp(format
, "wav49"))
924 /* If we have a gain option, process it now with sox */
925 if (type
== MVM_MESSAGE_EMAIL
&& (vmu
->volgain
< -.001 || vmu
->volgain
> .001) ) {
926 char newtmp
[PATH_MAX
];
927 char tmpcmd
[PATH_MAX
];
930 ast_copy_string(newtmp
, "/tmp/XXXXXX", sizeof(newtmp
));
931 ast_debug(3, "newtmp: %s\n", newtmp
);
932 tmpfd
= mkstemp(newtmp
);
933 snprintf(tmpcmd
, sizeof(tmpcmd
), "sox -v %.4f %s.%s %s.%s", vmu
->volgain
, filename
, format
, newtmp
, format
);
934 ast_safe_system(tmpcmd
);
935 finalfilename
= newtmp
;
936 ast_debug(3, "-- VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename
, format
, vmu
->volgain
, vmu
->username
);
938 finalfilename
= ast_strdupa(filename
);
941 /* Create file name */
942 snprintf(fname
, sizeof(fname
), "%s.%s", finalfilename
, format
);
944 if (template->attachment
)
945 ast_debug(1, "-- Attaching file '%s', format '%s', uservm is '%d'\n", finalfilename
, format
, attach_user_voicemail
);
947 /* Make a temporary file instead of piping directly to sendmail, in case the mail
951 p
= fdopen(pfd
, "w");
956 ast_debug(1, "-_-_- Opening temp file for e-mail: %s\n", tmp
);
959 ast_log(LOG_WARNING
, "Unable to open temporary file '%s'\n", tmp
);
962 /* Allocate channel used for chanvar substitution */
963 ast
= ast_channel_alloc(0, AST_STATE_DOWN
, 0, 0, "", "", "", 0, 0);
966 snprintf(dur
, sizeof(dur
), "%d:%02d", duration
/ 60, duration
% 60);
968 /* Does this user have a timezone specified? */
969 if (!ast_strlen_zero(vmu
->zonetag
)) {
970 /* Find the zone in the list */
971 struct minivm_zone
*z
;
972 AST_LIST_LOCK(&minivm_zones
);
973 AST_LIST_TRAVERSE(&minivm_zones
, z
, list
) {
974 if (strcmp(z
->name
, vmu
->zonetag
))
978 AST_LIST_UNLOCK(&minivm_zones
);
982 ast_localtime(&now
, &tm
, the_zone
? the_zone
->timezone
: NULL
);
983 ast_strftime(date
, sizeof(date
), "%a, %d %b %Y %H:%M:%S %z", &tm
);
985 /* Start printing the email to the temporary file */
986 fprintf(p
, "Date: %s\n", date
);
988 /* Set date format for voicemail mail */
989 ast_strftime(date
, sizeof(date
), template->dateformat
, &tm
);
992 /* Populate channel with channel variables for substitution */
993 prep_email_sub_vars(ast
, vmu
, cidnum
, cidname
, dur
, date
, counter
);
995 /* Find email address to use */
996 /* If there's a server e-mail adress in the account, user that, othterwise template */
997 fromemail
= ast_strlen_zero(vmu
->serveremail
) ? template->serveremail
: vmu
->serveremail
;
999 /* Find name to user for server e-mail */
1000 fromaddress
= ast_strlen_zero(template->fromaddress
) ? "" : template->fromaddress
;
1002 /* If needed, add hostname as domain */
1003 if (ast_strlen_zero(fromemail
))
1004 fromemail
= "asterisk";
1006 if (strchr(fromemail
, '@'))
1007 ast_copy_string(who
, fromemail
, sizeof(who
));
1009 char host
[MAXHOSTNAMELEN
];
1010 gethostname(host
, sizeof(host
)-1);
1011 snprintf(who
, sizeof(who
), "%s@%s", fromemail
, host
);
1014 if (ast_strlen_zero(fromaddress
)) {
1015 fprintf(p
, "From: Asterisk PBX <%s>\n", who
);
1017 /* Allocate a buffer big enough for variable substitution */
1018 int vmlen
= strlen(fromaddress
) * 3 + 200;
1020 ast_debug(4, "-_-_- Fromaddress template: %s\n", fromaddress
);
1021 if ((passdata
= alloca(vmlen
))) {
1022 pbx_substitute_variables_helper(ast
, fromaddress
, passdata
, vmlen
);
1023 len_passdata
= strlen(passdata
) * 2 + 3;
1024 passdata2
= alloca(len_passdata
);
1025 fprintf(p
, "From: %s <%s>\n", mailheader_quote(passdata
, passdata2
, len_passdata
), who
);
1027 ast_log(LOG_WARNING
, "Cannot allocate workspace for variable substitution\n");
1032 ast_debug(4, "-_-_- Fromstring now: %s\n", ast_strlen_zero(passdata
) ? "-default-" : passdata
);
1034 fprintf(p
, "Message-ID: <Asterisk-%d-%s-%d-%s>\n", (unsigned int)rand(), vmu
->username
, (int)getpid(), who
);
1035 len_passdata
= strlen(vmu
->fullname
) * 2 + 3;
1036 passdata2
= alloca(len_passdata
);
1037 if (!ast_strlen_zero(vmu
->email
))
1038 fprintf(p
, "To: %s <%s>\n", mailheader_quote(vmu
->fullname
, passdata2
, len_passdata
), vmu
->email
);
1040 fprintf(p
, "To: %s <%s@%s>\n", mailheader_quote(vmu
->fullname
, passdata2
, len_passdata
), vmu
->username
, vmu
->domain
);
1042 if (!ast_strlen_zero(template->subject
)) {
1044 int vmlen
= strlen(template->subject
) * 3 + 200;
1045 if ((passdata
= alloca(vmlen
))) {
1046 pbx_substitute_variables_helper(ast
, template->subject
, passdata
, vmlen
);
1047 fprintf(p
, "Subject: %s\n", passdata
);
1049 ast_log(LOG_WARNING
, "Cannot allocate workspace for variable substitution\n");
1054 ast_debug(4, "-_-_- Subject now: %s\n", passdata
);
1057 fprintf(p
, "Subject: New message in mailbox %s@%s\n", vmu
->username
, vmu
->domain
);
1058 ast_debug(1, "-_-_- Using default subject for this email \n");
1062 if (option_debug
> 2)
1063 fprintf(p
, "X-Asterisk-debug: template %s user account %s@%s\n", template->name
, vmu
->username
, vmu
->domain
);
1064 fprintf(p
, "MIME-Version: 1.0\n");
1066 /* Something unique. */
1067 snprintf(bound
, sizeof(bound
), "voicemail_%s%d%d", vmu
->username
, (int)getpid(), (unsigned int)rand());
1069 fprintf(p
, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound
);
1071 fprintf(p
, "--%s\n", bound
);
1072 fprintf(p
, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", global_charset
);
1073 if (!ast_strlen_zero(template->body
)) {
1075 int vmlen
= strlen(template->body
)*3 + 200;
1076 if ((passdata
= alloca(vmlen
))) {
1077 pbx_substitute_variables_helper(ast
, template->body
, passdata
, vmlen
);
1078 ast_debug(3, "Message now: %s\n-----\n", passdata
);
1079 fprintf(p
, "%s\n", passdata
);
1081 ast_log(LOG_WARNING
, "Cannot allocate workspace for variable substitution\n");
1083 fprintf(p
, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1085 "in mailbox %s from %s, on %s so you might\n"
1086 "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu
->fullname
,
1087 dur
, vmu
->username
, (cidname
? cidname
: (cidnum
? cidnum
: "an unknown caller")), date
);
1088 ast_debug(3, "Using default message body (no template)\n-----\n");
1090 /* Eww. We want formats to tell us their own MIME type */
1091 if (template->attachment
) {
1092 char *ctype
= "audio/x-";
1093 ast_debug(3, "-_-_- Attaching file to message: %s\n", fname
);
1094 if (!strcasecmp(format
, "ogg"))
1095 ctype
= "application/";
1097 fprintf(p
, "--%s\n", bound
);
1098 fprintf(p
, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype
, format
, format
);
1099 fprintf(p
, "Content-Transfer-Encoding: base64\n");
1100 fprintf(p
, "Content-Description: Voicemail sound attachment.\n");
1101 fprintf(p
, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter
? counter
: "", format
);
1103 base_encode(fname
, p
);
1104 fprintf(p
, "\n\n--%s--\n.\n", bound
);
1107 snprintf(tmp2
, sizeof(tmp2
), "( %s < %s ; rm -f %s ) &", global_mailcmd
, tmp
, tmp
);
1108 ast_safe_system(tmp2
);
1109 ast_debug(1, "Sent message to %s with command '%s' - %s\n", vmu
->email
, global_mailcmd
, template->attachment
? "(media attachment)" : "");
1110 ast_debug(3, "-_-_- Actual command used: %s\n", tmp2
);
1112 ast_channel_free(ast
);
1116 /*! \brief Create directory based on components */
1117 static int make_dir(char *dest
, int len
, const char *domain
, const char *username
, const char *folder
)
1119 return snprintf(dest
, len
, "%s%s/%s%s%s", MVM_SPOOL_DIR
, domain
, username
, ast_strlen_zero(folder
) ? "" : "/", folder
? folder
: "");
1122 /*! \brief Checks if directory exists. Does not create directory, but builds string in dest
1123 * \param dest String. base directory.
1124 * \param len Int. Length base directory string.
1125 * \param domain String. Ignored if is null or empty string.
1126 * \param username String. Ignored if is null or empty string.
1127 * \param folder String. Ignored if is null or empty string.
1128 * \return 0 on failure, 1 on success.
1130 static int check_dirpath(char *dest
, int len
, char *domain
, char *username
, char *folder
)
1132 struct stat filestat
;
1133 make_dir(dest
, len
, domain
, username
, folder
? folder
: "");
1134 if (stat(dest
, &filestat
)== -1)
1140 /*! \brief basically mkdir -p $dest/$domain/$username/$folder
1141 * \param dest String. base directory.
1142 * \param len Length of directory string
1143 * \param domain String. Ignored if is null or empty string.
1144 * \param folder String. Ignored if is null or empty string.
1145 * \param username String. Ignored if is null or empty string.
1146 * \return -1 on failure, 0 on success.
1148 static int create_dirpath(char *dest
, int len
, char *domain
, char *username
, char *folder
)
1151 make_dir(dest
, len
, domain
, username
, folder
);
1152 if ((res
= ast_mkdir(dest
, 0777))) {
1153 ast_log(LOG_WARNING
, "ast_mkdir '%s' failed: %s\n", dest
, strerror(res
));
1156 ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username
, domain
, folder
, dest
);
1161 /*! \brief Play intro message before recording voicemail
1163 static int invent_message(struct ast_channel
*chan
, char *domain
, char *username
, int busy
, char *ecodes
)
1168 ast_debug(2, "-_-_- Still preparing to play message ...\n");
1170 snprintf(fn
, sizeof(fn
), "%s%s/%s/greet", MVM_SPOOL_DIR
, domain
, username
);
1172 if (ast_fileexists(fn
, NULL
, NULL
) > 0) {
1173 res
= ast_streamfile(chan
, fn
, chan
->language
);
1176 res
= ast_waitstream(chan
, ecodes
);
1180 int numericusername
= 1;
1183 ast_debug(2, "-_-_- No personal prompts. Using default prompt set for language\n");
1186 ast_debug(2, "-_-_- Numeric? Checking %c\n", *i
);
1188 numericusername
= FALSE
;
1194 if (numericusername
) {
1195 if(ast_streamfile(chan
, "vm-theperson", chan
->language
))
1197 if ((res
= ast_waitstream(chan
, ecodes
)))
1200 res
= ast_say_digit_str(chan
, username
, ecodes
, chan
->language
);
1204 if(ast_streamfile(chan
, "vm-theextensionis", chan
->language
))
1206 if ((res
= ast_waitstream(chan
, ecodes
)))
1211 res
= ast_streamfile(chan
, busy
? "vm-isonphone" : "vm-isunavail", chan
->language
);
1214 res
= ast_waitstream(chan
, ecodes
);
1218 /*! \brief Delete media files and attribute file */
1219 static int vm_delete(char *file
)
1223 ast_debug(1, "-_-_- Deleting voicemail file %s\n", file
);
1225 res
= unlink(file
); /* Remove the meta data file */
1226 res
|= ast_filedelete(file
, NULL
); /* remove the media file */
1231 /*! \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1232 static int play_record_review(struct ast_channel
*chan
, char *playfile
, char *recordfile
, int maxtime
, char *fmt
,
1233 int outsidecaller
, struct minivm_account
*vmu
, int *duration
, const char *unlockdir
,
1234 signed char record_gain
)
1237 int max_attempts
= 3;
1240 int message_exists
= 0;
1241 signed char zero_gain
= 0;
1242 char *acceptdtmf
= "#";
1243 char *canceldtmf
= "";
1245 /* Note that urgent and private are for flagging messages as such in the future */
1247 /* barf if no pointer passed to store duration in */
1248 if (duration
== NULL
) {
1249 ast_log(LOG_WARNING
, "Error play_record_review called without duration pointer\n");
1253 cmd
= '3'; /* Want to start by recording */
1255 while ((cmd
>= 0) && (cmd
!= 't')) {
1259 ast_verb(3, "Reviewing the message\n");
1260 ast_streamfile(chan
, recordfile
, chan
->language
);
1261 cmd
= ast_waitstream(chan
, AST_DIGIT_ANY
);
1267 ast_verb(3, "Re-recording the message\n");
1269 ast_verb(3, "Recording the message\n");
1270 if (recorded
&& outsidecaller
)
1271 cmd
= ast_play_and_wait(chan
, "beep");
1273 /* 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 */
1275 ast_channel_setoption(chan
, AST_OPTION_RXGAIN
, &record_gain
, sizeof(record_gain
), 0);
1276 if (ast_test_flag(vmu
, MVM_OPERATOR
))
1278 cmd
= ast_play_and_record_full(chan
, playfile
, recordfile
, maxtime
, fmt
, duration
, global_silencethreshold
, global_maxsilence
, unlockdir
, acceptdtmf
, canceldtmf
);
1280 ast_channel_setoption(chan
, AST_OPTION_RXGAIN
, &zero_gain
, sizeof(zero_gain
), 0);
1281 if (cmd
== -1) /* User has hung up, no options to give */
1285 else if (cmd
== '*')
1288 /* If all is well, a message exists */
1301 cmd
= ast_play_and_wait(chan
, "vm-sorry");
1304 if(!ast_test_flag(vmu
, MVM_OPERATOR
)) {
1305 cmd
= ast_play_and_wait(chan
, "vm-sorry");
1308 if (message_exists
|| recorded
) {
1309 cmd
= ast_play_and_wait(chan
, "vm-saveoper");
1311 cmd
= ast_waitfordigit(chan
, 3000);
1313 ast_play_and_wait(chan
, "vm-msgsaved");
1316 ast_play_and_wait(chan
, "vm-deleted");
1317 vm_delete(recordfile
);
1323 /* If the caller is an ouside caller, and the review option is enabled,
1324 allow them to review the message, but let the owner of the box review
1326 if (outsidecaller
&& !ast_test_flag(vmu
, MVM_REVIEW
))
1328 if (message_exists
) {
1329 cmd
= ast_play_and_wait(chan
, "vm-review");
1331 cmd
= ast_play_and_wait(chan
, "vm-torerecord");
1333 cmd
= ast_waitfordigit(chan
, 600);
1336 if (!cmd
&& outsidecaller
&& ast_test_flag(vmu
, MVM_OPERATOR
)) {
1337 cmd
= ast_play_and_wait(chan
, "vm-reachoper");
1339 cmd
= ast_waitfordigit(chan
, 600);
1342 cmd
= ast_waitfordigit(chan
, 6000);
1346 if (attempts
> max_attempts
) {
1352 ast_play_and_wait(chan
, "vm-goodbye");
1358 /*! \brief Run external notification for voicemail message */
1359 static void run_externnotify(struct ast_channel
*chan
, struct minivm_account
*vmu
)
1361 char arguments
[BUFSIZ
];
1363 if (ast_strlen_zero(vmu
->externnotify
) && ast_strlen_zero(global_externnotify
))
1366 snprintf(arguments
, sizeof(arguments
), "%s %s@%s %s %s&",
1367 ast_strlen_zero(vmu
->externnotify
) ? global_externnotify
: vmu
->externnotify
,
1368 vmu
->username
, vmu
->domain
,
1369 chan
->cid
.cid_name
, chan
->cid
.cid_num
);
1371 ast_debug(1, "Executing: %s\n", arguments
);
1372 ast_safe_system(arguments
);
1375 /*! \brief Send message to voicemail account owner */
1376 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
)
1379 struct minivm_template
*etemplate
;
1380 char *messageformat
;
1382 char oldlocale
[100];
1383 const char *counter
;
1385 if (!ast_strlen_zero(vmu
->attachfmt
)) {
1386 if (strstr(format
, vmu
->attachfmt
)) {
1387 format
= vmu
->attachfmt
;
1389 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
);
1392 etemplate
= message_template_find(vmu
->etemplate
);
1394 etemplate
= message_template_find(templatename
);
1396 etemplate
= message_template_find("email-default");
1398 /* Attach only the first format */
1399 stringp
= messageformat
= ast_strdupa(format
);
1400 strsep(&stringp
, "|");
1402 if (!ast_strlen_zero(etemplate
->locale
)) {
1404 ast_copy_string(oldlocale
, setlocale(LC_TIME
, NULL
), sizeof(oldlocale
));
1405 ast_debug(2, "-_-_- Changing locale from %s to %s\n", oldlocale
, etemplate
->locale
);
1406 newlocale
= setlocale(LC_TIME
, etemplate
->locale
);
1407 if (newlocale
== NULL
) {
1408 ast_log(LOG_WARNING
, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate
->locale
);
1414 /* Read counter if available */
1415 ast_channel_lock(chan
);
1416 if ((counter
= pbx_builtin_getvar_helper(chan
, "MVM_COUNTER"))) {
1417 counter
= ast_strdupa(counter
);
1419 ast_channel_unlock(chan
);
1421 if (ast_strlen_zero(counter
)) {
1422 ast_debug(2, "-_-_- MVM_COUNTER not found\n");
1424 ast_debug(2, "-_-_- MVM_COUNTER found - will use it with value %s\n", counter
);
1427 res
= sendmail(etemplate
, vmu
, cidnum
, cidname
, filename
, messageformat
, duration
, etemplate
->attachment
, MVM_MESSAGE_EMAIL
, counter
);
1429 if (res
== 0 && !ast_strlen_zero(vmu
->pager
)) {
1430 /* Find template for paging */
1431 etemplate
= message_template_find(vmu
->ptemplate
);
1433 etemplate
= message_template_find("pager-default");
1434 if (etemplate
->locale
) {
1435 ast_copy_string(oldlocale
, setlocale(LC_TIME
, ""), sizeof(oldlocale
));
1436 setlocale(LC_TIME
, etemplate
->locale
);
1439 res
= sendmail(etemplate
, vmu
, cidnum
, cidname
, filename
, messageformat
, duration
, etemplate
->attachment
, MVM_MESSAGE_PAGE
, counter
);
1442 manager_event(EVENT_FLAG_CALL
, "MiniVoiceMail", "Action: SentNotification\rn\nMailbox: %s@%s\r\nCounter: %s\r\n", vmu
->username
, vmu
->domain
, counter
);
1444 run_externnotify(chan
, vmu
); /* Run external notification */
1446 if (etemplate
->locale
)
1447 setlocale(LC_TIME
, oldlocale
); /* Rest to old locale */
1452 /*! \brief Record voicemail message, store into file prepared for sending e-mail */
1453 static int leave_voicemail(struct ast_channel
*chan
, char *username
, struct leave_vm_options
*options
)
1455 char tmptxtfile
[PATH_MAX
];
1458 int res
= 0, txtdes
;
1462 char tmpdir
[PATH_MAX
];
1463 char ext_context
[256] = "";
1467 struct minivm_account
*vmu
;
1470 ast_copy_string(tmp
, username
, sizeof(tmp
));
1472 domain
= strchr(tmp
, '@');
1478 if (!(vmu
= find_account(domain
, username
, TRUE
))) {
1479 /* We could not find user, let's exit */
1480 ast_log(LOG_ERROR
, "Can't allocate temporary account for '%s@%s'\n", username
, domain
);
1481 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "FAILED");
1485 /* Setup pre-file if appropriate */
1486 if (strcmp(vmu
->domain
, "localhost"))
1487 snprintf(ext_context
, sizeof(ext_context
), "%s@%s", username
, vmu
->domain
);
1489 ast_copy_string(ext_context
, vmu
->domain
, sizeof(ext_context
));
1491 /* The meat of recording the message... All the announcements and beeps have been played*/
1492 if (ast_strlen_zero(vmu
->attachfmt
))
1493 ast_copy_string(fmt
, default_vmformat
, sizeof(fmt
));
1495 ast_copy_string(fmt
, vmu
->attachfmt
, sizeof(fmt
));
1497 if (ast_strlen_zero(fmt
)) {
1498 ast_log(LOG_WARNING
, "No format for saving voicemail? Default %s\n", default_vmformat
);
1499 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "FAILED");
1504 userdir
= check_dirpath(tmpdir
, sizeof(tmpdir
), vmu
->domain
, username
, "tmp");
1506 /* If we have no user directory, use generic temporary directory */
1508 create_dirpath(tmpdir
, sizeof(tmpdir
), "0000_minivm_temp", "mediafiles", "");
1509 ast_debug(3, "Creating temporary directory %s\n", tmpdir
);
1513 snprintf(tmptxtfile
, sizeof(tmptxtfile
), "%s/XXXXXX", tmpdir
);
1516 /* XXX This file needs to be in temp directory */
1517 txtdes
= mkstemp(tmptxtfile
);
1519 ast_log(LOG_ERROR
, "Unable to create message file %s: %s\n", tmptxtfile
, strerror(errno
));
1520 res
= ast_streamfile(chan
, "vm-mailboxfull", chan
->language
);
1522 res
= ast_waitstream(chan
, "");
1523 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "FAILED");
1528 /* Unless we're *really* silent, try to send the beep */
1529 res
= ast_streamfile(chan
, "beep", chan
->language
);
1531 res
= ast_waitstream(chan
, "");
1534 /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1535 /* Store information */
1536 ast_debug(2, "Open file for metadata: %s\n", tmptxtfile
);
1538 res
= play_record_review(chan
, NULL
, tmptxtfile
, global_vmmaxmessage
, fmt
, 1, vmu
, &duration
, NULL
, options
->record_gain
);
1540 txt
= fdopen(txtdes
, "w+");
1542 ast_log(LOG_WARNING
, "Error opening text file for output\n");
1545 struct timeval now
= ast_tvnow();
1547 char logbuf
[BUFSIZ
];
1548 get_date(date
, sizeof(date
));
1549 ast_localtime(&now
, &tm
, NULL
);
1550 ast_strftime(timebuf
, sizeof(timebuf
), "%H:%M:%S", &tm
);
1552 snprintf(logbuf
, sizeof(logbuf
),
1553 /* "Mailbox:domain:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1554 "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1561 ast_callerid_merge(callerid
, sizeof(callerid
), chan
->cid
.cid_name
, chan
->cid
.cid_num
, "Unknown"),
1565 duration
< global_vmminmessage
? "IGNORED" : "OK",
1568 fprintf(txt
, "%s", logbuf
);
1569 if (minivmlogfile
) {
1570 ast_mutex_lock(&minivmloglock
);
1571 fprintf(minivmlogfile
, "%s", logbuf
);
1572 ast_mutex_unlock(&minivmloglock
);
1575 if (duration
< global_vmminmessage
) {
1576 ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", duration
, global_vmminmessage
);
1578 ast_filedelete(tmptxtfile
, NULL
);
1580 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "FAILED");
1583 fclose(txt
); /* Close log file */
1584 if (ast_fileexists(tmptxtfile
, NULL
, NULL
) <= 0) {
1585 ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1587 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "FAILED");
1588 if(ast_test_flag(vmu
, MVM_ALLOCED
))
1593 /* Set channel variables for the notify application */
1594 pbx_builtin_setvar_helper(chan
, "MVM_FILENAME", tmptxtfile
);
1595 snprintf(timebuf
, sizeof(timebuf
), "%d", duration
);
1596 pbx_builtin_setvar_helper(chan
, "MVM_DURATION", timebuf
);
1597 pbx_builtin_setvar_helper(chan
, "MVM_FORMAT", fmt
);
1600 global_stats
.lastreceived
= ast_tvnow();
1601 global_stats
.receivedmessages
++;
1602 // /* Go ahead and delete audio files from system, they're not needed any more */
1603 // if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1604 // ast_filedelete(tmptxtfile, NULL);
1605 // /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1606 // ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1612 if(ast_test_flag(vmu
, MVM_ALLOCED
))
1615 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "SUCCESS");
1619 /*! \brief Notify voicemail account owners - either generic template or user specific */
1620 static int minivm_notify_exec(struct ast_channel
*chan
, void *data
)
1628 struct minivm_account
*vmu
;
1629 char *username
= argv
[0];
1630 const char *template = "";
1631 const char *filename
;
1633 const char *duration_string
;
1635 if (ast_strlen_zero(data
)) {
1636 ast_log(LOG_ERROR
, "Minivm needs at least an account argument \n");
1639 tmpptr
= ast_strdupa((char *)data
);
1641 ast_log(LOG_ERROR
, "Out of memory\n");
1644 argc
= ast_app_separate_args(tmpptr
, ',', argv
, sizeof(argv
) / sizeof(argv
[0]));
1646 if (argc
== 2 && !ast_strlen_zero(argv
[1]))
1649 ast_copy_string(tmp
, argv
[0], sizeof(tmp
));
1651 domain
= strchr(tmp
, '@');
1656 if (ast_strlen_zero(domain
) || ast_strlen_zero(username
)) {
1657 ast_log(LOG_ERROR
, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv
[0]);
1661 if(!(vmu
= find_account(domain
, username
, TRUE
))) {
1662 /* We could not find user, let's exit */
1663 ast_log(LOG_WARNING
, "Could not allocate temporary memory for '%s@%s'\n", username
, domain
);
1664 pbx_builtin_setvar_helper(chan
, "MINIVM_NOTIFY_STATUS", "FAILED");
1668 ast_channel_lock(chan
);
1669 if ((filename
= pbx_builtin_getvar_helper(chan
, "MVM_FILENAME"))) {
1670 filename
= ast_strdupa(filename
);
1672 ast_channel_unlock(chan
);
1673 /* Notify of new message to e-mail and pager */
1674 if (!ast_strlen_zero(filename
)) {
1675 ast_channel_lock(chan
);
1676 if ((format
= pbx_builtin_getvar_helper(chan
, "MVM_FORMAT"))) {
1677 format
= ast_strdupa(format
);
1679 if ((duration_string
= pbx_builtin_getvar_helper(chan
, "MVM_DURATION"))) {
1680 duration_string
= ast_strdupa(duration_string
);
1682 ast_channel_unlock(chan
);
1683 res
= notify_new_message(chan
, template, vmu
, filename
, atoi(duration_string
), format
, chan
->cid
.cid_num
, chan
->cid
.cid_name
);
1686 pbx_builtin_setvar_helper(chan
, "MINIVM_NOTIFY_STATUS", res
== 0 ? "SUCCESS" : "FAILED");
1689 if(ast_test_flag(vmu
, MVM_ALLOCED
))
1692 /* Ok, we're ready to rock and roll. Return to dialplan */
1698 /*! \brief Dialplan function to record voicemail */
1699 static int minivm_record_exec(struct ast_channel
*chan
, void *data
)
1703 struct leave_vm_options leave_options
;
1706 struct ast_flags flags
= { 0 };
1707 char *opts
[OPT_ARG_ARRAY_SIZE
];
1709 memset(&leave_options
, 0, sizeof(leave_options
));
1711 /* Answer channel if it's not already answered */
1712 if (chan
->_state
!= AST_STATE_UP
)
1715 if (ast_strlen_zero(data
)) {
1716 ast_log(LOG_ERROR
, "Minivm needs at least an account argument \n");
1719 tmp
= ast_strdupa((char *)data
);
1721 ast_log(LOG_ERROR
, "Out of memory\n");
1724 argc
= ast_app_separate_args(tmp
, ',', argv
, sizeof(argv
) / sizeof(argv
[0]));
1726 if (ast_app_parse_options(minivm_app_options
, &flags
, opts
, argv
[1])) {
1729 ast_copy_flags(&leave_options
, &flags
, OPT_SILENT
| OPT_BUSY_GREETING
| OPT_UNAVAIL_GREETING
);
1730 if (ast_test_flag(&flags
, OPT_RECORDGAIN
)) {
1733 if (sscanf(opts
[OPT_ARG_RECORDGAIN
], "%d", &gain
) != 1) {
1734 ast_log(LOG_WARNING
, "Invalid value '%s' provided for record gain option\n", opts
[OPT_ARG_RECORDGAIN
]);
1737 leave_options
.record_gain
= (signed char) gain
;
1741 /* Now run the appliation and good luck to you! */
1742 res
= leave_voicemail(chan
, argv
[0], &leave_options
);
1744 if (res
== ERROR_LOCK_PATH
) {
1745 ast_log(LOG_ERROR
, "Could not leave voicemail. The path is already locked.\n");
1746 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "FAILED");
1749 pbx_builtin_setvar_helper(chan
, "MINIVM_RECORD_STATUS", "SUCCESS");
1754 /*! \brief Play voicemail prompts - either generic or user specific */
1755 static int minivm_greet_exec(struct ast_channel
*chan
, void *data
)
1757 struct leave_vm_options leave_options
= { 0, '\0'};
1760 struct ast_flags flags
= { 0 };
1761 char *opts
[OPT_ARG_ARRAY_SIZE
];
1767 char dest
[PATH_MAX
];
1768 char prefile
[PATH_MAX
];
1769 char tempfile
[PATH_MAX
] = "";
1770 char ext_context
[256] = "";
1772 char ecodes
[16] = "#";
1774 struct minivm_account
*vmu
;
1775 char *username
= argv
[0];
1777 if (ast_strlen_zero(data
)) {
1778 ast_log(LOG_ERROR
, "Minivm needs at least an account argument \n");
1781 tmpptr
= ast_strdupa((char *)data
);
1783 ast_log(LOG_ERROR
, "Out of memory\n");
1786 argc
= ast_app_separate_args(tmpptr
, ',', argv
, sizeof(argv
) / sizeof(argv
[0]));
1789 if (ast_app_parse_options(minivm_app_options
, &flags
, opts
, argv
[1]))
1791 ast_copy_flags(&leave_options
, &flags
, OPT_SILENT
| OPT_BUSY_GREETING
| OPT_UNAVAIL_GREETING
);
1794 ast_copy_string(tmp
, argv
[0], sizeof(tmp
));
1796 domain
= strchr(tmp
, '@');
1801 if (ast_strlen_zero(domain
) || ast_strlen_zero(username
)) {
1802 ast_log(LOG_ERROR
, "Need username@domain as argument. Sorry. Argument: %s\n", argv
[0]);
1805 ast_debug(1, "-_-_- Trying to find configuration for user %s in domain %s\n", username
, domain
);
1807 if (!(vmu
= find_account(domain
, username
, TRUE
))) {
1808 ast_log(LOG_ERROR
, "Could not allocate memory. \n");
1812 /* Answer channel if it's not already answered */
1813 if (chan
->_state
!= AST_STATE_UP
)
1816 /* Setup pre-file if appropriate */
1817 if (strcmp(vmu
->domain
, "localhost"))
1818 snprintf(ext_context
, sizeof(ext_context
), "%s@%s", username
, vmu
->domain
);
1820 ast_copy_string(ext_context
, vmu
->domain
, sizeof(ext_context
));
1822 if (ast_test_flag(&leave_options
, OPT_BUSY_GREETING
)) {
1823 res
= check_dirpath(dest
, sizeof(dest
), vmu
->domain
, username
, "busy");
1825 snprintf(prefile
, sizeof(prefile
), "%s%s/%s/busy", MVM_SPOOL_DIR
, vmu
->domain
, username
);
1826 } else if (ast_test_flag(&leave_options
, OPT_UNAVAIL_GREETING
)) {
1827 res
= check_dirpath(dest
, sizeof(dest
), vmu
->domain
, username
, "unavail");
1829 snprintf(prefile
, sizeof(prefile
), "%s%s/%s/unavail", MVM_SPOOL_DIR
, vmu
->domain
, username
);
1831 /* Check for temporary greeting - it overrides busy and unavail */
1832 snprintf(tempfile
, sizeof(tempfile
), "%s%s/%s/temp", MVM_SPOOL_DIR
, vmu
->domain
, username
);
1833 if (!(res
= check_dirpath(dest
, sizeof(dest
), vmu
->domain
, username
, "temp"))) {
1834 ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile
);
1835 ast_copy_string(prefile
, tempfile
, sizeof(prefile
));
1837 ast_debug(2, "-_-_- Preparing to play message ...\n");
1839 /* Check current or macro-calling context for special extensions */
1840 if (ast_test_flag(vmu
, MVM_OPERATOR
)) {
1841 if (!ast_strlen_zero(vmu
->exit
)) {
1842 if (ast_exists_extension(chan
, vmu
->exit
, "o", 1, chan
->cid
.cid_num
)) {
1843 strncat(ecodes
, "0", sizeof(ecodes
) - strlen(ecodes
) - 1);
1846 } else if (ast_exists_extension(chan
, chan
->context
, "o", 1, chan
->cid
.cid_num
)) {
1847 strncat(ecodes
, "0", sizeof(ecodes
) - strlen(ecodes
) - 1);
1850 else if (!ast_strlen_zero(chan
->macrocontext
) && ast_exists_extension(chan
, chan
->macrocontext
, "o", 1, chan
->cid
.cid_num
)) {
1851 strncat(ecodes
, "0", sizeof(ecodes
) - strlen(ecodes
) - 1);
1856 if (!ast_strlen_zero(vmu
->exit
)) {
1857 if (ast_exists_extension(chan
, vmu
->exit
, "a", 1, chan
->cid
.cid_num
))
1858 strncat(ecodes
, "*", sizeof(ecodes
) - strlen(ecodes
) - 1);
1859 } else if (ast_exists_extension(chan
, chan
->context
, "a", 1, chan
->cid
.cid_num
))
1860 strncat(ecodes
, "*", sizeof(ecodes
) - strlen(ecodes
) - 1);
1861 else if (!ast_strlen_zero(chan
->macrocontext
) && ast_exists_extension(chan
, chan
->macrocontext
, "a", 1, chan
->cid
.cid_num
)) {
1862 strncat(ecodes
, "*", sizeof(ecodes
) - strlen(ecodes
) - 1);
1866 res
= 0; /* Reset */
1867 /* Play the beginning intro if desired */
1868 if (!ast_strlen_zero(prefile
)) {
1869 if (ast_streamfile(chan
, prefile
, chan
->language
) > -1)
1870 res
= ast_waitstream(chan
, ecodes
);
1872 ast_debug(2, "%s doesn't exist, doing what we can\n", prefile
);
1873 res
= invent_message(chan
, vmu
->domain
, username
, ast_test_flag(&leave_options
, OPT_BUSY_GREETING
), ecodes
);
1876 ast_debug(2, "Hang up during prefile playback\n");
1877 pbx_builtin_setvar_helper(chan
, "MINIVM_GREET_STATUS", "FAILED");
1878 if(ast_test_flag(vmu
, MVM_ALLOCED
))
1883 /* On a '#' we skip the instructions */
1884 ast_set_flag(&leave_options
, OPT_SILENT
);
1887 if (!res
&& !ast_test_flag(&leave_options
, OPT_SILENT
)) {
1888 res
= ast_streamfile(chan
, SOUND_INTRO
, chan
->language
);
1890 res
= ast_waitstream(chan
, ecodes
);
1892 ast_set_flag(&leave_options
, OPT_SILENT
);
1897 ast_stopstream(chan
);
1898 /* Check for a '*' here in case the caller wants to escape from voicemail to something
1899 other than the operator -- an automated attendant or mailbox login for example */
1901 chan
->exten
[0] = 'a';
1902 chan
->exten
[1] = '\0';
1903 if (!ast_strlen_zero(vmu
->exit
)) {
1904 ast_copy_string(chan
->context
, vmu
->exit
, sizeof(chan
->context
));
1905 } else if (ausemacro
&& !ast_strlen_zero(chan
->macrocontext
)) {
1906 ast_copy_string(chan
->context
, chan
->macrocontext
, sizeof(chan
->context
));
1909 pbx_builtin_setvar_helper(chan
, "MINIVM_GREET_STATUS", "USEREXIT");
1911 } else if (res
== '0') { /* Check for a '0' here */
1912 if(ouseexten
|| ousemacro
) {
1913 chan
->exten
[0] = 'o';
1914 chan
->exten
[1] = '\0';
1915 if (!ast_strlen_zero(vmu
->exit
)) {
1916 ast_copy_string(chan
->context
, vmu
->exit
, sizeof(chan
->context
));
1917 } else if (ousemacro
&& !ast_strlen_zero(chan
->macrocontext
)) {
1918 ast_copy_string(chan
->context
, chan
->macrocontext
, sizeof(chan
->context
));
1920 ast_play_and_wait(chan
, "transfer");
1922 pbx_builtin_setvar_helper(chan
, "MINIVM_GREET_STATUS", "USEREXIT");
1925 } else if (res
< 0) {
1926 pbx_builtin_setvar_helper(chan
, "MINIVM_GREET_STATUS", "FAILED");
1929 pbx_builtin_setvar_helper(chan
, "MINIVM_GREET_STATUS", "SUCCESS");
1931 if(ast_test_flag(vmu
, MVM_ALLOCED
))
1935 /* Ok, we're ready to rock and roll. Return to dialplan */
1940 /*! \brief Dialplan application to delete voicemail */
1941 static int minivm_delete_exec(struct ast_channel
*chan
, void *data
)
1944 char filename
[BUFSIZ
];
1946 if (!ast_strlen_zero(data
)) {
1947 ast_copy_string(filename
, (char *) data
, sizeof(filename
));
1949 ast_channel_lock(chan
);
1950 ast_copy_string(filename
, pbx_builtin_getvar_helper(chan
, "MVM_FILENAME"), sizeof(filename
));
1951 ast_channel_unlock(chan
);
1954 if (ast_strlen_zero(filename
)) {
1955 ast_log(LOG_ERROR
, "No filename given in application arguments or channel variable MVM_FILENAME\n");
1959 /* Go ahead and delete audio files from system, they're not needed any more */
1960 /* We should look for both audio and text files here */
1961 if (ast_fileexists(filename
, NULL
, NULL
) > 0) {
1962 res
= vm_delete(filename
);
1964 ast_debug(2, "-_-_- Can't delete file: %s\n", filename
);
1965 pbx_builtin_setvar_helper(chan
, "MINIVM_DELETE_STATUS", "FAILED");
1967 ast_debug(2, "-_-_- Deleted voicemail file :: %s \n", filename
);
1968 pbx_builtin_setvar_helper(chan
, "MINIVM_DELETE_STATUS", "SUCCESS");
1971 ast_debug(2, "-_-_- Filename does not exist: %s\n", filename
);
1972 pbx_builtin_setvar_helper(chan
, "MINIVM_DELETE_STATUS", "FAILED");
1978 /*! \brief Record specific messages for voicemail account */
1979 static int minivm_accmess_exec(struct ast_channel
*chan
, void *data
)
1984 char filename
[PATH_MAX
];
1987 char *tmpptr
= NULL
;
1988 struct minivm_account
*vmu
;
1989 char *username
= argv
[0];
1990 struct ast_flags flags
= { 0 };
1991 char *opts
[OPT_ARG_ARRAY_SIZE
];
1993 char *message
= NULL
;
1994 char *prompt
= NULL
;
1998 if (ast_strlen_zero(data
)) {
1999 ast_log(LOG_ERROR
, "MinivmAccmess needs at least two arguments: account and option\n");
2002 tmpptr
= ast_strdupa((char *)data
);
2005 ast_log(LOG_ERROR
, "Out of memory\n");
2008 argc
= ast_app_separate_args(tmpptr
, ',', argv
, sizeof(argv
) / sizeof(argv
[0]));
2012 ast_log(LOG_ERROR
, "MinivmAccmess needs at least two arguments: account and option\n");
2015 if (!error
&& strlen(argv
[1]) > 1) {
2016 ast_log(LOG_ERROR
, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv
[1]);
2020 if (!error
&& ast_app_parse_options(minivm_accmess_options
, &flags
, opts
, argv
[1])) {
2021 ast_log(LOG_ERROR
, "Can't parse option %s\n", argv
[1]);
2028 ast_copy_string(tmp
, argv
[0], sizeof(tmp
));
2030 domain
= strchr(tmp
, '@');
2035 if (ast_strlen_zero(domain
) || ast_strlen_zero(username
)) {
2036 ast_log(LOG_ERROR
, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv
[0]);
2040 if(!(vmu
= find_account(domain
, username
, TRUE
))) {
2041 /* We could not find user, let's exit */
2042 ast_log(LOG_WARNING
, "Could not allocate temporary memory for '%s@%s'\n", username
, domain
);
2043 pbx_builtin_setvar_helper(chan
, "MINIVM_NOTIFY_STATUS", "FAILED");
2047 /* Answer channel if it's not already answered */
2048 if (chan
->_state
!= AST_STATE_UP
)
2051 /* Here's where the action is */
2052 if (ast_test_flag(&flags
, OPT_BUSY_GREETING
)) {
2054 prompt
= "vm-rec-busy";
2055 } else if (ast_test_flag(&flags
, OPT_UNAVAIL_GREETING
)) {
2056 message
= "unavailable";
2057 prompt
= "vm-rec-unavail";
2058 } else if (ast_test_flag(&flags
, OPT_TEMP_GREETING
)) {
2060 prompt
= "vm-temp-greeting";
2061 } else if (ast_test_flag(&flags
, OPT_NAME_GREETING
)) {
2063 prompt
= "vm-rec-name";
2065 snprintf(filename
,sizeof(filename
), "%s%s/%s/%s", MVM_SPOOL_DIR
, vmu
->domain
, vmu
->username
, message
);
2066 /* Maybe we should check the result of play_record_review ? */
2067 cmd
= play_record_review(chan
, prompt
, filename
, global_maxgreet
, default_vmformat
, 0, vmu
, &duration
, NULL
, FALSE
);
2069 ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message
, filename
, duration
);
2071 if(ast_test_flag(vmu
, MVM_ALLOCED
))
2075 /* Ok, we're ready to rock and roll. Return to dialplan */
2080 /*! \brief Append new mailbox to mailbox list from configuration file */
2081 static int create_vmaccount(char *name
, struct ast_variable
*var
, int realtime
)
2083 struct minivm_account
*vmu
;
2086 char accbuf
[BUFSIZ
];
2088 ast_debug(3, "Creating %s account for [%s]\n", realtime
? "realtime" : "static", name
);
2090 ast_copy_string(accbuf
, name
, sizeof(accbuf
));
2092 domain
= strchr(accbuf
, '@');
2097 if (ast_strlen_zero(domain
)) {
2098 ast_log(LOG_ERROR
, "No domain given for mini-voicemail account %s. Not configured.\n", name
);
2102 ast_debug(3, "Creating static account for user %s domain %s\n", username
, domain
);
2104 /* Allocate user account */
2105 vmu
= ast_calloc(1, sizeof(*vmu
));
2109 ast_copy_string(vmu
->domain
, domain
, sizeof(vmu
->domain
));
2110 ast_copy_string(vmu
->username
, username
, sizeof(vmu
->username
));
2112 populate_defaults(vmu
);
2114 ast_debug(3, "...Configuring account %s\n", name
);
2117 ast_debug(3, "---- Configuring %s = \"%s\" for account %s\n", var
->name
, var
->value
, name
);
2118 if (!strcasecmp(var
->name
, "serveremail")) {
2119 ast_copy_string(vmu
->serveremail
, var
->value
, sizeof(vmu
->serveremail
));
2120 } else if (!strcasecmp(var
->name
, "email")) {
2121 ast_copy_string(vmu
->email
, var
->value
, sizeof(vmu
->email
));
2122 } else if (!strcasecmp(var
->name
, "accountcode")) {
2123 ast_copy_string(vmu
->accountcode
, var
->value
, sizeof(vmu
->accountcode
));
2124 } else if (!strcasecmp(var
->name
, "pincode")) {
2125 ast_copy_string(vmu
->pincode
, var
->value
, sizeof(vmu
->pincode
));
2126 } else if (!strcasecmp(var
->name
, "domain")) {
2127 ast_copy_string(vmu
->domain
, var
->value
, sizeof(vmu
->domain
));
2128 } else if (!strcasecmp(var
->name
, "language")) {
2129 ast_copy_string(vmu
->language
, var
->value
, sizeof(vmu
->language
));
2130 } else if (!strcasecmp(var
->name
, "timezone")) {
2131 ast_copy_string(vmu
->zonetag
, var
->value
, sizeof(vmu
->zonetag
));
2132 } else if (!strcasecmp(var
->name
, "externnotify")) {
2133 ast_copy_string(vmu
->externnotify
, var
->value
, sizeof(vmu
->externnotify
));
2134 } else if (!strcasecmp(var
->name
, "etemplate")) {
2135 ast_copy_string(vmu
->etemplate
, var
->value
, sizeof(vmu
->etemplate
));
2136 } else if (!strcasecmp(var
->name
, "ptemplate")) {
2137 ast_copy_string(vmu
->ptemplate
, var
->value
, sizeof(vmu
->ptemplate
));
2138 } else if (!strcasecmp(var
->name
, "fullname")) {
2139 ast_copy_string(vmu
->fullname
, var
->value
, sizeof(vmu
->fullname
));
2140 } else if (!strcasecmp(var
->name
, "setvar")) {
2142 char *varname
= ast_strdupa(var
->value
);
2143 struct ast_variable
*tmpvar
;
2145 if (varname
&& (varval
= strchr(varname
, '='))) {
2148 if ((tmpvar
= ast_variable_new(varname
, varval
, ""))) {
2149 tmpvar
->next
= vmu
->chanvars
;
2150 vmu
->chanvars
= tmpvar
;
2153 } else if (!strcasecmp(var
->name
, "pager")) {
2154 ast_copy_string(vmu
->pager
, var
->value
, sizeof(vmu
->pager
));
2155 } else if (!strcasecmp(var
->name
, "volgain")) {
2156 sscanf(var
->value
, "%lf", &vmu
->volgain
);
2158 ast_log(LOG_ERROR
, "Unknown configuration option for minivm account %s : %s\n", name
, var
->name
);
2162 ast_debug(3, "...Linking account %s\n", name
);
2164 AST_LIST_LOCK(&minivm_accounts
);
2165 AST_LIST_INSERT_TAIL(&minivm_accounts
, vmu
, list
);
2166 AST_LIST_UNLOCK(&minivm_accounts
);
2168 global_stats
.voicemailaccounts
++;
2170 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)" : "");
2174 /*! \brief Free Mini Voicemail timezone */
2175 static void free_zone(struct minivm_zone
*z
)
2180 /*! \brief Clear list of timezones */
2181 static void timezone_destroy_list(void)
2183 struct minivm_zone
*this;
2185 AST_LIST_LOCK(&minivm_zones
);
2186 while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones
, list
)))
2189 AST_LIST_UNLOCK(&minivm_zones
);
2192 /*! \brief Add time zone to memory list */
2193 static int timezone_add(const char *zonename
, const char *config
)
2196 struct minivm_zone
*newzone
;
2197 char *msg_format
, *timezone
;
2199 newzone
= ast_calloc(1, sizeof(*newzone
));
2200 if (newzone
== NULL
)
2203 msg_format
= ast_strdupa(config
);
2204 if (msg_format
== NULL
) {
2205 ast_log(LOG_WARNING
, "Out of memory.\n");
2210 timezone
= strsep(&msg_format
, "|");
2212 ast_log(LOG_WARNING
, "Invalid timezone definition : %s\n", zonename
);
2217 ast_copy_string(newzone
->name
, zonename
, sizeof(newzone
->name
));
2218 ast_copy_string(newzone
->timezone
, timezone
, sizeof(newzone
->timezone
));
2219 ast_copy_string(newzone
->msg_format
, msg_format
, sizeof(newzone
->msg_format
));
2221 AST_LIST_LOCK(&minivm_zones
);
2222 AST_LIST_INSERT_TAIL(&minivm_zones
, newzone
, list
);
2223 AST_LIST_UNLOCK(&minivm_zones
);
2225 global_stats
.timezones
++;
2230 /*! \brief Read message template from file */
2231 static char *message_template_parse_filebody(const char *filename
) {
2232 char buf
[BUFSIZ
* 6];
2233 char readbuf
[BUFSIZ
];
2234 char filenamebuf
[BUFSIZ
];
2240 if (ast_strlen_zero(filename
))
2242 if (*filename
== '/')
2243 ast_copy_string(filenamebuf
, filename
, sizeof(filenamebuf
));
2245 snprintf(filenamebuf
, sizeof(filenamebuf
), "%s/%s", ast_config_AST_CONFIG_DIR
, filename
);
2247 if (!(fi
= fopen(filenamebuf
, "r"))) {
2248 ast_log(LOG_ERROR
, "Can't read message template from file: %s\n", filenamebuf
);
2252 while (fgets(readbuf
, sizeof(readbuf
), fi
)) {
2254 if (writepos
!= buf
) {
2255 *writepos
= '\n'; /* Replace EOL with new line */
2258 ast_copy_string(writepos
, readbuf
, sizeof(buf
) - (writepos
- buf
));
2259 writepos
+= strlen(readbuf
) - 1;
2262 messagebody
= ast_calloc(1, strlen(buf
+ 1));
2263 ast_copy_string(messagebody
, buf
, strlen(buf
) + 1);
2264 ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf
+ 1) );
2265 ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody
);
2270 /*! \brief Parse emailbody template from configuration file */
2271 static char *message_template_parse_emailbody(const char *configuration
)
2273 char *tmpread
, *tmpwrite
;
2274 char *emailbody
= ast_strdup(configuration
);
2276 /* substitute strings \t and \n into the apropriate characters */
2277 tmpread
= tmpwrite
= emailbody
;
2278 while ((tmpwrite
= strchr(tmpread
,'\\'))) {
2279 int len
= strlen("\n");
2280 switch (tmpwrite
[1]) {
2282 memmove(tmpwrite
+ len
, tmpwrite
+ 2, strlen(tmpwrite
+ 2) + 1);
2283 strncpy(tmpwrite
, "\n", len
);
2286 memmove(tmpwrite
+ len
, tmpwrite
+ 2, strlen(tmpwrite
+ 2) + 1);
2287 strncpy(tmpwrite
, "\t", len
);
2290 ast_log(LOG_NOTICE
, "Substitution routine does not support this character: %c\n", tmpwrite
[1]);
2292 tmpread
= tmpwrite
+ len
;
2297 /*! \brief Apply general configuration options */
2298 static int apply_general_options(struct ast_variable
*var
)
2304 if (!strcmp(var
->name
, "mailcmd")) {
2305 ast_copy_string(global_mailcmd
, var
->value
, sizeof(global_mailcmd
)); /* User setting */
2306 } else if (!strcmp(var
->name
, "maxgreet")) {
2307 global_maxgreet
= atoi(var
->value
);
2308 } else if (!strcmp(var
->name
, "maxsilence")) {
2309 global_maxsilence
= atoi(var
->value
);
2310 if (global_maxsilence
> 0)
2311 global_maxsilence
*= 1000;
2312 } else if (!strcmp(var
->name
, "logfile")) {
2313 if (!ast_strlen_zero(var
->value
) ) {
2314 if(*(var
->value
) == '/')
2315 ast_copy_string(global_logfile
, var
->value
, sizeof(global_logfile
));
2317 snprintf(global_logfile
, sizeof(global_logfile
), "%s/%s", ast_config_AST_LOG_DIR
, var
->value
);
2319 } else if (!strcmp(var
->name
, "externnotify")) {
2320 /* External voicemail notify application */
2321 ast_copy_string(global_externnotify
, var
->value
, sizeof(global_externnotify
));
2322 } else if (!strcmp(var
->name
, "silencetreshold")) {
2323 /* Silence treshold */
2324 global_silencethreshold
= atoi(var
->value
);
2325 } else if (!strcmp(var
->name
, "maxmessage")) {
2327 if (sscanf(var
->value
, "%d", &x
) == 1) {
2328 global_vmmaxmessage
= x
;
2331 ast_log(LOG_WARNING
, "Invalid max message time length\n");
2333 } else if (!strcmp(var
->name
, "minmessage")) {
2335 if (sscanf(var
->value
, "%d", &x
) == 1) {
2336 global_vmminmessage
= x
;
2337 if (global_maxsilence
<= global_vmminmessage
)
2338 ast_log(LOG_WARNING
, "maxsilence should be less than minmessage or you may get empty messages\n");
2341 ast_log(LOG_WARNING
, "Invalid min message time length\n");
2343 } else if (!strcmp(var
->name
, "format")) {
2344 ast_copy_string(default_vmformat
, var
->value
, sizeof(default_vmformat
));
2345 } else if (!strcmp(var
->name
, "review")) {
2346 ast_set2_flag((&globalflags
), ast_true(var
->value
), MVM_REVIEW
);
2347 } else if (!strcmp(var
->name
, "operator")) {
2348 ast_set2_flag((&globalflags
), ast_true(var
->value
), MVM_OPERATOR
);
2355 /*! \brief Load minivoicemail configuration */
2356 static int load_config(int reload
)
2358 struct ast_config
*cfg
;
2359 struct ast_variable
*var
;
2361 const char *chanvar
;
2363 struct minivm_template
*template;
2364 struct ast_flags config_flags
= { reload
? CONFIG_FLAG_FILEUNCHANGED
: 0 };
2366 cfg
= ast_config_load(VOICEMAIL_CONFIG
, config_flags
);
2367 if (cfg
== CONFIG_STATUS_FILEUNCHANGED
)
2370 ast_mutex_lock(&minivmlock
);
2372 /* Destroy lists to reconfigure */
2373 message_destroy_list(); /* Destroy list of voicemail message templates */
2374 timezone_destroy_list(); /* Destroy list of timezones */
2375 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2376 ast_debug(2, "Destroyed memory objects...\n");
2378 /* First, set some default settings */
2379 global_externnotify
[0] = '\0';
2380 global_logfile
[0] = '\0';
2381 global_vmmaxmessage
= 2000;
2382 global_maxgreet
= 2000;
2383 global_vmminmessage
= 0;
2384 strcpy(global_mailcmd
, SENDMAIL
);
2385 global_maxsilence
= 0;
2386 global_saydurationminfo
= 2;
2387 ast_copy_string(default_vmformat
, "wav", sizeof(default_vmformat
));
2388 ast_set2_flag((&globalflags
), FALSE
, MVM_REVIEW
);
2389 ast_set2_flag((&globalflags
), FALSE
, MVM_OPERATOR
);
2390 strcpy(global_charset
, "ISO-8859-1");
2391 /* Reset statistics */
2392 memset(&global_stats
, 0, sizeof(global_stats
));
2393 global_stats
.reset
= ast_tvnow();
2395 global_silencethreshold
= ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE
);
2397 /* Make sure we could load configuration file */
2399 ast_log(LOG_WARNING
, "Failed to load configuration file. Module activated with default settings.\n");
2400 ast_mutex_unlock(&minivmlock
);
2404 ast_debug(2, "-_-_- Loaded configuration file, now parsing\n");
2406 /* General settings */
2408 cat
= ast_category_browse(cfg
, NULL
);
2410 ast_debug(3, "-_-_- Found configuration section [%s]\n", cat
);
2411 if (!strcasecmp(cat
, "general")) {
2412 /* Nothing right now */
2413 error
+= apply_general_options(ast_variable_browse(cfg
, cat
));
2414 } else if (!strncasecmp(cat
, "template-", 9)) {
2416 char *name
= cat
+ 9;
2418 /* Now build and link template to list */
2419 error
+= message_template_build(name
, ast_variable_browse(cfg
, cat
));
2421 var
= ast_variable_browse(cfg
, cat
);
2422 if (!strcasecmp(cat
, "zonemessages")) {
2423 /* Timezones in this context */
2425 timezone_add(var
->name
, var
->value
);
2429 /* Create mailbox from this */
2430 error
+= create_vmaccount(cat
, var
, FALSE
);
2433 /* Find next section in configuration file */
2434 cat
= ast_category_browse(cfg
, cat
);
2437 /* Configure the default email template */
2438 message_template_build("email-default", NULL
);
2439 template = message_template_find("email-default");
2441 /* Load date format config for voicemail mail */
2442 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "emaildateformat")))
2443 ast_copy_string(template->dateformat
, chanvar
, sizeof(template->dateformat
));
2444 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "emailfromstring")))
2445 ast_copy_string(template->fromaddress
, chanvar
, sizeof(template->fromaddress
));
2446 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "emailaaddress")))
2447 ast_copy_string(template->serveremail
, chanvar
, sizeof(template->serveremail
));
2448 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "emailcharset")))
2449 ast_copy_string(template->charset
, chanvar
, sizeof(template->charset
));
2450 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "emailsubject")))
2451 ast_copy_string(template->subject
, chanvar
, sizeof(template->subject
));
2452 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "emailbody")))
2453 template->body
= message_template_parse_emailbody(chanvar
);
2454 template->attachment
= TRUE
;
2456 message_template_build("pager-default", NULL
);
2457 template = message_template_find("pager-default");
2458 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "pagerfromstring")))
2459 ast_copy_string(template->fromaddress
, chanvar
, sizeof(template->fromaddress
));
2460 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "pageraddress")))
2461 ast_copy_string(template->serveremail
, chanvar
, sizeof(template->serveremail
));
2462 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "pagercharset")))
2463 ast_copy_string(template->charset
, chanvar
, sizeof(template->charset
));
2464 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "pagersubject")))
2465 ast_copy_string(template->subject
, chanvar
,sizeof(template->subject
));
2466 if ((chanvar
= ast_variable_retrieve(cfg
, "general", "pagerbody")))
2467 template->body
= message_template_parse_emailbody(chanvar
);
2468 template->attachment
= FALSE
;
2471 ast_log(LOG_ERROR
, "--- A total of %d errors found in mini-voicemail configuration\n", error
);
2473 ast_mutex_unlock(&minivmlock
);
2474 ast_config_destroy(cfg
);
2476 /* Close log file if it's open and disabled */
2478 fclose(minivmlogfile
);
2480 /* Open log file if it's enabled */
2481 if(!ast_strlen_zero(global_logfile
)) {
2482 minivmlogfile
= fopen(global_logfile
, "a");
2484 ast_log(LOG_ERROR
, "Failed to open minivm log file %s : %s\n", global_logfile
, strerror(errno
));
2486 ast_debug(3, "-_-_- Opened log file %s \n", global_logfile
);
2492 /*! \brief CLI routine for listing templates */
2493 static char *handle_minivm_list_templates(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
2495 struct minivm_template
*this;
2496 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2501 e
->command
= "minivm list templates";
2503 "Usage: minivm list templates\n"
2504 " Lists message templates for e-mail, paging and IM\n";
2511 return CLI_SHOWUSAGE
;
2513 AST_LIST_LOCK(&message_templates
);
2514 if (AST_LIST_EMPTY(&message_templates
)) {
2515 ast_cli(a
->fd
, "There are no message templates defined\n");
2516 AST_LIST_UNLOCK(&message_templates
);
2519 ast_cli(a
->fd
, HVLT_OUTPUT_FORMAT
, "Template name", "Charset", "Locale", "Attach media", "Subject");
2520 ast_cli(a
->fd
, HVLT_OUTPUT_FORMAT
, "-------------", "-------", "------", "------------", "-------");
2521 AST_LIST_TRAVERSE(&message_templates
, this, list
) {
2522 ast_cli(a
->fd
, HVLT_OUTPUT_FORMAT
, this->name
,
2523 this->charset
? this->charset
: "-",
2524 this->locale
? this->locale
: "-",
2525 this->attachment
? "Yes" : "No",
2526 this->subject
? this->subject
: "-");
2529 AST_LIST_UNLOCK(&message_templates
);
2530 ast_cli(a
->fd
, "\n * Total: %d minivoicemail message templates\n", count
);
2534 static char *complete_minivm_show_users(const char *line
, const char *word
, int pos
, int state
)
2538 struct minivm_account
*vmu
;
2539 const char *domain
= "";
2541 /* 0 - voicemail; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2545 return (state
== 0) ? ast_strdup("for") : NULL
;
2546 wordlen
= strlen(word
);
2547 AST_LIST_TRAVERSE(&minivm_accounts
, vmu
, list
) {
2548 if (!strncasecmp(word
, vmu
->domain
, wordlen
)) {
2549 if (domain
&& strcmp(domain
, vmu
->domain
) && ++which
> state
)
2550 return ast_strdup(vmu
->domain
);
2551 /* ignore repeated domains ? */
2552 domain
= vmu
->domain
;
2558 /*! \brief CLI command to list voicemail accounts */
2559 static char *handle_minivm_show_users(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
2561 struct minivm_account
*vmu
;
2562 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
2567 e
->command
= "minivm list accounts";
2569 "Usage: minivm list accounts\n"
2570 " Lists all mailboxes currently set up\n";
2573 return complete_minivm_show_users(a
->line
, a
->word
, a
->pos
, a
->n
);
2576 if ((a
->argc
< 3) || (a
->argc
> 5) || (a
->argc
== 4))
2577 return CLI_SHOWUSAGE
;
2578 if ((a
->argc
== 5) && strcmp(a
->argv
[3],"for"))
2579 return CLI_SHOWUSAGE
;
2581 AST_LIST_LOCK(&minivm_accounts
);
2582 if (AST_LIST_EMPTY(&minivm_accounts
)) {
2583 ast_cli(a
->fd
, "There are no voicemail users currently defined\n");
2584 AST_LIST_UNLOCK(&minivm_accounts
);
2587 ast_cli(a
->fd
, HMSU_OUTPUT_FORMAT
, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2588 ast_cli(a
->fd
, HMSU_OUTPUT_FORMAT
, "----", "----------", "----------", "----", "------", "---------");
2589 AST_LIST_TRAVERSE(&minivm_accounts
, vmu
, list
) {
2591 if ((a
->argc
== 3) || ((a
->argc
== 5) && !strcmp(a
->argv
[4], vmu
->domain
))) {
2593 snprintf(tmp
, sizeof(tmp
), "%s@%s", vmu
->username
, vmu
->domain
);
2594 ast_cli(a
->fd
, HMSU_OUTPUT_FORMAT
, tmp
, vmu
->etemplate
? vmu
->etemplate
: "-",
2595 vmu
->ptemplate
? vmu
->ptemplate
: "-",
2596 vmu
->zonetag
? vmu
->zonetag
: "-",
2597 vmu
->attachfmt
? vmu
->attachfmt
: "-",
2601 AST_LIST_UNLOCK(&minivm_accounts
);
2602 ast_cli(a
->fd
, "\n * Total: %d minivoicemail accounts\n", count
);
2606 /*! \brief Show a list of voicemail zones in the CLI */
2607 static char *handle_minivm_show_zones(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
2609 struct minivm_zone
*zone
;
2610 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
2611 char *res
= CLI_SUCCESS
;
2615 e
->command
= "minivm list zones";
2617 "Usage: minivm list zones\n"
2618 " Lists zone message formats\n";
2624 if (a
->argc
!= e
->args
)
2625 return CLI_SHOWUSAGE
;
2627 AST_LIST_LOCK(&minivm_zones
);
2628 if (!AST_LIST_EMPTY(&minivm_zones
)) {
2629 ast_cli(a
->fd
, HMSZ_OUTPUT_FORMAT
, "Zone", "Timezone", "Message Format");
2630 ast_cli(a
->fd
, HMSZ_OUTPUT_FORMAT
, "----", "--------", "--------------");
2631 AST_LIST_TRAVERSE(&minivm_zones
, zone
, list
) {
2632 ast_cli(a
->fd
, HMSZ_OUTPUT_FORMAT
, zone
->name
, zone
->timezone
, zone
->msg_format
);
2635 ast_cli(a
->fd
, "There are no voicemail zones currently defined\n");
2638 AST_LIST_UNLOCK(&minivm_zones
);
2643 /*! \brief CLI Show settings */
2644 static char *handle_minivm_show_settings(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
2648 e
->command
= "minivm show settings";
2650 "Usage: minivm show settings\n"
2651 " Display Mini-Voicemail general settings\n";
2657 ast_cli(a
->fd
, "* Mini-Voicemail general settings\n");
2658 ast_cli(a
->fd
, " -------------------------------\n");
2659 ast_cli(a
->fd
, "\n");
2660 ast_cli(a
->fd
, " Mail command (shell): %s\n", global_mailcmd
);
2661 ast_cli(a
->fd
, " Max silence: %d\n", global_maxsilence
);
2662 ast_cli(a
->fd
, " Silence threshold: %d\n", global_silencethreshold
);
2663 ast_cli(a
->fd
, " Max message length (secs): %d\n", global_vmmaxmessage
);
2664 ast_cli(a
->fd
, " Min message length (secs): %d\n", global_vmminmessage
);
2665 ast_cli(a
->fd
, " Default format: %s\n", default_vmformat
);
2666 ast_cli(a
->fd
, " Extern notify (shell): %s\n", global_externnotify
);
2667 ast_cli(a
->fd
, " Logfile: %s\n", global_logfile
[0] ? global_logfile
: "<disabled>");
2668 ast_cli(a
->fd
, " Operator exit: %s\n", ast_test_flag(&globalflags
, MVM_OPERATOR
) ? "Yes" : "No");
2669 ast_cli(a
->fd
, " Message review: %s\n", ast_test_flag(&globalflags
, MVM_REVIEW
) ? "Yes" : "No");
2671 ast_cli(a
->fd
, "\n");
2675 /*! \brief Show stats */
2676 static char *handle_minivm_show_stats(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
2684 e
->command
= "minivm show stats";
2686 "Usage: minivm show stats\n"
2687 " Display Mini-Voicemail counters\n";
2693 ast_cli(a
->fd
, "* Mini-Voicemail statistics\n");
2694 ast_cli(a
->fd
, " -------------------------\n");
2695 ast_cli(a
->fd
, "\n");
2696 ast_cli(a
->fd
, " Voicemail accounts: %5d\n", global_stats
.voicemailaccounts
);
2697 ast_cli(a
->fd
, " Templates: %5d\n", global_stats
.templates
);
2698 ast_cli(a
->fd
, " Timezones: %5d\n", global_stats
.timezones
);
2699 if (global_stats
.receivedmessages
== 0) {
2700 ast_cli(a
->fd
, " Received messages since last reset: <none>\n");
2702 ast_cli(a
->fd
, " Received messages since last reset: %d\n", global_stats
.receivedmessages
);
2703 ast_localtime(&global_stats
.lastreceived
, &time
, NULL
);
2704 ast_strftime(buf
, sizeof(buf
), "%a %b %e %r %Z %Y", &time
);
2705 ast_cli(a
->fd
, " Last received voicemail: %s\n", buf
);
2707 ast_localtime(&global_stats
.reset
, &time
, NULL
);
2708 ast_strftime(buf
, sizeof(buf
), "%a %b %e %r %Z %Y", &time
);
2709 ast_cli(a
->fd
, " Last reset: %s\n", buf
);
2711 ast_cli(a
->fd
, "\n");
2715 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
2716 static int minivm_account_func_read(struct ast_channel
*chan
, const char *cmd
, char *data
, char *buf
, size_t len
)
2718 struct minivm_account
*vmu
;
2719 char *username
, *domain
, *colname
;
2721 if (!(username
= ast_strdupa(data
))) {
2722 ast_log(LOG_ERROR
, "Memory Error!\n");
2726 if ((colname
= strchr(username
, ':'))) {
2732 if ((domain
= strchr(username
, '@'))) {
2736 if (ast_strlen_zero(username
) || ast_strlen_zero(domain
)) {
2737 ast_log(LOG_ERROR
, "This function needs a username and a domain: username@domain\n");
2741 if (!(vmu
= find_account(domain
, username
, TRUE
)))
2744 if (!strcasecmp(colname
, "hasaccount")) {
2745 ast_copy_string(buf
, (ast_test_flag(vmu
, MVM_ALLOCED
) ? "0" : "1"), len
);
2746 } else if (!strcasecmp(colname
, "fullname")) {
2747 ast_copy_string(buf
, vmu
->fullname
, len
);
2748 } else if (!strcasecmp(colname
, "email")) {
2749 if (!ast_strlen_zero(vmu
->email
))
2750 ast_copy_string(buf
, vmu
->email
, len
);
2752 snprintf(buf
, len
, "%s@%s", vmu
->username
, vmu
->domain
);
2753 } else if (!strcasecmp(colname
, "pager")) {
2754 ast_copy_string(buf
, vmu
->pager
, len
);
2755 } else if (!strcasecmp(colname
, "etemplate")) {
2756 if (!ast_strlen_zero(vmu
->etemplate
))
2757 ast_copy_string(buf
, vmu
->etemplate
, len
);
2759 ast_copy_string(buf
, "email-default", len
);
2760 } else if (!strcasecmp(colname
, "language")) {
2761 ast_copy_string(buf
, vmu
->language
, len
);
2762 } else if (!strcasecmp(colname
, "timezone")) {
2763 ast_copy_string(buf
, vmu
->zonetag
, len
);
2764 } else if (!strcasecmp(colname
, "ptemplate")) {
2765 if (!ast_strlen_zero(vmu
->ptemplate
))
2766 ast_copy_string(buf
, vmu
->ptemplate
, len
);
2768 ast_copy_string(buf
, "email-default", len
);
2769 } else if (!strcasecmp(colname
, "accountcode")) {
2770 ast_copy_string(buf
, vmu
->accountcode
, len
);
2771 } else if (!strcasecmp(colname
, "pincode")) {
2772 ast_copy_string(buf
, vmu
->pincode
, len
);
2773 } else if (!strcasecmp(colname
, "path")) {
2774 check_dirpath(buf
, len
, vmu
->domain
, vmu
->username
, NULL
);
2775 } else { /* Look in channel variables */
2776 struct ast_variable
*var
;
2779 for (var
= vmu
->chanvars
; var
; var
= var
->next
)
2780 if (!strcmp(var
->name
, colname
)) {
2781 ast_copy_string(buf
, var
->value
, len
);
2787 if(ast_test_flag(vmu
, MVM_ALLOCED
))
2793 /*! \brief lock directory
2795 only return failure if ast_lock_path returns 'timeout',
2796 not if the path does not exist or any other reason
2798 static int vm_lock_path(const char *path
)
2800 switch (ast_lock_path(path
)) {
2801 case AST_LOCK_TIMEOUT
:
2808 /*! \brief Access counter file, lock directory, read and possibly write it again changed
2809 \param directory Directory to crate file in
2810 \param countername filename
2811 \param value If set to zero, we only read the variable
2812 \param operand 0 to read, 1 to set new value, 2 to change
2813 \return -1 on error, otherwise counter value
2815 static int access_counter_file(char *directory
, char *countername
, int value
, int operand
)
2817 char filename
[BUFSIZ
];
2818 char readbuf
[BUFSIZ
];
2820 int old
= 0, counter
= 0;
2822 /* Lock directory */
2823 if (vm_lock_path(directory
)) {
2824 return -1; /* Could not lock directory */
2826 snprintf(filename
, sizeof(filename
), "%s/%s.counter", directory
, countername
);
2828 counterfile
= fopen(filename
, "r");
2830 if(fgets(readbuf
, sizeof(readbuf
), counterfile
)) {
2831 ast_debug(3, "Read this string from counter file: %s\n", readbuf
);
2832 old
= counter
= atoi(readbuf
);
2834 fclose(counterfile
);
2838 case 0: /* Read only */
2839 ast_unlock_path(directory
);
2840 ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory
, countername
, counter
);
2843 case 1: /* Set new value */
2846 case 2: /* Change value */
2848 if (counter
< 0) /* Don't allow counters to fall below zero */
2853 /* Now, write the new value to the file */
2854 counterfile
= fopen(filename
, "w");
2856 ast_log(LOG_ERROR
, "Could not open counter file for writing : %s - %s\n", filename
, strerror(errno
));
2857 ast_unlock_path(directory
);
2858 return -1; /* Could not open file for writing */
2860 fprintf(counterfile
, "%d\n\n", counter
);
2861 fclose(counterfile
);
2862 ast_unlock_path(directory
);
2863 ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory
, countername
, old
, counter
);
2867 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
2868 static int minivm_counter_func_read(struct ast_channel
*chan
, const char *cmd
, char *data
, char *buf
, size_t len
)
2870 char *username
, *domain
, *countername
;
2871 struct minivm_account
*vmu
= NULL
;
2872 char userpath
[BUFSIZ
];
2877 if (!(username
= ast_strdupa(data
))) { /* Copy indata to local buffer */
2878 ast_log(LOG_WARNING
, "Memory error!\n");
2881 if ((countername
= strchr(username
, ':'))) {
2882 *countername
= '\0';
2886 if ((domain
= strchr(username
, '@'))) {
2891 /* If we have neither username nor domain now, let's give up */
2892 if (ast_strlen_zero(username
) && ast_strlen_zero(domain
)) {
2893 ast_log(LOG_ERROR
, "No account given\n");
2897 if (ast_strlen_zero(countername
)) {
2898 ast_log(LOG_ERROR
, "This function needs two arguments: Account:countername\n");
2902 /* We only have a domain, no username */
2903 if (!ast_strlen_zero(username
) && ast_strlen_zero(domain
)) {
2908 /* If we can't find account or if the account is temporary, return. */
2909 if (!ast_strlen_zero(username
) && !(vmu
= find_account(domain
, username
, FALSE
))) {
2910 ast_log(LOG_ERROR
, "Minivm account does not exist: %s@%s\n", username
, domain
);
2914 create_dirpath(userpath
, sizeof(userpath
), domain
, username
, NULL
);
2916 /* We have the path, now read the counter file */
2917 res
= access_counter_file(userpath
, countername
, 0, 0);
2919 snprintf(buf
, len
, "%d", res
);
2923 /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
2924 static int minivm_counter_func_write(struct ast_channel
*chan
, const char *cmd
, char *data
, const char *value
)
2926 char *username
, *domain
, *countername
, *operand
;
2927 char userpath
[BUFSIZ
];
2928 struct minivm_account
*vmu
;
2934 change
= atoi(value
);
2936 if (!(username
= ast_strdupa(data
))) { /* Copy indata to local buffer */
2937 ast_log(LOG_WARNING
, "Memory error!\n");
2941 if ((countername
= strchr(username
, ':'))) {
2942 *countername
= '\0';
2945 if ((operand
= strchr(countername
, ':'))) {
2950 if ((domain
= strchr(username
, '@'))) {
2955 /* If we have neither username nor domain now, let's give up */
2956 if (ast_strlen_zero(username
) && ast_strlen_zero(domain
)) {
2957 ast_log(LOG_ERROR
, "No account given\n");
2961 /* We only have a domain, no username */
2962 if (!ast_strlen_zero(username
) && ast_strlen_zero(domain
)) {
2967 if (ast_strlen_zero(operand
) || ast_strlen_zero(countername
)) {
2968 ast_log(LOG_ERROR
, "Writing to this function requires three arguments: Account:countername:operand\n");
2972 /* If we can't find account or if the account is temporary, return. */
2973 if (!ast_strlen_zero(username
) && !(vmu
= find_account(domain
, username
, FALSE
))) {
2974 ast_log(LOG_ERROR
, "Minivm account does not exist: %s@%s\n", username
, domain
);
2978 create_dirpath(userpath
, sizeof(userpath
), domain
, username
, NULL
);
2979 /* Now, find out our operator */
2980 if (*operand
== 'i') /* Increment */
2982 else if (*operand
== 'd') {
2983 change
= change
* -1;
2985 } else if (*operand
== 's')
2988 ast_log(LOG_ERROR
, "Unknown operator: %s\n", operand
);
2992 /* We have the path, now read the counter file */
2993 access_counter_file(userpath
, countername
, change
, operation
);
2998 /*! \brief CLI commands for Mini-voicemail */
2999 static struct ast_cli_entry cli_minivm
[] = {
3000 AST_CLI_DEFINE(handle_minivm_show_users
, "List defined mini-voicemail boxes"),
3001 AST_CLI_DEFINE(handle_minivm_show_zones
, "List zone message formats"),
3002 AST_CLI_DEFINE(handle_minivm_list_templates
, "List message templates"),
3003 AST_CLI_DEFINE(handle_minivm_reload
, "Reload Mini-voicemail configuration"),
3004 AST_CLI_DEFINE(handle_minivm_show_stats
, "Show some mini-voicemail statistics"),
3005 AST_CLI_DEFINE(handle_minivm_show_settings
, "Show mini-voicemail general settings"),
3008 static struct ast_custom_function minivm_counter_function
= {
3009 .name
= "MINIVMCOUNTER",
3010 .synopsis
= "Reads or sets counters for MiniVoicemail message",
3011 .syntax
= "MINIVMCOUNTER(<account>:name[:operand])",
3012 .read
= minivm_counter_func_read
,
3013 .write
= minivm_counter_func_write
,
3014 .desc
= "Valid operands for changing the value of a counter when assigning a value are:\n"
3015 "- i Increment by value\n"
3016 "- d Decrement by value\n"
3017 "- s Set to value\n"
3018 "\nThe counters never goes below zero.\n"
3019 "- The name of the counter is a string, up to 10 characters\n"
3020 "- If account is given and it exists, the counter is specific for the account\n"
3021 "- If account is a domain and the domain directory exists, counters are specific for a domain\n"
3022 "The operation is atomic and the counter is locked while changing the value\n"
3023 "\nThe counters are stored as text files in the minivm account directories. It might be better to use\n"
3024 "realtime functions if you are using a database to operate your Asterisk\n",
3027 static struct ast_custom_function minivm_account_function
= {
3028 .name
= "MINIVMACCOUNT",
3029 .synopsis
= "Gets MiniVoicemail account information",
3030 .syntax
= "MINIVMACCOUNT(<account>:item)",
3031 .read
= minivm_account_func_read
,
3032 .desc
= "Valid items are:\n"
3033 "- path Path to account mailbox (if account exists, otherwise temporary mailbox)\n"
3034 "- hasaccount 1 if static Minivm account exists, 0 otherwise\n"
3035 "- fullname Full name of account owner\n"
3036 "- email Email address used for account\n"
3037 "- etemplate E-mail template for account (default template if none is configured)\n"
3038 "- ptemplate Pager template for account (default template if none is configured)\n"
3039 "- accountcode Account code for voicemail account\n"
3040 "- pincode Pin code for voicemail account\n"
3041 "- timezone Time zone for voicemail account\n"
3042 "- language Language for voicemail account\n"
3043 "- <channel variable name> Channel variable value (set in configuration for account)\n"
3047 /*! \brief Load mini voicemail module */
3048 static int load_module(void)
3052 res
= ast_register_application(app_minivm_record
, minivm_record_exec
, synopsis_minivm_record
, descrip_minivm_record
);
3053 res
= ast_register_application(app_minivm_greet
, minivm_greet_exec
, synopsis_minivm_greet
, descrip_minivm_greet
);
3054 res
= ast_register_application(app_minivm_notify
, minivm_notify_exec
, synopsis_minivm_notify
, descrip_minivm_notify
);
3055 res
= ast_register_application(app_minivm_delete
, minivm_delete_exec
, synopsis_minivm_delete
, descrip_minivm_delete
);
3056 res
= ast_register_application(app_minivm_accmess
, minivm_accmess_exec
, synopsis_minivm_accmess
, descrip_minivm_accmess
);
3058 ast_custom_function_register(&minivm_account_function
);
3059 ast_custom_function_register(&minivm_counter_function
);
3063 if ((res
= load_config(0)))
3066 ast_cli_register_multiple(cli_minivm
, sizeof(cli_minivm
)/sizeof(cli_minivm
[0]));
3068 /* compute the location of the voicemail spool directory */
3069 snprintf(MVM_SPOOL_DIR
, sizeof(MVM_SPOOL_DIR
), "%s/voicemail/", ast_config_AST_SPOOL_DIR
);
3074 /*! \brief Reload mini voicemail module */
3075 static int reload(void)
3077 return(load_config(1));
3080 /*! \brief Reload cofiguration */
3081 static char *handle_minivm_reload(struct ast_cli_entry
*e
, int cmd
, struct ast_cli_args
*a
)
3086 e
->command
= "minivm reload";
3088 "Usage: minivm reload\n"
3089 " Reload mini-voicemail configuration and reset statistics\n";
3096 ast_cli(a
->fd
, "\n-- Mini voicemail re-configured \n");
3100 /*! \brief Unload mini voicemail module */
3101 static int unload_module(void)
3105 res
= ast_unregister_application(app_minivm_record
);
3106 res
|= ast_unregister_application(app_minivm_greet
);
3107 res
|= ast_unregister_application(app_minivm_notify
);
3108 res
|= ast_unregister_application(app_minivm_delete
);
3109 res
|= ast_unregister_application(app_minivm_accmess
);
3110 ast_cli_unregister_multiple(cli_minivm
, sizeof(cli_minivm
)/sizeof(cli_minivm
[0]));
3111 ast_custom_function_unregister(&minivm_account_function
);
3112 ast_custom_function_unregister(&minivm_counter_function
);
3114 message_destroy_list(); /* Destroy list of voicemail message templates */
3115 timezone_destroy_list(); /* Destroy list of timezones */
3116 vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
3122 AST_MODULE_INFO(ASTERISK_GPL_KEY
, AST_MODFLAG_DEFAULT
, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3123 .load
= load_module
,
3124 .unload
= unload_module
,