Asterisk - The Open Source Telephony Project  21.4.1
app_minivm.c
Go to the documentation of this file.
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  * and Edvina AB, Sollentuna, Sweden
6  *
7  * Mark Spencer <markster@digium.com> (Comedian Mail)
8  * and Olle E. Johansson, Edvina.net <oej@edvina.net> (Mini-Voicemail changes)
9  *
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.
15  *
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.
19  */
20 
21 /*! \file
22  *
23  * \brief MiniVoiceMail - A Minimal Voicemail System for Asterisk
24  *
25  * A voicemail system in small building blocks, working together
26  * based on the Comedian Mail voicemail system (app_voicemail.c).
27  *
28  * \par See also
29  * \arg \ref minivm.conf "Config_minivm"
30  * \arg \ref App_minivm
31  *
32  * \ingroup applications
33  *
34  * \page App_minivm Asterisk Mini-voicemail - A minimal voicemail system
35  *
36  * This is a minimal voicemail system, building blocks for something
37  * else. It is built for multi-language systems.
38  * The current version is focused on accounts where voicemail is
39  * forwarded to users in e-mail. It's work in progress, with loosed ends hanging
40  * around from the old voicemail system and it's configuration.
41  *
42  * Hopefully, we can expand this to be a full replacement of voicemail() and voicemailmain()
43  * in the future.
44  *
45  * Dialplan applications
46  * - minivmRecord - record voicemail and send as e-mail ( \ref minivm_record_exec() )
47  * - minivmGreet - Play user's greeting or default greeting ( \ref minivm_greet_exec() )
48  * - minivmNotify - Notify user of message ( \ref minivm_notify_exec() )
49  * - minivmDelete - Delete voicemail message ( \ref minivm_delete_exec() )
50  * - minivmAccMess - Record personal messages (busy | unavailable | temporary)
51  *
52  * Dialplan functions
53  * - MINIVMACCOUNT() - A dialplan function
54  * - MINIVMCOUNTER() - Manage voicemail-related counters for accounts or domains
55  *
56  * CLI Commands
57  * - minivm list accounts
58  * - minivm list zones
59  * - minivm list templates
60  * - minivm show stats
61  * - minivm show settings
62  *
63  * Some notes
64  * - General configuration in minivm.conf
65  * - Users in realtime or configuration file
66  * - Or configured on the command line with just the e-mail address
67  *
68  * Voicemail accounts are identified by userid and domain
69  *
70  * Language codes are like setlocale - langcode_countrycode
71  * \note Don't use language codes like the rest of Asterisk, two letter countrycode. Use
72  * language_country like setlocale().
73  *
74  * Examples:
75  * - Swedish, Sweden sv_se
76  * - Swedish, Finland sv_fi
77  * - English, USA en_us
78  * - English, GB en_gb
79  *
80  * \par See also
81  * \arg \ref minivm.conf "Config_minivm"
82  * \arg \ref Config_minivm_examples
83  * \arg \ref Minivm_directories
84  * \arg \ref app_minivm.c
85  * \arg Comedian mail: app_voicemail.c
86  * \arg \ref minivm_accmess_exec
87  * \arg \ref minivm_greet_exec
88  * \arg \ref minivm_record_exec
89  * \arg \ref minivm_delete_exec
90  * \arg \ref minivm_notify_exec
91  *
92  * \arg \ref App_minivm_todo
93  */
94 /*! \page Minivm_directories Asterisk Mini-Voicemail Directory structure
95  *
96  * The directory structure for storing voicemail
97  * - AST_SPOOL_DIR - usually /var/spool/asterisk (configurable in asterisk.conf)
98  * - MVM_SPOOL_DIR - should be configurable, usually AST_SPOOL_DIR/voicemail
99  * - Domain MVM_SPOOL_DIR/domain
100  * - Username MVM_SPOOL_DIR/domain/username
101  * - /greet : Recording of account owner's name
102  * - /busy : Busy message
103  * - /unavailable : Unavailable message
104  * - /temp : Temporary message
105  *
106  * For account anita@localdomain.xx the account directory would as a default be
107  * \b /var/spool/asterisk/voicemail/localdomain.xx/anita
108  *
109  * To avoid transcoding, these sound files should be converted into several formats
110  * They are recorded in the format closest to the incoming streams
111  *
112  *
113  * Back: \ref App_minivm
114  */
115 
116 /*!
117  * \page minivm.conf minivm.conf
118  * \verbinclude minivm.conf.sample
119  *
120  * Back: \ref App_minivm
121  */
122 
123 /*! \page Config_minivm_examples Example dialplan for Mini-Voicemail
124  * \section Example dialplan scripts for Mini-Voicemail
125  * \verbinclude extensions_minivm.conf.sample
126  *
127  * Back: \ref App_minivm
128  */
129 
130 /*! \page App_minivm_todo Asterisk Mini-Voicemail - todo
131  * - configure accounts from AMI?
132  * - test, test, test, test
133  * - fix "vm-theextensionis.gsm" voiceprompt from Allison in various formats
134  * "The extension you are calling"
135  * - For trunk, consider using channel storage for information passing between small applications
136  * - Set default directory for voicemail
137  * - New app for creating directory for account if it does not exist
138  * - Re-insert code for IMAP storage at some point
139  * - Jabber integration for notifications
140  * - Figure out how to handle video in voicemail
141  * - Integration with the HTTP server
142  * - New app for moving messages between mailboxes, and optionally mark it as "new"
143  *
144  * For Asterisk 1.4/trunk
145  * - Use string fields for minivm_account
146  *
147  * Back: \ref App_minivm
148  */
149 
150 /*** MODULEINFO
151  <support_level>extended</support_level>
152  ***/
153 
154 #include "asterisk.h"
155 
156 #include <ctype.h>
157 #include <sys/time.h>
158 #include <sys/stat.h>
159 #include <sys/mman.h>
160 #include <time.h>
161 #include <dirent.h>
162 #include <locale.h>
163 
164 #include "asterisk/paths.h" /* use various paths */
165 #include "asterisk/lock.h"
166 #include "asterisk/file.h"
167 #include "asterisk/channel.h"
168 #include "asterisk/pbx.h"
169 #include "asterisk/config.h"
170 #include "asterisk/say.h"
171 #include "asterisk/module.h"
172 #include "asterisk/app.h"
173 #include "asterisk/mwi.h"
174 #include "asterisk/dsp.h"
175 #include "asterisk/localtime.h"
176 #include "asterisk/cli.h"
177 #include "asterisk/utils.h"
178 #include "asterisk/linkedlists.h"
179 #include "asterisk/callerid.h"
180 #include "asterisk/stasis.h"
181 #include "asterisk/stasis_channels.h"
182 #include "asterisk/json.h"
183 
184 /*** DOCUMENTATION
185 <application name="MinivmRecord" language="en_US">
186  <synopsis>
187  Receive Mini-Voicemail and forward via e-mail.
188  </synopsis>
189  <syntax>
190  <parameter name="mailbox" required="true" argsep="@">
191  <argument name="username" required="true">
192  <para>Voicemail username</para>
193  </argument>
194  <argument name="domain" required="true">
195  <para>Voicemail domain</para>
196  </argument>
197  </parameter>
198  <parameter name="options" required="false">
199  <optionlist>
200  <option name="0">
201  <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
202  </option>
203  <option name="*">
204  <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
205  </option>
206  <option name="g">
207  <argument name="gain">
208  <para>Amount of gain to use</para>
209  </argument>
210  <para>Use the specified amount of gain when recording the voicemail message.
211  The units are whole-number decibels (dB).</para>
212  </option>
213  </optionlist>
214  </parameter>
215  </syntax>
216  <description>
217  <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename></para>
218  <para>MiniVM records audio file in configured format and forwards message to e-mail and pager.</para>
219  <para>If there's no user account for that address, a temporary account will be used with default options.</para>
220  <para>The recorded file name and path will be stored in <variable>MVM_FILENAME</variable> and the duration
221  of the message will be stored in <variable>MVM_DURATION</variable></para>
222  <note><para>If the caller hangs up after the recording, the only way to send the message and clean up is to
223  execute in the <literal>h</literal> extension. The application will exit if any of the following DTMF digits
224  are received and the requested extension exist in the current context.</para></note>
225  <variablelist>
226  <variable name="MVM_RECORD_STATUS">
227  <para>This is the status of the record operation</para>
228  <value name="SUCCESS" />
229  <value name="USEREXIT" />
230  <value name="FAILED" />
231  </variable>
232  </variablelist>
233  </description>
234 </application>
235 <application name="MinivmGreet" language="en_US">
236  <synopsis>
237  Play Mini-Voicemail prompts.
238  </synopsis>
239  <syntax>
240  <parameter name="mailbox" required="true" argsep="@">
241  <argument name="username" required="true">
242  <para>Voicemail username</para>
243  </argument>
244  <argument name="domain" required="true">
245  <para>Voicemail domain</para>
246  </argument>
247  </parameter>
248  <parameter name="options" required="false">
249  <optionlist>
250  <option name="b">
251  <para>Play the <literal>busy</literal> greeting to the calling party.</para>
252  </option>
253  <option name="s">
254  <para>Skip the playback of instructions for leaving a message to the calling party.</para>
255  </option>
256  <option name="u">
257  <para>Play the <literal>unavailable</literal> greeting.</para>
258  </option>
259  </optionlist>
260  </parameter>
261  </syntax>
262  <description>
263  <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
264  <para>MinivmGreet() plays default prompts or user specific prompts for an account.</para>
265  <para>Busy and unavailable messages can be choosen, but will be overridden if a temporary
266  message exists for the account.</para>
267  <variablelist>
268  <variable name="MVM_GREET_STATUS">
269  <para>This is the status of the greeting playback.</para>
270  <value name="SUCCESS" />
271  <value name="USEREXIT" />
272  <value name="FAILED" />
273  </variable>
274  </variablelist>
275  </description>
276 </application>
277 <application name="MinivmNotify" language="en_US">
278  <synopsis>
279  Notify voicemail owner about new messages.
280  </synopsis>
281  <syntax>
282  <parameter name="mailbox" required="true" argsep="@">
283  <argument name="username" required="true">
284  <para>Voicemail username</para>
285  </argument>
286  <argument name="domain" required="true">
287  <para>Voicemail domain</para>
288  </argument>
289  </parameter>
290  <parameter name="options" required="false">
291  <optionlist>
292  <option name="template">
293  <para>E-mail template to use for voicemail notification</para>
294  </option>
295  </optionlist>
296  </parameter>
297  </syntax>
298  <description>
299  <para>This application is part of the Mini-Voicemail system, configured in minivm.conf.</para>
300  <para>MiniVMnotify forwards messages about new voicemail to e-mail and pager. If there's no user
301  account for that address, a temporary account will be used with default options (set in
302  <filename>minivm.conf</filename>).</para>
303  <para>If the channel variable <variable>MVM_COUNTER</variable> is set, this will be used in the message
304  file name and available in the template for the message.</para>
305  <para>If no template is given, the default email template will be used to send email and default pager
306  template to send paging message (if the user account is configured with a paging address.</para>
307  <variablelist>
308  <variable name="MVM_NOTIFY_STATUS">
309  <para>This is the status of the notification attempt</para>
310  <value name="SUCCESS" />
311  <value name="FAILED" />
312  </variable>
313  </variablelist>
314  </description>
315 </application>
316 <application name="MinivmDelete" language="en_US">
317  <synopsis>
318  Delete Mini-Voicemail voicemail messages.
319  </synopsis>
320  <syntax>
321  <parameter name="filename" required="true">
322  <para>File to delete</para>
323  </parameter>
324  </syntax>
325  <description>
326  <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
327  <para>It deletes voicemail file set in MVM_FILENAME or given filename.</para>
328  <variablelist>
329  <variable name="MVM_DELETE_STATUS">
330  <para>This is the status of the delete operation.</para>
331  <value name="SUCCESS" />
332  <value name="FAILED" />
333  </variable>
334  </variablelist>
335  </description>
336 </application>
337 
338 <application name="MinivmAccMess" language="en_US">
339  <synopsis>
340  Record account specific messages.
341  </synopsis>
342  <syntax>
343  <parameter name="mailbox" required="true" argsep="@">
344  <argument name="username" required="true">
345  <para>Voicemail username</para>
346  </argument>
347  <argument name="domain" required="true">
348  <para>Voicemail domain</para>
349  </argument>
350  </parameter>
351  <parameter name="options" required="false">
352  <optionlist>
353  <option name="u">
354  <para>Record the <literal>unavailable</literal> greeting.</para>
355  </option>
356  <option name="b">
357  <para>Record the <literal>busy</literal> greeting.</para>
358  </option>
359  <option name="t">
360  <para>Record the temporary greeting.</para>
361  </option>
362  <option name="n">
363  <para>Account name.</para>
364  </option>
365  </optionlist>
366  </parameter>
367  </syntax>
368  <description>
369  <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
370  <para>Use this application to record account specific audio/video messages for busy, unavailable
371  and temporary messages.</para>
372  <para>Account specific directories will be created if they do not exist.</para>
373  <variablelist>
374  <variable name="MVM_ACCMESS_STATUS">
375  <para>This is the result of the attempt to record the specified greeting.</para>
376  <para><literal>FAILED</literal> is set if the file can't be created.</para>
377  <value name="SUCCESS" />
378  <value name="FAILED" />
379  </variable>
380  </variablelist>
381  </description>
382 </application>
383 <application name="MinivmMWI" language="en_US">
384  <synopsis>
385  Send Message Waiting Notification to subscriber(s) of mailbox.
386  </synopsis>
387  <syntax>
388  <parameter name="mailbox" required="true" argsep="@">
389  <argument name="username" required="true">
390  <para>Voicemail username</para>
391  </argument>
392  <argument name="domain" required="true">
393  <para>Voicemail domain</para>
394  </argument>
395  </parameter>
396  <parameter name="urgent" required="true">
397  <para>Number of urgent messages in mailbox.</para>
398  </parameter>
399  <parameter name="new" required="true">
400  <para>Number of new messages in mailbox.</para>
401  </parameter>
402  <parameter name="old" required="true">
403  <para>Number of old messages in mailbox.</para>
404  </parameter>
405  </syntax>
406  <description>
407  <para>This application is part of the Mini-Voicemail system, configured in <filename>minivm.conf</filename>.</para>
408  <para>MinivmMWI is used to send message waiting indication to any devices whose channels have
409  subscribed to the mailbox passed in the first parameter.</para>
410  </description>
411 </application>
412 <function name="MINIVMCOUNTER" language="en_US">
413  <synopsis>
414  Reads or sets counters for MiniVoicemail message.
415  </synopsis>
416  <syntax argsep=":">
417  <parameter name="account" required="true">
418  <para>If account is given and it exists, the counter is specific for the account.</para>
419  <para>If account is a domain and the domain directory exists, counters are specific for a domain.</para>
420  </parameter>
421  <parameter name="name" required="true">
422  <para>The name of the counter is a string, up to 10 characters.</para>
423  </parameter>
424  <parameter name="operand">
425  <para>The counters never goes below zero. Valid operands for changing the value of a counter when assigning a value are:</para>
426  <enumlist>
427  <enum name="i"><para>Increment by value.</para></enum>
428  <enum name="d"><para>Decrement by value.</para></enum>
429  <enum name="s"><para>Set to value.</para></enum>
430  </enumlist>
431  </parameter>
432  </syntax>
433  <description>
434  <para>The operation is atomic and the counter is locked while changing the value. The counters are stored as text files in the minivm account directories. It might be better to use realtime functions if you are using a database to operate your Asterisk.</para>
435  </description>
436  <see-also>
437  <ref type="application">MinivmRecord</ref>
438  <ref type="application">MinivmGreet</ref>
439  <ref type="application">MinivmNotify</ref>
440  <ref type="application">MinivmDelete</ref>
441  <ref type="application">MinivmAccMess</ref>
442  <ref type="application">MinivmMWI</ref>
443  <ref type="function">MINIVMACCOUNT</ref>
444  </see-also>
445 </function>
446 <function name="MINIVMACCOUNT" language="en_US">
447  <synopsis>
448  Gets MiniVoicemail account information.
449  </synopsis>
450  <syntax argsep=":">
451  <parameter name="account" required="true" />
452  <parameter name="item" required="true">
453  <para>Valid items are:</para>
454  <enumlist>
455  <enum name="path">
456  <para>Path to account mailbox (if account exists, otherwise temporary mailbox).</para>
457  </enum>
458  <enum name="hasaccount">
459  <para>1 is static Minivm account exists, 0 otherwise.</para>
460  </enum>
461  <enum name="fullname">
462  <para>Full name of account owner.</para>
463  </enum>
464  <enum name="email">
465  <para>Email address used for account.</para>
466  </enum>
467  <enum name="etemplate">
468  <para>Email template for account (default template if none is configured).</para>
469  </enum>
470  <enum name="ptemplate">
471  <para>Pager template for account (default template if none is configured).</para>
472  </enum>
473  <enum name="accountcode">
474  <para>Account code for the voicemail account.</para>
475  </enum>
476  <enum name="pincode">
477  <para>Pin code for voicemail account.</para>
478  </enum>
479  <enum name="timezone">
480  <para>Time zone for voicemail account.</para>
481  </enum>
482  <enum name="language">
483  <para>Language for voicemail account.</para>
484  </enum>
485  <enum name="&lt;channel variable name&gt;">
486  <para>Channel variable value (set in configuration for account).</para>
487  </enum>
488  </enumlist>
489  </parameter>
490  </syntax>
491  <description>
492  <para />
493  </description>
494  <see-also>
495  <ref type="application">MinivmRecord</ref>
496  <ref type="application">MinivmGreet</ref>
497  <ref type="application">MinivmNotify</ref>
498  <ref type="application">MinivmDelete</ref>
499  <ref type="application">MinivmAccMess</ref>
500  <ref type="application">MinivmMWI</ref>
501  <ref type="function">MINIVMCOUNTER</ref>
502  </see-also>
503 </function>
504  <managerEvent language="en_US" name="MiniVoiceMail">
505  <managerEventInstance class="EVENT_FLAG_CALL">
506  <synopsis>Raised when a notification is sent out by a MiniVoiceMail application</synopsis>
507  <syntax>
508  <channel_snapshot/>
509  <parameter name="Action">
510  <para>What action was taken. Currently, this will always be <literal>SentNotification</literal></para>
511  </parameter>
512  <parameter name="Mailbox">
513  <para>The mailbox that the notification was about, specified as <literal>mailbox</literal>@<literal>context</literal></para>
514  </parameter>
515  <parameter name="Counter">
516  <para>A message counter derived from the <literal>MVM_COUNTER</literal> channel variable.</para>
517  </parameter>
518  </syntax>
519  </managerEventInstance>
520  </managerEvent>
521 ***/
522 
523 #ifndef TRUE
524 #define TRUE 1
525 #endif
526 #ifndef FALSE
527 #define FALSE 0
528 #endif
529 
530 
531 #define MVM_REVIEW (1 << 0) /*!< Review message */
532 #define MVM_OPERATOR (1 << 1) /*!< Operator exit during voicemail recording */
533 #define MVM_REALTIME (1 << 2) /*!< This user is a realtime account */
534 #define MVM_SVMAIL (1 << 3)
535 #define MVM_ENVELOPE (1 << 4)
536 #define MVM_PBXSKIP (1 << 9)
537 #define MVM_ALLOCED (1 << 13)
538 
539 /*! \brief Default mail command to mail voicemail. Change it with the
540  mailcmd= command in voicemail.conf */
541 #define SENDMAIL "/usr/sbin/sendmail -t"
542 
543 #define SOUND_INTRO "vm-intro"
544 #define EOL "\r\n"
545 
546 #define MAX_DATETIME_FORMAT 512
547 #define MAX_NUM_CID_CONTEXTS 10
548 
549 #define ERROR_LOCK_PATH -100
550 #define VOICEMAIL_DIR_MODE 0700
551 
552 #define VOICEMAIL_CONFIG "minivm.conf"
553 #define ASTERISK_USERNAME "asterisk" /*!< Default username for sending mail is asterisk\@localhost */
554 
555 /*! \brief Message types for notification */
557  MVM_MESSAGE_EMAIL,
558  MVM_MESSAGE_PAGE
559  /* For trunk: MVM_MESSAGE_JABBER, */
560 };
561 
562 static char MVM_SPOOL_DIR[PATH_MAX];
563 
564 /* Module declarations */
565 static char *app_minivm_record = "MinivmRecord"; /* Leave a message */
566 static char *app_minivm_greet = "MinivmGreet"; /* Play voicemail prompts */
567 static char *app_minivm_notify = "MinivmNotify"; /* Notify about voicemail by using one of several methods */
568 static char *app_minivm_delete = "MinivmDelete"; /* Notify about voicemail by using one of several methods */
569 static char *app_minivm_accmess = "MinivmAccMess"; /* Record personal voicemail messages */
570 static char *app_minivm_mwi = "MinivmMWI";
571 
572 
573 
574 enum minivm_option_flags {
575  OPT_SILENT = (1 << 0),
576  OPT_BUSY_GREETING = (1 << 1),
577  OPT_UNAVAIL_GREETING = (1 << 2),
578  OPT_TEMP_GREETING = (1 << 3),
579  OPT_NAME_GREETING = (1 << 4),
580  OPT_RECORDGAIN = (1 << 5),
581 };
582 
583 enum minivm_option_args {
584  OPT_ARG_RECORDGAIN = 0,
585  OPT_ARG_ARRAY_SIZE = 1,
586 };
587 
588 AST_APP_OPTIONS(minivm_app_options, {
589  AST_APP_OPTION('s', OPT_SILENT),
590  AST_APP_OPTION('b', OPT_BUSY_GREETING),
591  AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
592  AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
593 });
594 
595 AST_APP_OPTIONS(minivm_accmess_options, {
596  AST_APP_OPTION('b', OPT_BUSY_GREETING),
597  AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
598  AST_APP_OPTION('t', OPT_TEMP_GREETING),
599  AST_APP_OPTION('n', OPT_NAME_GREETING),
600 });
601 
602 /*!\internal
603  * \brief Structure for linked list of Mini-Voicemail users: \ref minivm_accounts */
605  char username[AST_MAX_CONTEXT]; /*!< Mailbox username */
606  char domain[AST_MAX_CONTEXT]; /*!< Voicemail domain */
607 
608  char pincode[10]; /*!< Secret pin code, numbers only */
609  char fullname[120]; /*!< Full name, for directory app */
610  char email[80]; /*!< E-mail address - override */
611  char pager[80]; /*!< E-mail address to pager (no attachment) */
612  char accountcode[AST_MAX_ACCOUNT_CODE]; /*!< Voicemail account account code */
613  char serveremail[80]; /*!< From: Mail address */
614  char externnotify[160]; /*!< Configurable notification command */
615  char language[MAX_LANGUAGE]; /*!< Config: Language setting */
616  char zonetag[80]; /*!< Time zone */
617  char uniqueid[20]; /*!< Unique integer identifier */
618  char exit[80]; /*!< Options for exiting from voicemail() */
619  char attachfmt[80]; /*!< Format for voicemail audio file attachment */
620  char etemplate[80]; /*!< Pager template */
621  char ptemplate[80]; /*!< Voicemail format */
622  unsigned int flags; /*!< MVM_ flags */
623  struct ast_variable *chanvars; /*!< Variables for e-mail template */
624  double volgain; /*!< Volume gain for voicemails sent via e-mail */
626 };
627 
628 /*!\internal
629  * \brief The list of e-mail accounts */
631 
632 /*!\internal
633  * \brief Linked list of e-mail templates in various languages
634  * These are used as templates for e-mails, pager messages and jabber messages
635  * \ref message_templates
636 */
638  char name[80]; /*!< Template name */
639  char *body; /*!< Body of this template */
640  char fromaddress[100]; /*!< Who's sending the e-mail? */
641  char serveremail[80]; /*!< From: Mail address */
642  char subject[100]; /*!< Subject line */
643  char charset[32]; /*!< Default character set for this template */
644  char locale[20]; /*!< Locale for setlocale() */
645  char dateformat[80]; /*!< Date format to use in this attachment */
646  int attachment; /*!< Attachment of media yes/no - no for pager messages */
647  AST_LIST_ENTRY(minivm_template) list; /*!< List mechanics */
648 };
649 
650 /*! \brief The list of e-mail templates */
651 static AST_LIST_HEAD_STATIC(message_templates, minivm_template);
652 
653 /*! \brief Options for leaving voicemail with the voicemail() application */
655  unsigned int flags;
656  signed char record_gain;
657 };
658 
659 /*! \brief Voicemail time zones */
660 struct minivm_zone {
661  char name[80]; /*!< Name of this time zone */
662  char timezone[80]; /*!< Timezone definition */
663  char msg_format[BUFSIZ]; /*!< Not used in minivm ...yet */
664  AST_LIST_ENTRY(minivm_zone) list; /*!< List mechanics */
665 };
666 
667 /*! \brief The list of e-mail time zones */
669 
670 /*! \brief Structure for gathering statistics */
671 struct minivm_stats {
672  int voicemailaccounts; /*!< Number of static accounts */
673  int timezones; /*!< Number of time zones */
674  int templates; /*!< Number of templates */
675 
676  struct timeval reset; /*!< Time for last reset */
677  int receivedmessages; /*!< Number of received messages since reset */
678  struct timeval lastreceived; /*!< Time for last voicemail sent */
679 };
680 
681 /*! \brief Statistics for voicemail */
683 
684 AST_MUTEX_DEFINE_STATIC(minivmlock); /*!< Lock to protect voicemail system */
685 AST_MUTEX_DEFINE_STATIC(minivmloglock); /*!< Lock to protect voicemail system log file */
686 
687 static FILE *minivmlogfile; /*!< The minivm log file */
688 
689 static int global_vmminmessage; /*!< Minimum duration of messages */
690 static int global_vmmaxmessage; /*!< Maximum duration of message */
691 static int global_maxsilence; /*!< Maximum silence during recording */
692 static int global_maxgreet; /*!< Maximum length of prompts */
693 static int global_silencethreshold = 128;
694 static char global_mailcmd[160]; /*!< Configurable mail cmd */
695 static char global_externnotify[160]; /*!< External notification application */
696 static char global_logfile[PATH_MAX]; /*!< Global log file for messages */
697 static char default_vmformat[80];
698 
699 static struct ast_flags globalflags = {0}; /*!< Global voicemail flags */
700 static int global_saydurationminfo;
701 
702 static double global_volgain; /*!< Volume gain for voicmemail via e-mail */
703 
704 /*!\internal
705  * \brief Default dateformat, can be overridden in configuration file */
706 #define DEFAULT_DATEFORMAT "%A, %B %d, %Y at %r"
707 #define DEFAULT_CHARSET "ISO-8859-1"
708 
709 /* Forward declarations */
710 static char *message_template_parse_filebody(const char *filename);
711 static char *message_template_parse_emailbody(const char *body);
712 static int create_vmaccount(char *name, struct ast_variable *var, int realtime);
713 static struct minivm_account *find_user_realtime(const char *domain, const char *username);
714 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a);
715 
716 /*!\internal
717  * \brief Create message template */
718 static struct minivm_template *message_template_create(const char *name)
719 {
720  struct minivm_template *template;
721 
722  template = ast_calloc(1, sizeof(*template));
723  if (!template)
724  return NULL;
725 
726  /* Set some defaults for templates */
727  ast_copy_string(template->name, name, sizeof(template->name));
728  ast_copy_string(template->dateformat, DEFAULT_DATEFORMAT, sizeof(template->dateformat));
729  ast_copy_string(template->charset, DEFAULT_CHARSET, sizeof(template->charset));
730  ast_copy_string(template->subject, "New message in mailbox ${MVM_USERNAME}@${MVM_DOMAIN}", sizeof(template->subject));
731  template->attachment = TRUE;
732 
733  return template;
734 }
735 
736 /*!\internal
737  * \brief Release memory allocated by message template */
738 static void message_template_free(struct minivm_template *template)
739 {
740  if (template->body)
741  ast_free(template->body);
742 
743  ast_free (template);
744 }
745 
746 /*!\internal
747  * \brief Build message template from configuration */
748 static int message_template_build(const char *name, struct ast_variable *var)
749 {
750  struct minivm_template *template;
751  int error = 0;
752 
753  template = message_template_create(name);
754  if (!template) {
755  ast_log(LOG_ERROR, "Out of memory, can't allocate message template object %s.\n", name);
756  return -1;
757  }
758 
759  while (var) {
760  ast_debug(3, "Configuring template option %s = \"%s\" for template %s\n", var->name, var->value, name);
761  if (!strcasecmp(var->name, "fromaddress")) {
762  ast_copy_string(template->fromaddress, var->value, sizeof(template->fromaddress));
763  } else if (!strcasecmp(var->name, "fromemail")) {
764  ast_copy_string(template->serveremail, var->value, sizeof(template->serveremail));
765  } else if (!strcasecmp(var->name, "subject")) {
766  ast_copy_string(template->subject, var->value, sizeof(template->subject));
767  } else if (!strcasecmp(var->name, "locale")) {
768  ast_copy_string(template->locale, var->value, sizeof(template->locale));
769  } else if (!strcasecmp(var->name, "attachmedia")) {
770  template->attachment = ast_true(var->value);
771  } else if (!strcasecmp(var->name, "dateformat")) {
772  ast_copy_string(template->dateformat, var->value, sizeof(template->dateformat));
773  } else if (!strcasecmp(var->name, "charset")) {
774  ast_copy_string(template->charset, var->value, sizeof(template->charset));
775  } else if (!strcasecmp(var->name, "templatefile")) {
776  if (template->body)
777  ast_free(template->body);
778  template->body = message_template_parse_filebody(var->value);
779  if (!template->body) {
780  ast_log(LOG_ERROR, "Error reading message body definition file %s\n", var->value);
781  error++;
782  }
783  } else if (!strcasecmp(var->name, "messagebody")) {
784  if (template->body)
785  ast_free(template->body);
786  template->body = message_template_parse_emailbody(var->value);
787  if (!template->body) {
788  ast_log(LOG_ERROR, "Error parsing message body definition:\n %s\n", var->value);
789  error++;
790  }
791  } else {
792  ast_log(LOG_ERROR, "Unknown message template configuration option \"%s=%s\"\n", var->name, var->value);
793  error++;
794  }
795  var = var->next;
796  }
797  if (error)
798  ast_log(LOG_ERROR, "-- %d errors found parsing message template definition %s\n", error, name);
799 
801  AST_LIST_INSERT_TAIL(&message_templates, template, list);
803 
805 
806  return error;
807 }
808 
809 /*!\internal
810  * \brief Find named template */
811 static struct minivm_template *message_template_find(const char *name)
812 {
813  struct minivm_template *this, *res = NULL;
814 
815  if (ast_strlen_zero(name))
816  return NULL;
817 
819  AST_LIST_TRAVERSE(&message_templates, this, list) {
820  if (!strcasecmp(this->name, name)) {
821  res = this;
822  break;
823  }
824  }
826 
827  return res;
828 }
829 
830 
831 /*!\internal
832  * \brief Clear list of templates */
833 static void message_destroy_list(void)
834 {
835  struct minivm_template *this;
837  while ((this = AST_LIST_REMOVE_HEAD(&message_templates, list))) {
838  message_template_free(this);
839  }
840 
842 }
843 
844 static int get_date(char *s, int len)
845 {
846  struct ast_tm tm;
847  struct timeval now = ast_tvnow();
848 
849  ast_localtime(&now, &tm, NULL);
850  return ast_strftime(s, len, "%a %b %e %r %Z %Y", &tm);
851 }
852 
853 
854 /*!\internal
855  * \brief Free user structure - if it's allocated */
856 static void free_user(struct minivm_account *vmu)
857 {
858  if (vmu->chanvars)
860  ast_free(vmu);
861 }
862 
863 
864 
865 /*!\internal
866  * \brief Prepare for voicemail template by adding channel variables
867  * to the channel
868 */
869 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)
870 {
871  char callerid[256];
872  struct ast_variable *var;
873 
874  if (!channel) {
875  ast_log(LOG_ERROR, "No allocated channel, giving up...\n");
876  return;
877  }
878 
879  for (var = vmu->chanvars ; var ; var = var->next) {
880  pbx_builtin_setvar_helper(channel, var->name, var->value);
881  }
882 
883  /* Prepare variables for substition in email body and subject */
884  pbx_builtin_setvar_helper(channel, "MVM_NAME", vmu->fullname);
885  pbx_builtin_setvar_helper(channel, "MVM_DUR", dur);
886  pbx_builtin_setvar_helper(channel, "MVM_DOMAIN", vmu->domain);
887  pbx_builtin_setvar_helper(channel, "MVM_USERNAME", vmu->username);
888  pbx_builtin_setvar_helper(channel, "MVM_CALLERID", ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, "Unknown Caller"));
889  pbx_builtin_setvar_helper(channel, "MVM_CIDNAME", (cidname ? cidname : "an unknown caller"));
890  pbx_builtin_setvar_helper(channel, "MVM_CIDNUM", (cidnum ? cidnum : "an unknown caller"));
891  pbx_builtin_setvar_helper(channel, "MVM_DATE", date);
892  if (!ast_strlen_zero(counter))
893  pbx_builtin_setvar_helper(channel, "MVM_COUNTER", counter);
894 }
895 
896 /*!\internal
897  * \brief Set default values for Mini-Voicemail users */
898 static void populate_defaults(struct minivm_account *vmu)
899 {
900  ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
901  ast_copy_string(vmu->attachfmt, default_vmformat, sizeof(vmu->attachfmt));
902  vmu->volgain = global_volgain;
903 }
904 
905 /*!\internal
906  * \brief Allocate new vm user and set default values */
907 static struct minivm_account *mvm_user_alloc(void)
908 {
909  struct minivm_account *new;
910 
911  new = ast_calloc(1, sizeof(*new));
912  if (!new)
913  return NULL;
914  populate_defaults(new);
915 
916  return new;
917 }
918 
919 
920 /*!\internal
921  * \brief Clear list of users */
922 static void vmaccounts_destroy_list(void)
923 {
924  struct minivm_account *this;
925  AST_LIST_LOCK(&minivm_accounts);
926  while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
927  ast_free(this);
928  AST_LIST_UNLOCK(&minivm_accounts);
929 }
930 
931 
932 /*!\internal
933  * \brief Find user from static memory object list */
934 static struct minivm_account *find_account(const char *domain, const char *username, int createtemp)
935 {
936  struct minivm_account *vmu = NULL, *cur;
937 
938 
939  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
940  ast_log(LOG_NOTICE, "No username or domain? \n");
941  return NULL;
942  }
943  ast_debug(3, "Looking for voicemail user %s in domain %s\n", username, domain);
944 
945  AST_LIST_LOCK(&minivm_accounts);
946  AST_LIST_TRAVERSE(&minivm_accounts, cur, list) {
947  /* Is this the voicemail account we're looking for? */
948  if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
949  break;
950  }
951  AST_LIST_UNLOCK(&minivm_accounts);
952 
953  if (cur) {
954  ast_debug(3, "Found account for %s@%s\n", username, domain);
955  vmu = cur;
956 
957  } else
958  vmu = find_user_realtime(domain, username);
959 
960  if (createtemp && !vmu) {
961  /* Create a temporary user, send e-mail and be gone */
962  vmu = mvm_user_alloc();
963  ast_set2_flag(vmu, TRUE, MVM_ALLOCED);
964  if (vmu) {
965  ast_copy_string(vmu->username, username, sizeof(vmu->username));
966  ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
967  ast_debug(1, "Created temporary account\n");
968  }
969 
970  }
971  return vmu;
972 }
973 
974 /*!\internal
975  * \brief Find user in realtime storage
976  * \return pointer to minivm_account structure
977 */
978 static struct minivm_account *find_user_realtime(const char *domain, const char *username)
979 {
980  struct ast_variable *var;
981  struct minivm_account *retval;
982  char name[MAXHOSTNAMELEN];
983 
984  retval = mvm_user_alloc();
985  if (!retval)
986  return NULL;
987 
988  if (username)
989  ast_copy_string(retval->username, username, sizeof(retval->username));
990 
991  populate_defaults(retval);
992  var = ast_load_realtime("minivm", "username", username, "domain", domain, SENTINEL);
993 
994  if (!var) {
995  ast_free(retval);
996  return NULL;
997  }
998 
999  snprintf(name, sizeof(name), "%s@%s", username, domain);
1000  create_vmaccount(name, var, TRUE);
1001 
1002  ast_variables_destroy(var);
1003  return retval;
1004 }
1005 
1006 /*!\internal
1007  * \brief Check if the string would need encoding within the MIME standard, to
1008  * avoid confusing certain mail software that expects messages to be 7-bit
1009  * clean.
1010  */
1011 static int check_mime(const char *str)
1012 {
1013  for (; *str; str++) {
1014  if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
1015  return 1;
1016  }
1017  }
1018  return 0;
1019 }
1020 
1021 /*!\internal
1022  * \brief Encode a string according to the MIME rules for encoding strings
1023  * that are not 7-bit clean or contain control characters.
1024  *
1025  * Additionally, if the encoded string would exceed the MIME limit of 76
1026  * characters per line, then the encoding will be broken up into multiple
1027  * sections, separated by a space character, in order to facilitate
1028  * breaking up the associated header across multiple lines.
1029  *
1030  * \param end An expandable buffer for holding the result
1031  * \param maxlen \see ast_str
1032  * \param charset Character set in which the result should be encoded
1033  * \param start A string to be encoded
1034  * \param preamble The length of the first line already used for this string,
1035  * to ensure that each line maintains a maximum length of 76 chars.
1036  * \param postamble the length of any additional characters appended to the
1037  * line, used to ensure proper field wrapping.
1038  * \return The encoded string.
1039  */
1040 static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *charset, const char *start, size_t preamble, size_t postamble)
1041 {
1042  struct ast_str *tmp = ast_str_alloca(80);
1043  int first_section = 1;
1044 
1045  ast_str_reset(*end);
1046  ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1047  for (; *start; start++) {
1048  int need_encoding = 0;
1049  if (*start < 33 || *start > 126 || strchr("()<>@,:;/\"[]?.=_", *start)) {
1050  need_encoding = 1;
1051  }
1052  if ((first_section && need_encoding && preamble + ast_str_strlen(tmp) > 70) ||
1053  (first_section && !need_encoding && preamble + ast_str_strlen(tmp) > 72) ||
1054  (!first_section && need_encoding && ast_str_strlen(tmp) > 70) ||
1055  (!first_section && !need_encoding && ast_str_strlen(tmp) > 72)) {
1056  /* Start new line */
1057  ast_str_append(end, maxlen, "%s%s?=", first_section ? "" : " ", ast_str_buffer(tmp));
1058  ast_str_set(&tmp, -1, "=?%s?Q?", charset);
1059  first_section = 0;
1060  }
1061  if (need_encoding && *start == ' ') {
1062  ast_str_append(&tmp, -1, "_");
1063  } else if (need_encoding) {
1064  ast_str_append(&tmp, -1, "=%hhX", *start);
1065  } else {
1066  ast_str_append(&tmp, -1, "%c", *start);
1067  }
1068  }
1069  ast_str_append(end, maxlen, "%s%s?=%s", first_section ? "" : " ", ast_str_buffer(tmp), ast_str_strlen(tmp) + postamble > 74 ? " " : "");
1070  return ast_str_buffer(*end);
1071 }
1072 
1073 /*!\internal
1074  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
1075  * \param from The string to work with.
1076  * \param buf The destination buffer to write the modified quoted string.
1077  * \param maxlen Always zero. \see ast_str
1078  *
1079  * \return The destination string with quotes wrapped on it (the to field).
1080  */
1081 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
1082 {
1083  const char *ptr;
1084 
1085  /* We're only ever passing 0 to maxlen, so short output isn't possible */
1086  ast_str_set(buf, maxlen, "\"");
1087  for (ptr = from; *ptr; ptr++) {
1088  if (*ptr == '"' || *ptr == '\\') {
1089  ast_str_append(buf, maxlen, "\\%c", *ptr);
1090  } else {
1091  ast_str_append(buf, maxlen, "%c", *ptr);
1092  }
1093  }
1094  ast_str_append(buf, maxlen, "\"");
1095 
1096  return ast_str_buffer(*buf);
1097 }
1098 
1099 /*!\internal
1100  * \brief Send voicemail with audio file as an attachment */
1101 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)
1102 {
1103  RAII_VAR(struct ast_str *, str1, ast_str_create(16), ast_free);
1104  RAII_VAR(struct ast_str *, str2, ast_str_create(16), ast_free);
1105  FILE *p = NULL;
1106  int pfd;
1107  char email[256] = "";
1108  char who[256] = "";
1109  char date[256];
1110  char bound[256];
1111  char fname[PATH_MAX];
1112  char dur[PATH_MAX];
1113  char tmp[80] = "/tmp/astmail-XXXXXX";
1114  char mail_cmd_buffer[PATH_MAX];
1115  char sox_gain_tmpdir[PATH_MAX] = ""; /* Only used with volgain */
1116  char *file_to_delete = NULL, *dir_to_delete = NULL;
1117  struct timeval now;
1118  struct ast_tm tm;
1119  struct minivm_zone *the_zone = NULL;
1120  struct ast_channel *chan = NULL;
1121  char *fromaddress;
1122  char *fromemail;
1123  int res = -1;
1124 
1125  if (!str1 || !str2) {
1126  return -1;
1127  }
1128 
1129  if (type == MVM_MESSAGE_EMAIL) {
1130  if (vmu && !ast_strlen_zero(vmu->email)) {
1131  ast_copy_string(email, vmu->email, sizeof(email));
1132  } else if (!ast_strlen_zero(vmu->username) && !ast_strlen_zero(vmu->domain))
1133  snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1134  } else if (type == MVM_MESSAGE_PAGE) {
1135  ast_copy_string(email, vmu->pager, sizeof(email));
1136  }
1137 
1138  if (ast_strlen_zero(email)) {
1139  ast_log(LOG_WARNING, "No address to send message to.\n");
1140  return -1;
1141  }
1142 
1143  ast_debug(3, "Sending mail to %s@%s - Using template %s\n", vmu->username, vmu->domain, template->name);
1144 
1145  if (!strcmp(format, "wav49"))
1146  format = "WAV";
1147 
1148  /* If we have a gain option, process it now with sox */
1149  if (type == MVM_MESSAGE_EMAIL && (vmu->volgain < -.001 || vmu->volgain > .001) ) {
1150  char sox_gain_cmd[PATH_MAX];
1151 
1152  ast_copy_string(sox_gain_tmpdir, "/tmp/minivm-gain-XXXXXX", sizeof(sox_gain_tmpdir));
1153  ast_debug(3, "sox_gain_tmpdir: %s\n", sox_gain_tmpdir);
1154  if (!mkdtemp(sox_gain_tmpdir)) {
1155  ast_log(LOG_WARNING, "Failed to create temporary directory for volgain: %d\n", errno);
1156  return -1;
1157  }
1158  snprintf(fname, sizeof(fname), "%s/output.%s", sox_gain_tmpdir, format);
1159  snprintf(sox_gain_cmd, sizeof(sox_gain_cmd), "sox -v %.4f %s.%s %s", vmu->volgain, filename, format, fname);
1160  ast_safe_system(sox_gain_cmd);
1161  ast_debug(3, "VOLGAIN: Stored at: %s.%s - Level: %.4f - Mailbox: %s\n", filename, format, vmu->volgain, vmu->username);
1162 
1163  /* Mark some things for deletion */
1164  file_to_delete = fname;
1165  dir_to_delete = sox_gain_tmpdir;
1166  } else {
1167  snprintf(fname, sizeof(fname), "%s.%s", filename, format);
1168  }
1169 
1170  if (template->attachment)
1171  ast_debug(1, "Attaching file '%s', format '%s', uservm is '%d'\n", fname, format, attach_user_voicemail);
1172 
1173  /* Make a temporary file instead of piping directly to sendmail, in case the mail
1174  command hangs */
1175  pfd = mkstemp(tmp);
1176  if (pfd > -1) {
1177  p = fdopen(pfd, "w");
1178  if (!p) {
1179  close(pfd);
1180  pfd = -1;
1181  }
1182  ast_debug(1, "Opening temp file for e-mail: %s\n", tmp);
1183  }
1184  if (!p) {
1185  ast_log(LOG_WARNING, "Unable to open temporary file '%s'\n", tmp);
1186  goto out;
1187  }
1188  /* Allocate channel used for chanvar substitution */
1189  chan = ast_dummy_channel_alloc();
1190  if (!chan) {
1191  goto out;
1192  }
1193 
1194  snprintf(dur, sizeof(dur), "%d:%02d", duration / 60, duration % 60);
1195 
1196  /* Does this user have a timezone specified? */
1197  if (!ast_strlen_zero(vmu->zonetag)) {
1198  /* Find the zone in the list */
1199  struct minivm_zone *z;
1201  AST_LIST_TRAVERSE(&minivm_zones, z, list) {
1202  if (strcmp(z->name, vmu->zonetag))
1203  continue;
1204  the_zone = z;
1205  }
1207  }
1208 
1209  now = ast_tvnow();
1210  ast_localtime(&now, &tm, the_zone ? the_zone->timezone : NULL);
1211  ast_strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", &tm);
1212 
1213  /* Start printing the email to the temporary file */
1214  fprintf(p, "Date: %s\n", date);
1215 
1216  /* Set date format for voicemail mail */
1217  ast_strftime(date, sizeof(date), template->dateformat, &tm);
1218 
1219  /* Populate channel with channel variables for substitution */
1220  prep_email_sub_vars(chan, vmu, cidnum, cidname, dur, date, counter);
1221 
1222  /* Find email address to use */
1223  /* If there's a server e-mail address in the account, use that, otherwise template */
1224  fromemail = ast_strlen_zero(vmu->serveremail) ? template->serveremail : vmu->serveremail;
1225 
1226  /* Find name to user for server e-mail */
1227  fromaddress = ast_strlen_zero(template->fromaddress) ? "" : template->fromaddress;
1228 
1229  /* If needed, add hostname as domain */
1230  if (ast_strlen_zero(fromemail))
1231  fromemail = "asterisk";
1232 
1233  if (strchr(fromemail, '@'))
1234  ast_copy_string(who, fromemail, sizeof(who));
1235  else {
1236  char host[MAXHOSTNAMELEN];
1237  gethostname(host, sizeof(host)-1);
1238  snprintf(who, sizeof(who), "%s@%s", fromemail, host);
1239  }
1240 
1241  if (ast_strlen_zero(fromaddress)) {
1242  fprintf(p, "From: Asterisk PBX <%s>\n", who);
1243  } else {
1244  ast_debug(4, "Fromaddress template: %s\n", fromaddress);
1245  ast_str_substitute_variables(&str1, 0, chan, fromaddress);
1246  if (check_mime(ast_str_buffer(str1))) {
1247  int first_line = 1;
1248  char *ptr;
1249  ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("From: "), strlen(who) + 3);
1250  while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1251  *ptr = '\0';
1252  fprintf(p, "%s %s\n", first_line ? "From:" : "", ast_str_buffer(str2));
1253  first_line = 0;
1254  /* Substring is smaller, so this will never grow */
1255  ast_str_set(&str2, 0, "%s", ptr + 1);
1256  }
1257  fprintf(p, "%s %s <%s>\n", first_line ? "From:" : "", ast_str_buffer(str2), who);
1258  } else {
1259  fprintf(p, "From: %s <%s>\n", ast_str_quote(&str2, 0, ast_str_buffer(str1)), who);
1260  }
1261  }
1262 
1263  fprintf(p, "Message-ID: <Asterisk-%u-%s-%d-%s>\n", (unsigned int)ast_random(), vmu->username, (int)getpid(), who);
1264 
1265  if (ast_strlen_zero(vmu->email)) {
1266  snprintf(email, sizeof(email), "%s@%s", vmu->username, vmu->domain);
1267  } else {
1268  ast_copy_string(email, vmu->email, sizeof(email));
1269  }
1270 
1271  if (check_mime(vmu->fullname)) {
1272  int first_line = 1;
1273  char *ptr;
1274  ast_str_encode_mime(&str2, 0, template->charset, vmu->fullname, strlen("To: "), strlen(email) + 3);
1275  while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1276  *ptr = '\0';
1277  fprintf(p, "%s %s\n", first_line ? "To:" : "", ast_str_buffer(str2));
1278  first_line = 0;
1279  /* Substring is smaller, so this will never grow */
1280  ast_str_set(&str2, 0, "%s", ptr + 1);
1281  }
1282  fprintf(p, "%s %s <%s>\n", first_line ? "To:" : "", ast_str_buffer(str2), email);
1283  } else {
1284  fprintf(p, "To: %s <%s>\n", ast_str_quote(&str2, 0, vmu->fullname), email);
1285  }
1286 
1287  if (!ast_strlen_zero(template->subject)) {
1288  ast_str_substitute_variables(&str1, 0, chan, template->subject);
1289  if (check_mime(ast_str_buffer(str1))) {
1290  int first_line = 1;
1291  char *ptr;
1292  ast_str_encode_mime(&str2, 0, template->charset, ast_str_buffer(str1), strlen("Subject: "), 0);
1293  while ((ptr = strchr(ast_str_buffer(str2), ' '))) {
1294  *ptr = '\0';
1295  fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1296  first_line = 0;
1297  /* Substring is smaller, so this will never grow */
1298  ast_str_set(&str2, 0, "%s", ptr + 1);
1299  }
1300  fprintf(p, "%s %s\n", first_line ? "Subject:" : "", ast_str_buffer(str2));
1301  } else {
1302  fprintf(p, "Subject: %s\n", ast_str_buffer(str1));
1303  }
1304  } else {
1305  fprintf(p, "Subject: New message in mailbox %s@%s\n", vmu->username, vmu->domain);
1306  ast_debug(1, "Using default subject for this email \n");
1307  }
1308 
1309  if (DEBUG_ATLEAST(3))
1310  fprintf(p, "X-Asterisk-debug: template %s user account %s@%s\n", template->name, vmu->username, vmu->domain);
1311  fprintf(p, "MIME-Version: 1.0\n");
1312 
1313  /* Something unique. */
1314  snprintf(bound, sizeof(bound), "voicemail_%s%d%u", vmu->username, (int)getpid(), (unsigned int)ast_random());
1315 
1316  fprintf(p, "Content-Type: multipart/mixed; boundary=\"%s\"\n\n\n", bound);
1317 
1318  fprintf(p, "--%s\n", bound);
1319  fprintf(p, "Content-Type: text/plain; charset=%s\nContent-Transfer-Encoding: 8bit\n\n", template->charset);
1320  if (!ast_strlen_zero(template->body)) {
1321  ast_str_substitute_variables(&str1, 0, chan, template->body);
1322  ast_debug(3, "Message now: %s\n-----\n", ast_str_buffer(str1));
1323  fprintf(p, "%s\n", ast_str_buffer(str1));
1324  } else {
1325  fprintf(p, "Dear %s:\n\n\tJust wanted to let you know you were just left a %s long message \n"
1326  "in mailbox %s from %s, on %s so you might\n"
1327  "want to check it when you get a chance. Thanks!\n\n\t\t\t\t--Asterisk\n\n", vmu->fullname,
1328  dur, vmu->username, (cidname ? cidname : (cidnum ? cidnum : "an unknown caller")), date);
1329  ast_debug(3, "Using default message body (no template)\n-----\n");
1330  }
1331  /* Eww. We want formats to tell us their own MIME type */
1332  if (template->attachment) {
1333  char *ctype = "audio/x-";
1334  ast_debug(3, "Attaching file to message: %s\n", fname);
1335  if (!strcasecmp(format, "ogg"))
1336  ctype = "application/";
1337 
1338  fprintf(p, "--%s\n", bound);
1339  fprintf(p, "Content-Type: %s%s; name=\"voicemailmsg.%s\"\n", ctype, format, format);
1340  fprintf(p, "Content-Transfer-Encoding: base64\n");
1341  fprintf(p, "Content-Description: Voicemail sound attachment.\n");
1342  fprintf(p, "Content-Disposition: attachment; filename=\"voicemail%s.%s\"\n\n", counter ? counter : "", format);
1343 
1344  ast_base64_encode_file_path(fname, p, EOL);
1345  fprintf(p, "\n\n--%s--\n.\n", bound);
1346  }
1347  fclose(p);
1348 
1349  chan = ast_channel_unref(chan);
1350 
1351  if (file_to_delete && dir_to_delete) {
1352  /* We can't delete these files ourselves because the mail command will execute in
1353  the background and we'll end up deleting them out from under it. */
1354  res = snprintf(mail_cmd_buffer, sizeof(mail_cmd_buffer),
1355  "( %s < %s ; rm -f %s %s ; rmdir %s ) &",
1356  global_mailcmd, tmp, tmp, file_to_delete, dir_to_delete);
1357  } else {
1358  res = snprintf(mail_cmd_buffer, sizeof(mail_cmd_buffer),
1359  "( %s < %s ; rm -f %s ) &",
1360  global_mailcmd, tmp, tmp);
1361  }
1362 
1363  if (res < sizeof(mail_cmd_buffer)) {
1364  file_to_delete = dir_to_delete = NULL;
1365  } else {
1366  ast_log(LOG_ERROR, "Could not send message, command line too long\n");
1367  res = -1;
1368  goto out;
1369  }
1370 
1371  ast_safe_system(mail_cmd_buffer);
1372  ast_debug(1, "Sent message to %s with command '%s'%s\n", vmu->email, global_mailcmd, template->attachment ? " - (media attachment)" : "");
1373  ast_debug(3, "Actual command used: %s\n", mail_cmd_buffer);
1374 
1375  res = 0;
1376 
1377 out:
1378  if (file_to_delete) {
1379  unlink(file_to_delete);
1380  }
1381 
1382  if (dir_to_delete) {
1383  rmdir(dir_to_delete);
1384  }
1385 
1386  return res;
1387 }
1388 
1389 /*!\internal
1390  * \brief Create directory based on components */
1391 static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
1392 {
1393  return snprintf(dest, len, "%s%s/%s%s%s", MVM_SPOOL_DIR, domain, username, ast_strlen_zero(folder) ? "" : "/", folder ? folder : "");
1394 }
1395 
1396 /*!\internal
1397  * \brief Checks if directory exists. Does not create directory, but builds string in dest
1398  * \param dest String. base directory.
1399  * \param len Int. Length base directory string.
1400  * \param domain String. Ignored if is null or empty string.
1401  * \param username String. Ignored if is null or empty string.
1402  * \param folder String. Ignored if is null or empty string.
1403  * \return 0 on failure, 1 on success.
1404  */
1405 static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1406 {
1407  struct stat filestat;
1408  make_dir(dest, len, domain, username, folder ? folder : "");
1409  if (stat(dest, &filestat)== -1)
1410  return FALSE;
1411  else
1412  return TRUE;
1413 }
1414 
1415 /*!\internal
1416  * \brief basically mkdir -p $dest/$domain/$username/$folder
1417  * \param dest String. base directory.
1418  * \param len Length of directory string
1419  * \param domain String. Ignored if is null or empty string.
1420  * \param folder String. Ignored if is null or empty string.
1421  * \param username String. Ignored if is null or empty string.
1422  * \return -1 on failure, 0 on success.
1423  */
1424 static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
1425 {
1426  int res;
1427  make_dir(dest, len, domain, username, folder);
1428  if ((res = ast_mkdir(dest, 0777))) {
1429  ast_log(LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1430  return -1;
1431  }
1432  ast_debug(2, "Creating directory for %s@%s folder %s : %s\n", username, domain, folder, dest);
1433  return 0;
1434 }
1435 
1436 
1437 /*!\internal
1438  * \brief Play intro message before recording voicemail
1439  */
1440 static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
1441 {
1442  int res;
1443  char fn[PATH_MAX];
1444 
1445  ast_debug(2, "Still preparing to play message ...\n");
1446 
1447  snprintf(fn, sizeof(fn), "%s%s/%s/greet", MVM_SPOOL_DIR, domain, username);
1448 
1449  if (ast_fileexists(fn, NULL, NULL) > 0) {
1450  res = ast_streamfile(chan, fn, ast_channel_language(chan));
1451  if (res)
1452  return -1;
1453  res = ast_waitstream(chan, ecodes);
1454  if (res)
1455  return res;
1456  } else {
1457  int numericusername = 1;
1458  char *i = username;
1459 
1460  ast_debug(2, "No personal prompts. Using default prompt set for language\n");
1461 
1462  while (*i) {
1463  ast_debug(2, "Numeric? Checking %c\n", *i);
1464  if (!isdigit(*i)) {
1465  numericusername = FALSE;
1466  break;
1467  }
1468  i++;
1469  }
1470 
1471  if (numericusername) {
1472  if (ast_streamfile(chan, "vm-theperson", ast_channel_language(chan)))
1473  return -1;
1474  if ((res = ast_waitstream(chan, ecodes)))
1475  return res;
1476 
1477  res = ast_say_digit_str(chan, username, ecodes, ast_channel_language(chan));
1478  if (res)
1479  return res;
1480  } else {
1481  if (ast_streamfile(chan, "vm-theextensionis", ast_channel_language(chan)))
1482  return -1;
1483  if ((res = ast_waitstream(chan, ecodes)))
1484  return res;
1485  }
1486  }
1487 
1488  res = ast_streamfile(chan, busy ? "vm-isonphone" : "vm-isunavail", ast_channel_language(chan));
1489  if (res)
1490  return -1;
1491  res = ast_waitstream(chan, ecodes);
1492  return res;
1493 }
1494 
1495 /*!\internal
1496  * \brief Delete media files and attribute file */
1497 static int vm_delete(char *file)
1498 {
1499  int res;
1500 
1501  ast_debug(1, "Deleting voicemail file %s\n", file);
1502 
1503  res = unlink(file); /* Remove the meta data file */
1504  res |= ast_filedelete(file, NULL); /* remove the media file */
1505  return res;
1506 }
1507 
1508 
1509 /*!\internal
1510  * \brief Record voicemail message & let caller review or re-record it, or set options if applicable */
1511 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt,
1512  int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir,
1513  signed char record_gain)
1514 {
1515  int cmd = 0;
1516  int max_attempts = 3;
1517  int attempts = 0;
1518  int recorded = 0;
1519  int message_exists = 0;
1520  signed char zero_gain = 0;
1521  char *acceptdtmf = "#";
1522  char *canceldtmf = "";
1523 
1524  /* Note that urgent and private are for flagging messages as such in the future */
1525 
1526  /* barf if no pointer passed to store duration in */
1527  if (duration == NULL) {
1528  ast_log(LOG_WARNING, "Error play_record_review called without duration pointer\n");
1529  return -1;
1530  }
1531 
1532  cmd = '3'; /* Want to start by recording */
1533 
1534  while ((cmd >= 0) && (cmd != 't')) {
1535  switch (cmd) {
1536  case '1':
1537  ast_verb(3, "Saving message as is\n");
1538  ast_stream_and_wait(chan, "vm-msgsaved", "");
1539  cmd = 't';
1540  break;
1541  case '2':
1542  /* Review */
1543  ast_verb(3, "Reviewing the message\n");
1544  ast_streamfile(chan, recordfile, ast_channel_language(chan));
1545  cmd = ast_waitstream(chan, AST_DIGIT_ANY);
1546  break;
1547  case '3':
1548  message_exists = 0;
1549  /* Record */
1550  if (recorded == 1)
1551  ast_verb(3, "Re-recording the message\n");
1552  else
1553  ast_verb(3, "Recording the message\n");
1554  if (recorded && outsidecaller)
1555  cmd = ast_play_and_wait(chan, "beep");
1556  recorded = 1;
1557  /* 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 */
1558  if (record_gain)
1559  ast_channel_setoption(chan, AST_OPTION_RXGAIN, &record_gain, sizeof(record_gain), 0);
1560  if (ast_test_flag(vmu, MVM_OPERATOR))
1561  canceldtmf = "0";
1562  cmd = ast_play_and_record_full(chan, playfile, recordfile, maxtime, fmt, duration, sound_duration, 0, global_silencethreshold, global_maxsilence, unlockdir, acceptdtmf, canceldtmf, 0, AST_RECORD_IF_EXISTS_OVERWRITE);
1563  if (record_gain)
1564  ast_channel_setoption(chan, AST_OPTION_RXGAIN, &zero_gain, sizeof(zero_gain), 0);
1565  if (cmd == -1) /* User has hung up, no options to give */
1566  return cmd;
1567  if (cmd == '0')
1568  break;
1569  else if (cmd == '*')
1570  break;
1571  else {
1572  /* If all is well, a message exists */
1573  message_exists = 1;
1574  cmd = 0;
1575  }
1576  break;
1577  case '4':
1578  case '5':
1579  case '6':
1580  case '7':
1581  case '8':
1582  case '9':
1583  case '*':
1584  case '#':
1585  cmd = ast_play_and_wait(chan, "vm-sorry");
1586  break;
1587  case '0':
1588  if(!ast_test_flag(vmu, MVM_OPERATOR)) {
1589  cmd = ast_play_and_wait(chan, "vm-sorry");
1590  break;
1591  }
1592  if (message_exists || recorded) {
1593  cmd = ast_play_and_wait(chan, "vm-saveoper");
1594  if (!cmd)
1595  cmd = ast_waitfordigit(chan, 3000);
1596  if (cmd == '1') {
1597  ast_play_and_wait(chan, "vm-msgsaved");
1598  cmd = '0';
1599  } else {
1600  ast_play_and_wait(chan, "vm-deleted");
1601  vm_delete(recordfile);
1602  cmd = '0';
1603  }
1604  }
1605  return cmd;
1606  default:
1607  /* If the caller is an outside caller, and the review option is enabled,
1608  allow them to review the message, but let the owner of the box review
1609  their OGM's */
1610  if (outsidecaller && !ast_test_flag(vmu, MVM_REVIEW))
1611  return cmd;
1612  if (message_exists) {
1613  cmd = ast_play_and_wait(chan, "vm-review");
1614  } else {
1615  cmd = ast_play_and_wait(chan, "vm-torerecord");
1616  if (!cmd)
1617  cmd = ast_waitfordigit(chan, 600);
1618  }
1619 
1620  if (!cmd && outsidecaller && ast_test_flag(vmu, MVM_OPERATOR)) {
1621  cmd = ast_play_and_wait(chan, "vm-reachoper");
1622  if (!cmd)
1623  cmd = ast_waitfordigit(chan, 600);
1624  }
1625  if (!cmd)
1626  cmd = ast_waitfordigit(chan, 6000);
1627  if (!cmd) {
1628  attempts++;
1629  }
1630  if (attempts > max_attempts) {
1631  cmd = 't';
1632  }
1633  }
1634  }
1635  if (outsidecaller)
1636  ast_play_and_wait(chan, "vm-goodbye");
1637  if (cmd == 't')
1638  cmd = 0;
1639  return cmd;
1640 }
1641 
1642 /*! \brief Run external notification for voicemail message */
1643 static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
1644 {
1645  char fquser[AST_MAX_CONTEXT * 2];
1646  char *argv[5] = { NULL };
1647  struct ast_party_caller *caller;
1648  char *cid;
1649  int idx;
1650 
1651  if (ast_strlen_zero(vmu->externnotify) && ast_strlen_zero(global_externnotify)) {
1652  return;
1653  }
1654 
1655  snprintf(fquser, sizeof(fquser), "%s@%s", vmu->username, vmu->domain);
1656 
1657  caller = ast_channel_caller(chan);
1658  idx = 0;
1659  argv[idx++] = ast_strlen_zero(vmu->externnotify) ? global_externnotify : vmu->externnotify;
1660  argv[idx++] = fquser;
1661  cid = S_COR(caller->id.name.valid, caller->id.name.str, NULL);
1662  if (cid) {
1663  argv[idx++] = cid;
1664  }
1665  cid = S_COR(caller->id.number.valid, caller->id.number.str, NULL);
1666  if (cid) {
1667  argv[idx++] = cid;
1668  }
1669  argv[idx] = NULL;
1670 
1671  ast_debug(1, "Executing: %s %s %s %s\n",
1672  argv[0], argv[1], argv[2] ?: "", argv[3] ?: "");
1673  ast_safe_execvp(1, argv[0], argv);
1674 }
1675 
1676 /*!\internal
1677  * \brief Send message to voicemail account owner */
1678 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)
1679 {
1680  RAII_VAR(struct ast_json *, json_object, NULL, ast_json_unref);
1681  RAII_VAR(struct stasis_message *, message, NULL, ao2_cleanup);
1682  RAII_VAR(struct ast_mwi_state *, mwi_state, NULL, ao2_cleanup);
1683  char *stringp;
1684  struct minivm_template *etemplate;
1685  char *messageformat;
1686  int res = 0;
1687  char oldlocale[100];
1688  const char *counter;
1689 
1690  if (!ast_strlen_zero(vmu->attachfmt)) {
1691  if (strstr(format, vmu->attachfmt)) {
1692  format = vmu->attachfmt;
1693  } else {
1694  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);
1695  }
1696  }
1697 
1698  etemplate = message_template_find(vmu->etemplate);
1699  if (!etemplate)
1700  etemplate = message_template_find(templatename);
1701  if (!etemplate)
1702  etemplate = message_template_find("email-default");
1703 
1704  /* Attach only the first format */
1705  stringp = messageformat = ast_strdupa(format);
1706  strsep(&stringp, "|");
1707 
1708  if (!ast_strlen_zero(etemplate->locale)) {
1709  char *new_locale;
1710  ast_copy_string(oldlocale, setlocale(LC_TIME, NULL), sizeof(oldlocale));
1711  ast_debug(2, "Changing locale from %s to %s\n", oldlocale, etemplate->locale);
1712  new_locale = setlocale(LC_TIME, etemplate->locale);
1713  if (new_locale == NULL) {
1714  ast_log(LOG_WARNING, "-_-_- Changing to new locale did not work. Locale: %s\n", etemplate->locale);
1715  }
1716  }
1717 
1718 
1719 
1720  /* Read counter if available */
1721  ast_channel_lock(chan);
1722  if ((counter = pbx_builtin_getvar_helper(chan, "MVM_COUNTER"))) {
1723  counter = ast_strdupa(counter);
1724  }
1725  ast_channel_unlock(chan);
1726 
1727  if (ast_strlen_zero(counter)) {
1728  ast_debug(2, "MVM_COUNTER not found\n");
1729  } else {
1730  ast_debug(2, "MVM_COUNTER found - will use it with value %s\n", counter);
1731  }
1732 
1733  res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_EMAIL, counter);
1734 
1735  if (res == 0 && !ast_strlen_zero(vmu->pager)) {
1736  /* Find template for paging */
1737  etemplate = message_template_find(vmu->ptemplate);
1738  if (!etemplate)
1739  etemplate = message_template_find("pager-default");
1740 
1741  if (!ast_strlen_zero(etemplate->locale)) {
1742  ast_copy_string(oldlocale, setlocale(LC_TIME, ""), sizeof(oldlocale));
1743  setlocale(LC_TIME, etemplate->locale);
1744  }
1745 
1746  res = sendmail(etemplate, vmu, cidnum, cidname, filename, messageformat, duration, etemplate->attachment, MVM_MESSAGE_PAGE, counter);
1747  }
1748 
1749  mwi_state = ast_mwi_create(vmu->username, vmu->domain);
1750  if (!mwi_state) {
1751  goto notify_cleanup;
1752  }
1753  mwi_state->snapshot = ast_channel_snapshot_get_latest(ast_channel_uniqueid(chan));
1754 
1755  json_object = ast_json_pack("{s: s, s: s, s: s}",
1756  "Event", "MiniVoiceMail",
1757  "Action", "SentNotification",
1758  "Counter", counter ?: "");
1759  if (!json_object) {
1760  goto notify_cleanup;
1761  }
1762  message = ast_mwi_blob_create(mwi_state, ast_mwi_vm_app_type(), json_object);
1763  if (!message) {
1764  goto notify_cleanup;
1765  }
1766  stasis_publish(ast_mwi_topic(mwi_state->uniqueid), message);
1767 
1768 notify_cleanup:
1769  run_externnotify(chan, vmu); /* Run external notification */
1770  if (!ast_strlen_zero(etemplate->locale)) {
1771  setlocale(LC_TIME, oldlocale); /* Reset to old locale */
1772  }
1773  return res;
1774 }
1775 
1776 
1777 /*!\internal
1778  * \brief Record voicemail message, store into file prepared for sending e-mail */
1779 static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
1780 {
1781  char tmptxtfile[PATH_MAX];
1782  char callerid[256];
1783  FILE *txt;
1784  int res = 0, txtdes;
1785  int duration = 0;
1786  int sound_duration = 0;
1787  char date[256];
1788  char tmpdir[PATH_MAX];
1789  char ext_context[256] = "";
1790  char fmt[80];
1791  char *domain;
1792  char tmp[256] = "";
1793  struct minivm_account *vmu;
1794  int userdir;
1795 
1796  ast_copy_string(tmp, username, sizeof(tmp));
1797  username = tmp;
1798  domain = strchr(tmp, '@');
1799  if (domain) {
1800  *domain = '\0';
1801  domain++;
1802  }
1803 
1804  if (!(vmu = find_account(domain, username, TRUE))) {
1805  /* We could not find user, let's exit */
1806  ast_log(LOG_ERROR, "Can't allocate temporary account for '%s@%s'\n", username, domain);
1807  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1808  return 0;
1809  }
1810 
1811  /* Setup pre-file if appropriate */
1812  if (strcmp(vmu->domain, "localhost"))
1813  snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
1814  else
1815  ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
1816 
1817  /* The meat of recording the message... All the announcements and beeps have been played*/
1818  if (ast_strlen_zero(vmu->attachfmt))
1819  ast_copy_string(fmt, default_vmformat, sizeof(fmt));
1820  else
1821  ast_copy_string(fmt, vmu->attachfmt, sizeof(fmt));
1822 
1823  if (ast_strlen_zero(fmt)) {
1824  ast_log(LOG_WARNING, "No format for saving voicemail? Default %s\n", default_vmformat);
1825  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1826  return res;
1827  }
1828 
1829  userdir = check_dirpath(tmpdir, sizeof(tmpdir), vmu->domain, username, "tmp");
1830 
1831  /* If we have no user directory, use generic temporary directory */
1832  if (!userdir) {
1833  create_dirpath(tmpdir, sizeof(tmpdir), "0000_minivm_temp", "mediafiles", "");
1834  ast_debug(3, "Creating temporary directory %s\n", tmpdir);
1835  }
1836 
1837 
1838  snprintf(tmptxtfile, sizeof(tmptxtfile), "%s/XXXXXX", tmpdir);
1839 
1840  /* XXX This file needs to be in temp directory */
1841  txtdes = mkstemp(tmptxtfile);
1842  if (txtdes < 0) {
1843  ast_log(LOG_ERROR, "Unable to create message file %s: %s\n", tmptxtfile, strerror(errno));
1844  res = ast_streamfile(chan, "vm-mailboxfull", ast_channel_language(chan));
1845  if (!res)
1846  res = ast_waitstream(chan, "");
1847  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1848  return res;
1849  }
1850 
1851  if (res >= 0) {
1852  /* Unless we're *really* silent, try to send the beep */
1853  res = ast_streamfile(chan, "beep", ast_channel_language(chan));
1854  if (!res)
1855  res = ast_waitstream(chan, "");
1856  }
1857 
1858  /* OEJ XXX Maybe this can be turned into a log file? Hmm. */
1859  /* Store information */
1860  ast_debug(2, "Open file for metadata: %s\n", tmptxtfile);
1861 
1862  res = play_record_review(chan, NULL, tmptxtfile, global_vmmaxmessage, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain);
1863 
1864  txt = fdopen(txtdes, "w+");
1865  if (!txt) {
1866  ast_log(LOG_WARNING, "Error opening text file for output\n");
1867  } else {
1868  struct ast_tm tm;
1869  struct timeval now = ast_tvnow();
1870  char timebuf[30];
1871  char logbuf[BUFSIZ];
1872  get_date(date, sizeof(date));
1873  ast_localtime(&now, &tm, NULL);
1874  ast_strftime(timebuf, sizeof(timebuf), "%H:%M:%S", &tm);
1875 
1876  ast_callerid_merge(callerid, sizeof(callerid),
1877  S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
1878  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
1879  "Unknown");
1880  snprintf(logbuf, sizeof(logbuf),
1881  /* "Mailbox:domain:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1882  "%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1883  username,
1884  ast_channel_context(chan),
1885  ast_channel_exten(chan),
1886  ast_channel_priority(chan),
1887  ast_channel_name(chan),
1888  callerid,
1889  date,
1890  timebuf,
1891  duration,
1892  duration < global_vmminmessage ? "IGNORED" : "OK",
1893  vmu->accountcode
1894  );
1895  fprintf(txt, "%s", logbuf);
1896  if (minivmlogfile) {
1897  ast_mutex_lock(&minivmloglock);
1898  fprintf(minivmlogfile, "%s", logbuf);
1899  ast_mutex_unlock(&minivmloglock);
1900  }
1901 
1902  if (sound_duration < global_vmminmessage) {
1903  ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
1904  fclose(txt);
1905  ast_filedelete(tmptxtfile, NULL);
1906  unlink(tmptxtfile);
1907  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1908  return 0;
1909  }
1910  fclose(txt); /* Close log file */
1911  if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1912  ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1913  unlink(tmptxtfile);
1914  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1915  if(ast_test_flag(vmu, MVM_ALLOCED))
1916  free_user(vmu);
1917  return 0;
1918  }
1919 
1920  /* Set channel variables for the notify application */
1921  pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1922  snprintf(timebuf, sizeof(timebuf), "%d", duration);
1923  pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1924  pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1925 
1926  }
1929 #if 0
1930  /* Go ahead and delete audio files from system, they're not needed any more */
1931  if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1932  ast_filedelete(tmptxtfile, NULL);
1933  /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1934  ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1935  }
1936 #endif
1937 
1938  if (res > 0)
1939  res = 0;
1940 
1941  if(ast_test_flag(vmu, MVM_ALLOCED))
1942  free_user(vmu);
1943 
1944  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
1945  return res;
1946 }
1947 
1948 /*!\internal
1949  * \brief Queue a message waiting event */
1950 static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
1951 {
1952  char *mailbox, *context;
1953 
1954  mailbox = ast_strdupa(mbx);
1955  context = ast_strdupa(ctx);
1956  if (ast_strlen_zero(context)) {
1957  context = "default";
1958  }
1959 
1960  ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id);
1961 }
1962 
1963 /*!\internal
1964  * \brief Send MWI using interal Asterisk event subsystem */
1965 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
1966 {
1967  int argc;
1968  char *argv[4];
1969  int res = 0;
1970  char *tmpptr;
1971  char tmp[PATH_MAX];
1972  char *mailbox;
1973  char *domain;
1974  if (ast_strlen_zero(data)) {
1975  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1976  return -1;
1977  }
1978  tmpptr = ast_strdupa((char *)data);
1979  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1980  if (argc < 4) {
1981  ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
1982  return -1;
1983  }
1984  ast_copy_string(tmp, argv[0], sizeof(tmp));
1985  mailbox = tmp;
1986  domain = strchr(tmp, '@');
1987  if (domain) {
1988  *domain = '\0';
1989  domain++;
1990  }
1991  if (ast_strlen_zero(domain) || ast_strlen_zero(mailbox)) {
1992  ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
1993  return -1;
1994  }
1995  queue_mwi_event(ast_channel_uniqueid(chan), mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
1996 
1997  return res;
1998 }
1999 
2000 
2001 /*!\internal
2002  * \brief Notify voicemail account owners - either generic template or user specific */
2003 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2004 {
2005  int argc;
2006  char *argv[2];
2007  int res = 0;
2008  char tmp[PATH_MAX];
2009  char *domain;
2010  char *tmpptr;
2011  struct minivm_account *vmu;
2012  char *username;
2013  const char *template = "";
2014  const char *filename;
2015  const char *format;
2016  const char *duration_string;
2017  if (ast_strlen_zero(data)) {
2018  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2019  return -1;
2020  }
2021  tmpptr = ast_strdupa((char *)data);
2022  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2023 
2024  if (argc == 2 && !ast_strlen_zero(argv[1]))
2025  template = argv[1];
2026 
2027  ast_copy_string(tmp, argv[0], sizeof(tmp));
2028  username = tmp;
2029  domain = strchr(tmp, '@');
2030  if (domain) {
2031  *domain = '\0';
2032  domain++;
2033  }
2034  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2035  ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2036  return -1;
2037  }
2038 
2039  if(!(vmu = find_account(domain, username, TRUE))) {
2040  /* We could not find user, let's exit */
2041  ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2042  pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2043  return -1;
2044  }
2045 
2046  ast_channel_lock(chan);
2047  if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2048  filename = ast_strdupa(filename);
2049  }
2050  ast_channel_unlock(chan);
2051  /* Notify of new message to e-mail and pager */
2052  if (!ast_strlen_zero(filename)) {
2053  ast_channel_lock(chan);
2054  if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2055  format = ast_strdupa(format);
2056  }
2057  if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2058  duration_string = ast_strdupa(duration_string);
2059  }
2060  ast_channel_unlock(chan);
2061  res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
2062  format,
2063  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2064  S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
2065  }
2066 
2067  pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2068 
2069 
2070  if(ast_test_flag(vmu, MVM_ALLOCED))
2071  free_user(vmu);
2072 
2073  /* Ok, we're ready to rock and roll. Return to dialplan */
2074 
2075  return res;
2076 
2077 }
2078 
2079 /*!\internal
2080  * \brief Dialplan function to record voicemail */
2081 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2082 {
2083  int res = 0;
2084  char *tmp;
2085  struct leave_vm_options leave_options;
2086  int argc;
2087  char *argv[2];
2088  struct ast_flags flags = { 0 };
2089  char *opts[OPT_ARG_ARRAY_SIZE];
2090 
2091  memset(&leave_options, 0, sizeof(leave_options));
2092 
2093  /* Answer channel if it's not already answered */
2094  if (ast_channel_state(chan) != AST_STATE_UP)
2095  ast_answer(chan);
2096 
2097  if (ast_strlen_zero(data)) {
2098  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2099  return -1;
2100  }
2101  tmp = ast_strdupa((char *)data);
2102  argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2103  if (argc == 2) {
2104  if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2105  return -1;
2106  }
2107  ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2108  if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2109  int gain;
2110 
2111  if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
2112  ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2113  return -1;
2114  } else
2115  leave_options.record_gain = (signed char) gain;
2116  }
2117  }
2118 
2119  /* Now run the appliation and good luck to you! */
2120  res = leave_voicemail(chan, argv[0], &leave_options);
2121 
2122  if (res == ERROR_LOCK_PATH) {
2123  ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2124  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2125  res = 0;
2126  }
2127  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2128 
2129  return res;
2130 }
2131 
2132 /*!\internal
2133  * \brief Play voicemail prompts - either generic or user specific */
2134 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2135 {
2136  struct leave_vm_options leave_options = { 0, '\0'};
2137  int argc;
2138  char *argv[2];
2139  struct ast_flags flags = { 0 };
2140  char *opts[OPT_ARG_ARRAY_SIZE];
2141  int res = 0;
2142  int ouseexten = 0;
2143  char tmp[PATH_MAX];
2144  char dest[PATH_MAX];
2145  char prefile[PATH_MAX] = "";
2146  char tempfile[PATH_MAX] = "";
2147  char ext_context[256] = "";
2148  char *domain;
2149  char ecodes[16] = "#";
2150  char *tmpptr;
2151  struct minivm_account *vmu;
2152  char *username;
2153 
2154  if (ast_strlen_zero(data)) {
2155  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2156  return -1;
2157  }
2158  tmpptr = ast_strdupa((char *)data);
2159  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2160 
2161  if (argc == 2) {
2162  if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2163  return -1;
2164  ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2165  }
2166 
2167  ast_copy_string(tmp, argv[0], sizeof(tmp));
2168  username = tmp;
2169  domain = strchr(tmp, '@');
2170  if (domain) {
2171  *domain = '\0';
2172  domain++;
2173  }
2174  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2175  ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
2176  return -1;
2177  }
2178  ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2179 
2180  if (!(vmu = find_account(domain, username, TRUE))) {
2181  ast_log(LOG_ERROR, "Could not allocate memory. \n");
2182  return -1;
2183  }
2184 
2185  /* Answer channel if it's not already answered */
2186  if (ast_channel_state(chan) != AST_STATE_UP)
2187  ast_answer(chan);
2188 
2189  /* Setup pre-file if appropriate */
2190  if (strcmp(vmu->domain, "localhost"))
2191  snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2192  else
2193  ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2194 
2195  if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2196  res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2197  if (res)
2198  snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2199  } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2200  res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2201  if (res)
2202  snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2203  }
2204  /* Check for temporary greeting - it overrides busy and unavail */
2205  snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2206  if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2207  ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2208  ast_copy_string(prefile, tempfile, sizeof(prefile));
2209  }
2210  ast_debug(2, "Preparing to play message ...\n");
2211 
2212  /* Check current context for special extensions */
2213  if (ast_test_flag(vmu, MVM_OPERATOR)) {
2214  if (!ast_strlen_zero(vmu->exit)) {
2215  if (ast_exists_extension(chan, vmu->exit, "o", 1,
2216  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2217  strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2218  ouseexten = 1;
2219  }
2220  } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
2221  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2222  strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2223  ouseexten = 1;
2224  }
2225  }
2226 
2227  if (!ast_strlen_zero(vmu->exit)) {
2228  if (ast_exists_extension(chan, vmu->exit, "a", 1,
2229  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2230  strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2231  }
2232  } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
2233  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2234  strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2235  }
2236 
2237  res = 0; /* Reset */
2238  /* Play the beginning intro if desired */
2239  if (!ast_strlen_zero(prefile)) {
2240  if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1)
2241  res = ast_waitstream(chan, ecodes);
2242  } else {
2243  ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2244  res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2245  }
2246  if (res < 0) {
2247  ast_debug(2, "Hang up during prefile playback\n");
2248  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2249  if(ast_test_flag(vmu, MVM_ALLOCED))
2250  free_user(vmu);
2251  return -1;
2252  }
2253  if (res == '#') {
2254  /* On a '#' we skip the instructions */
2255  ast_set_flag(&leave_options, OPT_SILENT);
2256  res = 0;
2257  }
2258  if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2259  res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
2260  if (!res)
2261  res = ast_waitstream(chan, ecodes);
2262  if (res == '#') {
2263  ast_set_flag(&leave_options, OPT_SILENT);
2264  res = 0;
2265  }
2266  }
2267  if (res > 0)
2268  ast_stopstream(chan);
2269  /* Check for a '*' here in case the caller wants to escape from voicemail to something
2270  other than the operator -- an automated attendant or mailbox login for example */
2271  if (res == '*') {
2272  ast_channel_exten_set(chan, "a");
2273  if (!ast_strlen_zero(vmu->exit)) {
2274  ast_channel_context_set(chan, vmu->exit);
2275  }
2276  ast_channel_priority_set(chan, 0);
2277  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2278  res = 0;
2279  } else if (res == '0') { /* Check for a '0' here */
2280  if(ouseexten) {
2281  ast_channel_exten_set(chan, "o");
2282  if (!ast_strlen_zero(vmu->exit)) {
2283  ast_channel_context_set(chan, vmu->exit);
2284  }
2285  ast_play_and_wait(chan, "transfer");
2286  ast_channel_priority_set(chan, 0);
2287  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2288  }
2289  res = 0;
2290  } else if (res < 0) {
2291  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2292  res = -1;
2293  } else
2294  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2295 
2296  if(ast_test_flag(vmu, MVM_ALLOCED))
2297  free_user(vmu);
2298 
2299 
2300  /* Ok, we're ready to rock and roll. Return to dialplan */
2301  return res;
2302 
2303 }
2304 
2305 /*!\internal
2306  * \brief Dialplan application to delete voicemail */
2307 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2308 {
2309  int res = 0;
2310  char filename[BUFSIZ];
2311 
2312  if (!ast_strlen_zero(data)) {
2313  ast_copy_string(filename, (char *) data, sizeof(filename));
2314  } else {
2315  ast_channel_lock(chan);
2316  ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2317  ast_channel_unlock(chan);
2318  }
2319 
2320  if (ast_strlen_zero(filename)) {
2321  ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2322  return res;
2323  }
2324 
2325  /* Go ahead and delete audio files from system, they're not needed any more */
2326  /* We should look for both audio and text files here */
2327  if (ast_fileexists(filename, NULL, NULL) > 0) {
2328  res = vm_delete(filename);
2329  if (res) {
2330  ast_debug(2, "Can't delete file: %s\n", filename);
2331  pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2332  } else {
2333  ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2334  pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2335  }
2336  } else {
2337  ast_debug(2, "Filename does not exist: %s\n", filename);
2338  pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2339  }
2340 
2341  return res;
2342 }
2343 
2344 /*! \brief Record specific messages for voicemail account */
2345 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2346 {
2347  int argc = 0;
2348  char *argv[2];
2349  char filename[PATH_MAX];
2350  char tmp[PATH_MAX];
2351  char *domain;
2352  char *tmpptr = NULL;
2353  struct minivm_account *vmu;
2354  char *username;
2355  struct ast_flags flags = { 0 };
2356  char *opts[OPT_ARG_ARRAY_SIZE];
2357  int error = FALSE;
2358  char *message = NULL;
2359  char *prompt = NULL;
2360  int duration;
2361 
2362  if (ast_strlen_zero(data)) {
2363  ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2364  error = TRUE;
2365  } else {
2366  tmpptr = ast_strdupa((char *)data);
2367  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2368  }
2369 
2370  if (argc <=1) {
2371  ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2372  error = TRUE;
2373  }
2374  if (!error && strlen(argv[1]) > 1) {
2375  ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2376  error = TRUE;
2377  }
2378 
2379  if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2380  ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2381  error = TRUE;
2382  }
2383 
2384  if (error) {
2385  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2386  return -1;
2387  }
2388 
2389  ast_copy_string(tmp, argv[0], sizeof(tmp));
2390  username = tmp;
2391  domain = strchr(tmp, '@');
2392  if (domain) {
2393  *domain = '\0';
2394  domain++;
2395  }
2396  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2397  ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2398  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2399  return -1;
2400  }
2401 
2402  if(!(vmu = find_account(domain, username, TRUE))) {
2403  /* We could not find user, let's exit */
2404  ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2405  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2406  return -1;
2407  }
2408 
2409  /* Answer channel if it's not already answered */
2410  if (ast_channel_state(chan) != AST_STATE_UP)
2411  ast_answer(chan);
2412 
2413  /* Here's where the action is */
2414  if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2415  message = "busy";
2416  prompt = "vm-rec-busy";
2417  } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2418  message = "unavailable";
2419  prompt = "vm-rec-unv";
2420  } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2421  message = "temp";
2422  prompt = "vm-rec-temp";
2423  } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2424  message = "greet";
2425  prompt = "vm-rec-name";
2426  }
2427  snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2428  /* Maybe we should check the result of play_record_review ? */
2429  play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
2430 
2431  ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2432 
2433  if(ast_test_flag(vmu, MVM_ALLOCED))
2434  free_user(vmu);
2435 
2436  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2437 
2438  /* Ok, we're ready to rock and roll. Return to dialplan */
2439  return 0;
2440 }
2441 
2442 /*! \brief Append new mailbox to mailbox list from configuration file */
2443 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2444 {
2445  struct minivm_account *vmu;
2446  char *domain;
2447  char *username;
2448  char accbuf[BUFSIZ];
2449 
2450  ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2451 
2452  ast_copy_string(accbuf, name, sizeof(accbuf));
2453  username = accbuf;
2454  domain = strchr(accbuf, '@');
2455  if (domain) {
2456  *domain = '\0';
2457  domain++;
2458  }
2459  if (ast_strlen_zero(domain)) {
2460  ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2461  return 0;
2462  }
2463 
2464  ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2465 
2466  /* Allocate user account */
2467  vmu = ast_calloc(1, sizeof(*vmu));
2468  if (!vmu)
2469  return 0;
2470 
2471  ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2472  ast_copy_string(vmu->username, username, sizeof(vmu->username));
2473 
2474  populate_defaults(vmu);
2475 
2476  ast_debug(3, "...Configuring account %s\n", name);
2477 
2478  while (var) {
2479  ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2480  if (!strcasecmp(var->name, "serveremail")) {
2481  ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2482  } else if (!strcasecmp(var->name, "email")) {
2483  ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2484  } else if (!strcasecmp(var->name, "accountcode")) {
2485  ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2486  } else if (!strcasecmp(var->name, "pincode")) {
2487  ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2488  } else if (!strcasecmp(var->name, "domain")) {
2489  ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2490  } else if (!strcasecmp(var->name, "language")) {
2491  ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2492  } else if (!strcasecmp(var->name, "timezone")) {
2493  ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2494  } else if (!strcasecmp(var->name, "externnotify")) {
2495  ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2496  } else if (!strcasecmp(var->name, "etemplate")) {
2497  ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2498  } else if (!strcasecmp(var->name, "ptemplate")) {
2499  ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2500  } else if (!strcasecmp(var->name, "fullname")) {
2501  ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2502  } else if (!strcasecmp(var->name, "setvar")) {
2503  char *varval;
2504  char varname[strlen(var->value) + 1];
2505  struct ast_variable *tmpvar;
2506 
2507  strcpy(varname, var->value); /* safe */
2508  if ((varval = strchr(varname, '='))) {
2509  *varval = '\0';
2510  varval++;
2511  if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2512  tmpvar->next = vmu->chanvars;
2513  vmu->chanvars = tmpvar;
2514  }
2515  }
2516  } else if (!strcasecmp(var->name, "pager")) {
2517  ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2518  } else if (!strcasecmp(var->name, "volgain")) {
2519  sscanf(var->value, "%30lf", &vmu->volgain);
2520  } else {
2521  ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2522  }
2523  var = var->next;
2524  }
2525  ast_debug(3, "...Linking account %s\n", name);
2526 
2527  AST_LIST_LOCK(&minivm_accounts);
2528  AST_LIST_INSERT_TAIL(&minivm_accounts, vmu, list);
2529  AST_LIST_UNLOCK(&minivm_accounts);
2530 
2532 
2533  ast_debug(2, "MVM :: 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)" : "");
2534  return 0;
2535 }
2536 
2537 /*! \brief Free Mini Voicemail timezone */
2538 static void free_zone(struct minivm_zone *z)
2539 {
2540  ast_free(z);
2541 }
2542 
2543 /*! \brief Clear list of timezones */
2544 static void timezone_destroy_list(void)
2545 {
2546  struct minivm_zone *this;
2547 
2549  while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2550  free_zone(this);
2551 
2553 }
2554 
2555 /*! \brief Add time zone to memory list */
2556 static int timezone_add(const char *zonename, const char *config)
2557 {
2558  struct minivm_zone *newzone;
2559  char *msg_format, *timezone_str;
2560 
2561  newzone = ast_calloc(1, sizeof(*newzone));
2562  if (newzone == NULL)
2563  return 0;
2564 
2565  msg_format = ast_strdupa(config);
2566 
2567  timezone_str = strsep(&msg_format, "|");
2568  if (!msg_format) {
2569  ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2570  ast_free(newzone);
2571  return 0;
2572  }
2573 
2574  ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2575  ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2576  ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2577 
2579  AST_LIST_INSERT_TAIL(&minivm_zones, newzone, list);
2581 
2583 
2584  return 0;
2585 }
2586 
2587 /*! \brief Read message template from file */
2588 static char *message_template_parse_filebody(const char *filename) {
2589  char buf[BUFSIZ * 6];
2590  char readbuf[BUFSIZ];
2591  char filenamebuf[BUFSIZ];
2592  char *writepos;
2593  char *messagebody;
2594  FILE *fi;
2595  int lines = 0;
2596 
2597  if (ast_strlen_zero(filename))
2598  return NULL;
2599  if (*filename == '/')
2600  ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2601  else
2602  snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2603 
2604  if (!(fi = fopen(filenamebuf, "r"))) {
2605  ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2606  return NULL;
2607  }
2608  writepos = buf;
2609  while (fgets(readbuf, sizeof(readbuf), fi)) {
2610  lines ++;
2611  if (writepos != buf) {
2612  *writepos = '\n'; /* Replace EOL with new line */
2613  writepos++;
2614  }
2615  ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2616  writepos += strlen(readbuf) - 1;
2617  }
2618  fclose(fi);
2619  messagebody = ast_calloc(1, strlen(buf + 1));
2620  ast_copy_string(messagebody, buf, strlen(buf) + 1);
2621  ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2622  ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2623 
2624  return messagebody;
2625 }
2626 
2627 /*! \brief Parse emailbody template from configuration file */
2628 static char *message_template_parse_emailbody(const char *configuration)
2629 {
2630  char *tmpread, *tmpwrite;
2631  char *emailbody = ast_strdup(configuration);
2632 
2633  /* substitute strings \t and \n into the apropriate characters */
2634  tmpread = tmpwrite = emailbody;
2635  while ((tmpwrite = strchr(tmpread,'\\'))) {
2636  int len = strlen("\n");
2637  switch (tmpwrite[1]) {
2638  case 'n':
2639  memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2640  tmpwrite[0] = '\n';
2641  break;
2642  case 't':
2643  memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2644  tmpwrite[0] = '\t';
2645  break;
2646  default:
2647  ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2648  }
2649  tmpread = tmpwrite + len;
2650  }
2651  return emailbody;
2652 }
2653 
2654 /*! \brief Apply general configuration options */
2655 static int apply_general_options(struct ast_variable *var)
2656 {
2657  int error = 0;
2658 
2659  while (var) {
2660  /* Mail command */
2661  if (!strcmp(var->name, "mailcmd")) {
2662  ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2663  } else if (!strcmp(var->name, "maxgreet")) {
2664  global_maxgreet = atoi(var->value);
2665  } else if (!strcmp(var->name, "maxsilence")) {
2666  global_maxsilence = atoi(var->value);
2667  if (global_maxsilence > 0)
2668  global_maxsilence *= 1000;
2669  } else if (!strcmp(var->name, "logfile")) {
2670  if (!ast_strlen_zero(var->value) ) {
2671  if(*(var->value) == '/')
2672  ast_copy_string(global_logfile, var->value, sizeof(global_logfile));
2673  else
2674  snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2675  }
2676  } else if (!strcmp(var->name, "externnotify")) {
2677  /* External voicemail notify application */
2678  ast_copy_string(global_externnotify, var->value, sizeof(global_externnotify));
2679  } else if (!strcmp(var->name, "silencethreshold") || !strcmp(var->name, "silencetreshold")) {
2680  /* Silence treshold */
2681  global_silencethreshold = atoi(var->value);
2682  } else if (!strcmp(var->name, "maxmessage")) {
2683  int x;
2684  if (sscanf(var->value, "%30d", &x) == 1) {
2685  global_vmmaxmessage = x;
2686  } else {
2687  error ++;
2688  ast_log(LOG_WARNING, "Invalid max message time length\n");
2689  }
2690  } else if (!strcmp(var->name, "minmessage")) {
2691  int x;
2692  if (sscanf(var->value, "%30d", &x) == 1) {
2693  global_vmminmessage = x;
2694  if (global_maxsilence <= global_vmminmessage)
2695  ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2696  } else {
2697  error ++;
2698  ast_log(LOG_WARNING, "Invalid min message time length\n");
2699  }
2700  } else if (!strcmp(var->name, "format")) {
2701  ast_copy_string(default_vmformat, var->value, sizeof(default_vmformat));
2702  } else if (!strcmp(var->name, "review")) {
2703  ast_set2_flag((&globalflags), ast_true(var->value), MVM_REVIEW);
2704  } else if (!strcmp(var->name, "operator")) {
2705  ast_set2_flag((&globalflags), ast_true(var->value), MVM_OPERATOR);
2706  }
2707  var = var->next;
2708  }
2709  return error;
2710 }
2711 
2712 /*! \brief Load minivoicemail configuration */
2713 static int load_config(int reload)
2714 {
2715  struct ast_config *cfg;
2716  struct ast_variable *var;
2717  char *cat;
2718  const char *chanvar;
2719  int error = 0;
2720  struct minivm_template *template;
2721  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2722 
2723  cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2724  if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2725  return 0;
2726  } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2727  ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
2728  return 0;
2729  }
2730 
2731  ast_mutex_lock(&minivmlock);
2732 
2733  /* Destroy lists to reconfigure */
2734  message_destroy_list(); /* Destroy list of voicemail message templates */
2735  timezone_destroy_list(); /* Destroy list of timezones */
2736  vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2737  ast_debug(2, "Destroyed memory objects...\n");
2738 
2739  /* First, set some default settings */
2740  global_externnotify[0] = '\0';
2741  global_logfile[0] = '\0';
2742  global_vmmaxmessage = 2000;
2743  global_maxgreet = 2000;
2744  global_vmminmessage = 0;
2745  strcpy(global_mailcmd, SENDMAIL);
2746  global_maxsilence = 0;
2747  global_saydurationminfo = 2;
2748  ast_copy_string(default_vmformat, "wav", sizeof(default_vmformat));
2749  ast_set2_flag((&globalflags), FALSE, MVM_REVIEW);
2750  ast_set2_flag((&globalflags), FALSE, MVM_OPERATOR);
2751  /* Reset statistics */
2752  memset(&global_stats, 0, sizeof(global_stats));
2754 
2755  global_silencethreshold = ast_dsp_get_threshold_from_settings(THRESHOLD_SILENCE);
2756 
2757  /* Make sure we could load configuration file */
2758  if (!cfg) {
2759  ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2760  ast_mutex_unlock(&minivmlock);
2761  return 0;
2762  }
2763 
2764  ast_debug(2, "Loaded configuration file, now parsing\n");
2765 
2766  /* General settings */
2767 
2768  cat = ast_category_browse(cfg, NULL);
2769  while (cat) {
2770  ast_debug(3, "Found configuration section [%s]\n", cat);
2771  if (!strcasecmp(cat, "general")) {
2772  /* Nothing right now */
2773  error += apply_general_options(ast_variable_browse(cfg, cat));
2774  } else if (!strncasecmp(cat, "template-", 9)) {
2775  /* Template */
2776  char *name = cat + 9;
2777 
2778  /* Now build and link template to list */
2779  error += message_template_build(name, ast_variable_browse(cfg, cat));
2780  } else {
2781  var = ast_variable_browse(cfg, cat);
2782  if (!strcasecmp(cat, "zonemessages")) {
2783  /* Timezones in this context */
2784  while (var) {
2785  timezone_add(var->name, var->value);
2786  var = var->next;
2787  }
2788  } else {
2789  /* Create mailbox from this */
2790  error += create_vmaccount(cat, var, FALSE);
2791  }
2792  }
2793  /* Find next section in configuration file */
2794  cat = ast_category_browse(cfg, cat);
2795  }
2796 
2797  /* Configure the default email template */
2798  message_template_build("email-default", NULL);
2799  template = message_template_find("email-default");
2800 
2801  /* Load date format config for voicemail mail */
2802  if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2803  ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2804  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2805  ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2806  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2807  ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2808  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaddress")))
2809  ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2810  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2811  ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2812  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2813  ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2814  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2815  template->body = message_template_parse_emailbody(chanvar);
2816  template->attachment = TRUE;
2817 
2818  message_template_build("pager-default", NULL);
2819  template = message_template_find("pager-default");
2820  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2821  ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2822  if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2823  ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2824  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2825  ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2826  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2827  ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2828  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2829  template->body = message_template_parse_emailbody(chanvar);
2830  template->attachment = FALSE;
2831 
2832  if (error)
2833  ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2834 
2835  ast_mutex_unlock(&minivmlock);
2836  ast_config_destroy(cfg);
2837 
2838  /* Close log file if it's open and disabled */
2839  if(minivmlogfile)
2840  fclose(minivmlogfile);
2841 
2842  /* Open log file if it's enabled */
2843  if(!ast_strlen_zero(global_logfile)) {
2844  minivmlogfile = fopen(global_logfile, "a");
2845  if(!minivmlogfile)
2846  ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2847  if (minivmlogfile)
2848  ast_debug(3, "Opened log file %s \n", global_logfile);
2849  }
2850 
2851  return 0;
2852 }
2853 
2854 /*! \brief CLI routine for listing templates */
2855 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2856 {
2857  struct minivm_template *this;
2858 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2859  int count = 0;
2860 
2861  switch (cmd) {
2862  case CLI_INIT:
2863  e->command = "minivm list templates";
2864  e->usage =
2865  "Usage: minivm list templates\n"
2866  " Lists message templates for e-mail, paging and IM\n";
2867  return NULL;
2868  case CLI_GENERATE:
2869  return NULL;
2870  }
2871 
2872  if (a->argc > 3)
2873  return CLI_SHOWUSAGE;
2874 
2877  ast_cli(a->fd, "There are no message templates defined\n");
2879  return CLI_FAILURE;
2880  }
2881  ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2882  ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2883  AST_LIST_TRAVERSE(&message_templates, this, list) {
2884  ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
2885  S_OR(this->charset, "-"),
2886  S_OR(this->locale, "-"),
2887  this->attachment ? "Yes" : "No",
2888  S_OR(this->subject, "-"));
2889  count++;
2890  }
2892  ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2893  return CLI_SUCCESS;
2894 }
2895 
2896 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2897 {
2898  int which = 0;
2899  int wordlen;
2900  struct minivm_account *vmu;
2901  const char *domain = "";
2902 
2903  /* 0 - minivm; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2904  if (pos > 4)
2905  return NULL;
2906  wordlen = strlen(word);
2907  AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2908  if (!strncasecmp(word, vmu->domain, wordlen)) {
2909  if (domain && strcmp(domain, vmu->domain) && ++which > state)
2910  return ast_strdup(vmu->domain);
2911  /* ignore repeated domains ? */
2912  domain = vmu->domain;
2913  }
2914  }
2915  return NULL;
2916 }
2917 
2918 /*! \brief CLI command to list voicemail accounts */
2919 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2920 {
2921  struct minivm_account *vmu;
2922 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
2923  int count = 0;
2924 
2925  switch (cmd) {
2926  case CLI_INIT:
2927  e->command = "minivm list accounts [for]";
2928  e->usage =
2929  "Usage: minivm list accounts [for <domain>]\n"
2930  " Lists all mailboxes currently set up\n";
2931  return NULL;
2932  case CLI_GENERATE:
2933  return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
2934  }
2935 
2936  if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
2937  return CLI_SHOWUSAGE;
2938  if ((a->argc == 5) && strcmp(a->argv[3],"for"))
2939  return CLI_SHOWUSAGE;
2940 
2941  AST_LIST_LOCK(&minivm_accounts);
2942  if (AST_LIST_EMPTY(&minivm_accounts)) {
2943  ast_cli(a->fd, "There are no voicemail users currently defined\n");
2944  AST_LIST_UNLOCK(&minivm_accounts);
2945  return CLI_FAILURE;
2946  }
2947  ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2948  ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
2949  AST_LIST_TRAVERSE(&minivm_accounts, vmu, list) {
2950  char tmp[256] = "";
2951  if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
2952  count++;
2953  snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2954  ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, S_OR(vmu->etemplate, "-"),
2955  S_OR(vmu->ptemplate, "-"),
2956  S_OR(vmu->zonetag, "-"),
2957  S_OR(vmu->attachfmt, "-"),
2958  vmu->fullname);
2959  }
2960  }
2961  AST_LIST_UNLOCK(&minivm_accounts);
2962  ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
2963  return CLI_SUCCESS;
2964 }
2965 
2966 /*! \brief Show a list of voicemail zones in the CLI */
2967 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2968 {
2969  struct minivm_zone *zone;
2970 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
2971  char *res = CLI_SUCCESS;
2972 
2973  switch (cmd) {
2974  case CLI_INIT:
2975  e->command = "minivm list zones";
2976  e->usage =
2977  "Usage: minivm list zones\n"
2978  " Lists zone message formats\n";
2979  return NULL;
2980  case CLI_GENERATE:
2981  return NULL;
2982  }
2983 
2984  if (a->argc != e->args)
2985  return CLI_SHOWUSAGE;
2986 
2988  if (!AST_LIST_EMPTY(&minivm_zones)) {
2989  ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
2990  ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
2991  AST_LIST_TRAVERSE(&minivm_zones, zone, list) {
2992  ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
2993  }
2994  } else {
2995  ast_cli(a->fd, "There are no voicemail zones currently defined\n");
2996  res = CLI_FAILURE;
2997  }
2999 
3000  return res;
3001 }
3002 
3003 /*! \brief CLI Show settings */
3004 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3005 {
3006  switch (cmd) {
3007  case CLI_INIT:
3008  e->command = "minivm show settings";
3009  e->usage =
3010  "Usage: minivm show settings\n"
3011  " Display Mini-Voicemail general settings\n";
3012  return NULL;
3013  case CLI_GENERATE:
3014  return NULL;
3015  }
3016 
3017  ast_cli(a->fd, "* Mini-Voicemail general settings\n");
3018  ast_cli(a->fd, " -------------------------------\n");
3019  ast_cli(a->fd, "\n");
3020  ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd);
3021  ast_cli(a->fd, " Max silence: %d\n", global_maxsilence);
3022  ast_cli(a->fd, " Silence threshold: %d\n", global_silencethreshold);
3023  ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage);
3024  ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage);
3025  ast_cli(a->fd, " Default format: %s\n", default_vmformat);
3026  ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify);
3027  ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
3028  ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
3029  ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
3030 
3031  ast_cli(a->fd, "\n");
3032  return CLI_SUCCESS;
3033 }
3034 
3035 /*! \brief Show stats */
3036 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3037 {
3038  struct ast_tm timebuf;
3039  char buf[BUFSIZ];
3040 
3041  switch (cmd) {
3042 
3043  case CLI_INIT:
3044  e->command = "minivm show stats";
3045  e->usage =
3046  "Usage: minivm show stats\n"
3047  " Display Mini-Voicemail counters\n";
3048  return NULL;
3049  case CLI_GENERATE:
3050  return NULL;
3051  }
3052 
3053  ast_cli(a->fd, "* Mini-Voicemail statistics\n");
3054  ast_cli(a->fd, " -------------------------\n");
3055  ast_cli(a->fd, "\n");
3056  ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts);
3057  ast_cli(a->fd, " Templates: %5d\n", global_stats.templates);
3058  ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones);
3059  if (global_stats.receivedmessages == 0) {
3060  ast_cli(a->fd, " Received messages since last reset: <none>\n");
3061  } else {
3062  ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
3063  ast_localtime(&global_stats.lastreceived, &timebuf, NULL);
3064  ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
3065  ast_cli(a->fd, " Last received voicemail: %s\n", buf);
3066  }
3067  ast_localtime(&global_stats.reset, &timebuf, NULL);
3068  ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
3069  ast_cli(a->fd, " Last reset: %s\n", buf);
3070 
3071  ast_cli(a->fd, "\n");
3072  return CLI_SUCCESS;
3073 }
3074 
3075 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
3076 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3077 {
3078  struct minivm_account *vmu;
3079  char *username, *domain, *colname;
3080 
3081  username = ast_strdupa(data);
3082 
3083  if ((colname = strchr(username, ':'))) {
3084  *colname = '\0';
3085  colname++;
3086  } else {
3087  colname = "path";
3088  }
3089  if ((domain = strchr(username, '@'))) {
3090  *domain = '\0';
3091  domain++;
3092  }
3093  if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
3094  ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
3095  return 0;
3096  }
3097 
3098  if (!(vmu = find_account(domain, username, TRUE)))
3099  return 0;
3100 
3101  if (!strcasecmp(colname, "hasaccount")) {
3102  ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
3103  } else if (!strcasecmp(colname, "fullname")) {
3104  ast_copy_string(buf, vmu->fullname, len);
3105  } else if (!strcasecmp(colname, "email")) {
3106  if (!ast_strlen_zero(vmu->email))
3107  ast_copy_string(buf, vmu->email, len);
3108  else
3109  snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
3110  } else if (!strcasecmp(colname, "pager")) {
3111  ast_copy_string(buf, vmu->pager, len);
3112  } else if (!strcasecmp(colname, "etemplate")) {
3113  if (!ast_strlen_zero(vmu->etemplate))
3114  ast_copy_string(buf, vmu->etemplate, len);
3115  else
3116  ast_copy_string(buf, "email-default", len);
3117  } else if (!strcasecmp(colname, "language")) {
3118  ast_copy_string(buf, vmu->language, len);
3119  } else if (!strcasecmp(colname, "timezone")) {
3120  ast_copy_string(buf, vmu->zonetag, len);
3121  } else if (!strcasecmp(colname, "ptemplate")) {
3122  if (!ast_strlen_zero(vmu->ptemplate))
3123  ast_copy_string(buf, vmu->ptemplate, len);
3124  else
3125  ast_copy_string(buf, "email-default", len);
3126  } else if (!strcasecmp(colname, "accountcode")) {
3127  ast_copy_string(buf, vmu->accountcode, len);
3128  } else if (!strcasecmp(colname, "pincode")) {
3129  ast_copy_string(buf, vmu->pincode, len);
3130  } else if (!strcasecmp(colname, "path")) {
3131  check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
3132  } else { /* Look in channel variables */
3133  struct ast_variable *var;
3134 
3135  for (var = vmu->chanvars ; var ; var = var->next)
3136  if (!strcmp(var->name, colname)) {
3137  ast_copy_string(buf, var->value, len);
3138  break;
3139  }
3140  }
3141 
3142  if(ast_test_flag(vmu, MVM_ALLOCED))
3143  free_user(vmu);
3144 
3145  return 0;
3146 }
3147 
3148 /*! \brief lock directory
3149 
3150  only return failure if ast_lock_path returns 'timeout',
3151  not if the path does not exist or any other reason
3152 */
3153 static int vm_lock_path(const char *path)
3154 {
3155  switch (ast_lock_path(path)) {
3156  case AST_LOCK_TIMEOUT:
3157  return -1;
3158  default:
3159  return 0;
3160  }
3161 }
3162 
3163 /*! \brief Access counter file, lock directory, read and possibly write it again changed
3164  \param directory Directory to crate file in
3165  \param countername filename
3166  \param value If set to zero, we only read the variable
3167  \param operand 0 to read, 1 to set new value, 2 to change
3168  \return -1 on error, otherwise counter value
3169 */
3170 static int access_counter_file(char *directory, char *countername, int value, int operand)
3171 {
3172  char filename[BUFSIZ];
3173  char readbuf[BUFSIZ];
3174  FILE *counterfile;
3175  int old = 0, counter = 0;
3176 
3177  /* Lock directory */
3178  if (vm_lock_path(directory)) {
3179  return -1; /* Could not lock directory */
3180  }
3181  snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
3182  if (operand != 1) {
3183  counterfile = fopen(filename, "r");
3184  if (counterfile) {
3185  if(fgets(readbuf, sizeof(readbuf), counterfile)) {
3186  ast_debug(3, "Read this string from counter file: %s\n", readbuf);
3187  old = counter = atoi(readbuf);
3188  }
3189  fclose(counterfile);
3190  }
3191  }
3192  switch (operand) {
3193  case 0: /* Read only */
3194  ast_unlock_path(directory);
3195  ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
3196  return counter;
3197  break;
3198  case 1: /* Set new value */
3199  counter = value;
3200  break;
3201  case 2: /* Change value */
3202  counter += value;
3203  if (counter < 0) /* Don't allow counters to fall below zero */
3204  counter = 0;
3205  break;
3206  }
3207 
3208  /* Now, write the new value to the file */
3209  counterfile = fopen(filename, "w");
3210  if (!counterfile) {
3211  ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
3212  ast_unlock_path(directory);
3213  return -1; /* Could not open file for writing */
3214  }
3215  fprintf(counterfile, "%d\n\n", counter);
3216  fclose(counterfile);
3217  ast_unlock_path(directory);
3218  ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
3219  return counter;
3220 }
3221 
3222 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
3223 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3224 {
3225  char *username, *domain, *countername;
3226  char userpath[BUFSIZ];
3227  int res;
3228 
3229  *buf = '\0';
3230 
3231  username = ast_strdupa(data);
3232 
3233  if ((countername = strchr(username, ':'))) {
3234  *countername = '\0';
3235  countername++;
3236  }
3237 
3238  if ((domain = strchr(username, '@'))) {
3239  *domain = '\0';
3240  domain++;
3241  }
3242 
3243  /* If we have neither username nor domain now, let's give up */
3244  if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3245  ast_log(LOG_ERROR, "No account given\n");
3246  return -1;
3247  }
3248 
3249  if (ast_strlen_zero(countername)) {
3250  ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
3251  return -1;
3252  }
3253 
3254  /* We only have a domain, no username */
3255  if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3256  domain = username;
3257  username = NULL;
3258  }
3259 
3260  /* If we can't find account or if the account is temporary, return. */
3261  if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
3262  ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3263  return 0;
3264  }
3265 
3266  create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3267 
3268  /* We have the path, now read the counter file */
3269  res = access_counter_file(userpath, countername, 0, 0);
3270  if (res >= 0)
3271  snprintf(buf, len, "%d", res);
3272  return 0;
3273 }
3274 
3275 /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
3276 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
3277 {
3278  char *username, *domain, *countername, *operand;
3279  char userpath[BUFSIZ];
3280  int change = 0;
3281  int operation = 0;
3282 
3283  if(!value)
3284  return -1;
3285  change = atoi(value);
3286 
3287  username = ast_strdupa(data);
3288 
3289  if ((countername = strchr(username, ':'))) {
3290  *countername = '\0';
3291  countername++;
3292  }
3293  if ((operand = strchr(countername, ':'))) {
3294  *operand = '\0';
3295  operand++;
3296  }
3297 
3298  if ((domain = strchr(username, '@'))) {
3299  *domain = '\0';
3300  domain++;
3301  }
3302 
3303  /* If we have neither username nor domain now, let's give up */
3304  if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3305  ast_log(LOG_ERROR, "No account given\n");
3306  return -1;
3307  }
3308 
3309  /* We only have a domain, no username */
3310  if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3311  domain = username;
3312  username = NULL;
3313  }
3314 
3315  if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
3316  ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
3317  return -1;
3318  }
3319 
3320  /* If we can't find account or if the account is temporary, return. */
3321  if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
3322  ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3323  return 0;
3324  }
3325 
3326  create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3327  /* Now, find out our operator */
3328  if (*operand == 'i') /* Increment */
3329  operation = 2;
3330  else if (*operand == 'd') {
3331  change = change * -1;
3332  operation = 2;
3333  } else if (*operand == 's')
3334  operation = 1;
3335  else {
3336  ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3337  return -1;
3338  }
3339 
3340  /* We have the path, now read the counter file */
3341  access_counter_file(userpath, countername, change, operation);
3342  return 0;
3343 }
3344 
3345 
3346 /*! \brief CLI commands for Mini-voicemail */
3347 static struct ast_cli_entry cli_minivm[] = {
3348  AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
3349  AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
3350  AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"),
3351  AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
3352  AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
3353  AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
3354 };
3355 
3356 static struct ast_custom_function minivm_counter_function = {
3357  .name = "MINIVMCOUNTER",
3358  .read = minivm_counter_func_read,
3359  .write = minivm_counter_func_write,
3360 };
3361 
3362 static struct ast_custom_function minivm_account_function = {
3363  .name = "MINIVMACCOUNT",
3364  .read = minivm_account_func_read,
3365 };
3366 
3367 /*! \brief Load mini voicemail module */
3368 static int load_module(void)
3369 {
3370  int res;
3371 
3372  res = ast_register_application_xml(app_minivm_record, minivm_record_exec);
3373  res = ast_register_application_xml(app_minivm_greet, minivm_greet_exec);
3374  res = ast_register_application_xml(app_minivm_notify, minivm_notify_exec);
3375  res = ast_register_application_xml(app_minivm_delete, minivm_delete_exec);
3376  res = ast_register_application_xml(app_minivm_accmess, minivm_accmess_exec);
3377  res = ast_register_application_xml(app_minivm_mwi, minivm_mwi_exec);
3378 
3379  ast_custom_function_register(&minivm_account_function);
3380  ast_custom_function_register(&minivm_counter_function);
3381  if (res)
3382  return(res);
3383 
3384  if ((res = load_config(0)))
3385  return(res);
3386 
3387  ast_cli_register_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
3388 
3389  /* compute the location of the voicemail spool directory */
3390  snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3391 
3392  return res;
3393 }
3394 
3395 /*! \brief Reload mini voicemail module */
3396 static int reload(void)
3397 {
3398  return(load_config(1));
3399 }
3400 
3401 /*! \brief Reload cofiguration */
3402 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3403 {
3404 
3405  switch (cmd) {
3406  case CLI_INIT:
3407  e->command = "minivm reload";
3408  e->usage =
3409  "Usage: minivm reload\n"
3410  " Reload mini-voicemail configuration and reset statistics\n";
3411  return NULL;
3412  case CLI_GENERATE:
3413  return NULL;
3414  }
3415 
3416  reload();
3417  ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
3418  return CLI_SUCCESS;
3419 }
3420 
3421 /*! \brief Unload mini voicemail module */
3422 static int unload_module(void)
3423 {
3424  int res;
3425 
3426  res = ast_unregister_application(app_minivm_record);
3427  res |= ast_unregister_application(app_minivm_greet);
3428  res |= ast_unregister_application(app_minivm_notify);
3429  res |= ast_unregister_application(app_minivm_delete);
3430  res |= ast_unregister_application(app_minivm_accmess);
3431  res |= ast_unregister_application(app_minivm_mwi);
3432 
3433  ast_cli_unregister_multiple(cli_minivm, ARRAY_LEN(cli_minivm));
3434  ast_custom_function_unregister(&minivm_account_function);
3435  ast_custom_function_unregister(&minivm_counter_function);
3436 
3437  message_destroy_list(); /* Destroy list of voicemail message templates */
3438  timezone_destroy_list(); /* Destroy list of timezones */
3439  vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
3440 
3441  return res;
3442 }
3443 
3444 
3445 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3446  .support_level = AST_MODULE_SUPPORT_EXTENDED,
3447  .load = load_module,
3448  .unload = unload_module,
3449  .reload = reload,
3450 );
const char * name
Definition: pbx.h:119
struct ast_variable * next
char attachfmt[80]
Definition: app_minivm.c:619
static int reload(void)
Reload mini voicemail module.
Definition: app_minivm.c:3396
Main Channel structure associated with a channel.
struct timeval reset
Definition: app_minivm.c:676
char * str
Subscriber phone number (Malloced)
Definition: channel.h:291
int ast_streamfile(struct ast_channel *c, const char *filename, const char *preflang)
Streams a file.
Definition: file.c:1293
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
Asterisk locking-related definitions:
Asterisk main include file. File version handling, generic pbx functions.
static char * handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
CLI command to list voicemail accounts.
Definition: app_minivm.c:2919
struct stasis_message * ast_mwi_blob_create(struct ast_mwi_state *mwi_state, struct stasis_message_type *message_type, struct ast_json *blob)
Creates a ast_mwi_blob message.
Definition: mwi.c:467
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
#define MVM_REVIEW
Definition: app_minivm.c:531
static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
Append new mailbox to mailbox list from configuration file.
Definition: app_minivm.c:2443
char etemplate[80]
Definition: app_minivm.c:620
CallerID (and other GR30) management and generation Includes code and algorithms from the Zapata libr...
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
char name[80]
Definition: app_minivm.c:661
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:2958
static char * handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
CLI routine for listing templates.
Definition: app_minivm.c:2855
static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
basically mkdir -p $dest/$context/$ext/$folder
static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
Creates a file system path expression for a folder within the voicemail data folder and the appropria...
Time-related functions and macros.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
struct ast_party_name name
Subscriber name.
Definition: channel.h:340
Convenient Signal Processing routines.
static int global_vmminmessage
Definition: app_minivm.c:689
static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag)
Sends email notification that a user has a new voicemail waiting for them.
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
struct ast_mwi_state * ast_mwi_create(const char *mailbox, const char *context)
Create a ast_mwi_state object.
Definition: mwi.c:152
descriptor for a cli entry.
Definition: cli.h:171
static char * handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Reload cofiguration.
Definition: app_minivm.c:3402
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
${MINIVMCOUNTER()} Dialplan function - read counters
Definition: app_minivm.c:3223
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
char language[MAX_LANGUAGE]
Definition: app_minivm.c:615
struct ast_tm * ast_localtime(const struct timeval *timep, struct ast_tm *p_tm, const char *zone)
Timezone-independent version of localtime_r(3).
Definition: localtime.c:1739
mvm_messagetype
Message types for notification.
Definition: app_minivm.c:556
Structure for variables, used for configurations and for channel variables.
char username[AST_MAX_CONTEXT]
Definition: app_minivm.c:605
static int timezone_add(const char *zonename, const char *config)
Add time zone to memory list.
Definition: app_minivm.c:2556
void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
int ast_say_digit_str(struct ast_channel *chan, const char *num, const char *ints, const char *lang)
says digits of a string
Definition: channel.c:8259
ast_channel_state
ast_channel states
Definition: channelstate.h:35
char * str
Subscriber name (Malloced)
Definition: channel.h:264
static void free_zone(struct minivm_zone *z)
Free Mini Voicemail timezone.
Definition: app_minivm.c:2538
int ast_str_append(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Append to a thread local dynamic string.
Definition: strings.h:1139
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
Definition: linkedlists.h:450
static int global_vmmaxmessage
Definition: app_minivm.c:690
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
static char global_mailcmd[160]
Definition: app_minivm.c:694
char accountcode[AST_MAX_ACCOUNT_CODE]
Definition: app_minivm.c:612
static int check_mime(const char *str)
Check if the string would need encoding within the MIME standard, to avoid confusing certain mail sof...
Generic File Format Support. Should be included by clients of the file handling routines. File service providers should instead include mod_format.h.
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3326
int ast_unlock_path(const char *path)
Unlock a path.
Definition: main/app.c:2630
static char * handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Show a list of voicemail zones in the CLI.
Definition: app_minivm.c:2967
int ast_filedelete(const char *filename, const char *fmt)
Deletes a file.
Definition: file.c:1141
int ast_channel_setoption(struct ast_channel *channel, int option, void *data, int datalen, int block)
Sets an option on a channel.
Definition: channel.c:7422
struct timeval lastreceived
Definition: app_minivm.c:678
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
char domain[AST_MAX_CONTEXT]
Definition: app_minivm.c:606
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
static int get_date(char *s, int len)
Gets the current date and time, as formatted string.
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
#define MAX_LANGUAGE
Definition: channel.h:172
Utility functions.
int args
This gets set in ast_cli_register()
Definition: cli.h:185
int ast_base64_encode_file_path(const char *filename, FILE *outputfile, const char *endl)
Performs a base 64 encode algorithm on the contents of a File.
Definition: utils.c:702
int voicemailaccounts
Definition: app_minivm.c:672
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
char pincode[10]
Definition: app_minivm.c:608
Number structure.
Definition: app_followme.c:154
#define AST_MAX_ACCOUNT_CODE
Definition: channel.h:170
Custom localtime functions for multiple timezones.
int ast_str_set(struct ast_str **buf, ssize_t max_len, const char *fmt,...)
Set a dynamic string using variable arguments.
Definition: strings.h:1113
static struct minivm_stats global_stats
Statistics for voicemail.
Definition: app_minivm.c:682
struct ast_party_id id
Caller party ID.
Definition: channel.h:420
char locale[20]
Definition: app_minivm.c:644
enum AST_LOCK_RESULT ast_lock_path(const char *path)
Lock a filesystem path.
Definition: main/app.c:2614
Configuration File Parser.
static void timezone_destroy_list(void)
Clear list of timezones.
Definition: app_minivm.c:2544
#define ast_config_load(filename, flags)
Load a config file.
char exit[80]
Definition: app_minivm.c:618
char email[80]
Definition: app_minivm.c:610
General Asterisk PBX channel definitions.
Asterisk JSON abstraction layer.
unsigned int flags
Definition: app_minivm.c:622
Asterisk file paths, configured in asterisk.conf.
#define MVM_OPERATOR
Definition: app_minivm.c:532
static int unload_module(void)
Unload mini voicemail module.
Definition: app_minivm.c:3422
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_dummy_channel_alloc()
Create a fake channel structure.
Definition: channel.h:1282
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
static int global_maxgreet
Definition: app_minivm.c:692
Caller Party information.
Definition: channel.h:418
int ast_play_and_wait(struct ast_channel *chan, const char *fn)
Play a stream and wait for a digit, returning the digit that was pressed.
Definition: main/app.c:1616
#define S_COR(a, b, c)
returns the equivalent of logic or for strings, with an additional boolean check: second one if not e...
Definition: strings.h:87
static struct ast_cli_entry cli_minivm[]
CLI commands for Mini-voicemail.
Definition: app_minivm.c:3347
int ast_play_and_record_full(struct ast_channel *chan, const char *playfile, const char *recordfile, int maxtime_sec, const char *fmt, int *duration, int *sound_duration, int beep, int silencethreshold, int maxsilence_ms, const char *path, const char *acceptdtmf, const char *canceldtmf, int skip_confirmation_sound, enum ast_record_if_exists if_exists)
Record a file based on input from a channel This function will play "auth-thankyou" upon successful r...
Definition: main/app.c:2149
char zonetag[80]
Definition: app_minivm.c:616
char timezone[80]
Definition: app_minivm.c:662
A set of macros to manage forward-linked lists.
#define ast_debug(level,...)
Log a DEBUG message.
static const char * ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
Wraps a character sequence in double quotes, escaping occurences of quotes within the string...
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
static char * handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
CLI Show settings.
Definition: app_minivm.c:3004
static char * message_template_parse_filebody(const char *filename)
Read message template from file.
Definition: app_minivm.c:2588
int ast_exists_extension(struct ast_channel *c, const char *context, const char *exten, int priority, const char *callerid)
Determine whether an extension exists.
Definition: pbx.c:4175
int ast_app_parse_options(const struct ast_app_option *options, struct ast_flags *flags, char **args, char *optstr)
Parses a string containing application options and sets flags/arguments.
Definition: main/app.c:3066
static int vm_delete(char *file)
Removes the voicemail sound and information file.
struct stasis_topic * ast_mwi_topic(const char *uniqueid)
Get the Stasis Message Bus API topic for MWI messages on a unique ID.
Definition: mwi.c:104
Core PBX routines and definitions.
static int load_module(void)
Load mini voicemail module.
Definition: app_minivm.c:3368
static int global_maxsilence
Definition: app_minivm.c:691
#define AST_LIST_HEAD_STATIC(name, type)
Defines a structure to be used to hold a list of specified type, statically initialized.
Definition: linkedlists.h:291
static FILE * minivmlogfile
Definition: app_minivm.c:687
double volgain
Definition: app_minivm.c:624
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
struct ast_variable * chanvars
Definition: app_minivm.c:623
static struct ast_vm_user * find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
Finds a voicemail user from the realtime engine.
static char * handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Show stats.
Definition: app_minivm.c:3036
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true". This function checks to see whether a string passed to it is an indication of an "true" value. It checks to see if the string is "yes", "true", "y", "t", "on" or "1".
Definition: utils.c:2199
Support for dynamic strings.
Definition: strings.h:623
int ast_safe_system(const char *s)
Safely spawn an OS shell command while closing file descriptors.
Definition: extconf.c:829
char uniqueid[20]
Definition: app_minivm.c:617
#define AST_OPTION_RXGAIN
#define AST_APP_OPTION_ARG(option, flagno, argno)
Declares an application option that accepts an argument.
char msg_format[BUFSIZ]
Definition: app_minivm.c:663
Voicemail time zones.
Definition: app_minivm.c:660
static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_options *options)
Prompts the user and records a voicemail to a mailbox.
int receivedmessages
Definition: app_minivm.c:677
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
void stasis_publish(struct stasis_topic *topic, struct stasis_message *message)
Publish a message to a topic's subscribers.
Definition: stasis.c:1511
static ast_mutex_t minivmloglock
Definition: app_minivm.c:685
#define AST_MAX_CONTEXT
Definition: channel.h:135
char * command
Definition: cli.h:186
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
int ast_stream_and_wait(struct ast_channel *chan, const char *file, const char *digits)
stream file until digit If the file name is non-empty, try to play it.
Definition: file.c:1878
static char * message_template_parse_emailbody(const char *body)
Parse emailbody template from configuration file.
Definition: app_minivm.c:2628
int ast_strftime(char *buf, size_t len, const char *format, const struct ast_tm *tm)
Special version of strftime(3) that handles fractions of a second. Takes the same arguments as strfti...
Definition: localtime.c:2524
#define ast_publish_mwi_state_channel(mailbox, context, new_msgs, old_msgs, channel_id)
Publish a MWI state update associated with some channel.
Definition: mwi.h:395
static char global_externnotify[160]
Definition: app_minivm.c:695
static double global_volgain
Definition: app_minivm.c:702
Structure used to handle boolean flags.
Definition: utils.h:199
The list of e-mail time zones.
Definition: app_minivm.c:668
char fullname[120]
Definition: app_minivm.c:609
static int vm_lock_path(const char *path)
lock directory
Definition: app_minivm.c:3153
Options for leaving voicemail with the voicemail() application.
Definition: app_minivm.c:654
const char * usage
Definition: cli.h:177
int pbx_builtin_setvar_helper(struct ast_channel *chan, const char *name, const char *value)
Add a variable to the channel variable stack, removing the most recently set value for the same name...
#define SENDMAIL
Default mail command to mail voicemail. Change it with the mailcmd= command in voicemail.conf.
Definition: app_minivm.c:541
int ast_waitfordigit(struct ast_channel *c, int ms)
Waits for a digit.
Definition: channel.c:3175
The list of e-mail templates.
Definition: app_minivm.c:651
void ast_str_reset(struct ast_str *buf)
Reset the content of a dynamic string. Useful before a series of ast_str_append.
Definition: strings.h:693
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:730
static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
${MINIVMCOUNTER()} Dialplan function - changes counter data
Definition: app_minivm.c:3276
char ptemplate[80]
Definition: app_minivm.c:621
char pager[80]
Definition: app_minivm.c:611
struct ast_channel_snapshot * ast_channel_snapshot_get_latest(const char *uniqueid)
Obtain the latest ast_channel_snapshot from the Stasis Message Bus API cache. This is an ao2 object...
Standard Command Line Interface.
static int load_config(int reload)
Load minivoicemail configuration.
Definition: app_minivm.c:2713
structure for queuing ARI channel variable setting
Definition: control.c:707
char externnotify[160]
Definition: app_minivm.c:614
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:425
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
${MINIVMACCOUNT()} Dialplan function - reads account data
Definition: app_minivm.c:3076
int ast_waitstream(struct ast_channel *c, const char *breakon)
Waits for a stream to stop or digit to be pressed.
Definition: file.c:1840
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:1129
Asterisk MWI API.
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2805
static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
Record specific messages for voicemail account.
Definition: app_minivm.c:2345
Abstract JSON element (object, array, string, int, ...).
static int access_counter_file(char *directory, char *countername, int value, int operand)
Access counter file, lock directory, read and possibly write it again changed.
Definition: app_minivm.c:3170
char fromaddress[100]
Definition: app_minivm.c:640
unsigned char valid
TRUE if the name information is valid/present.
Definition: channel.h:279
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
struct stasis_message_type * ast_mwi_vm_app_type(void)
Get the Stasis Message Bus API message type for voicemail application specific messages.
Structure for gathering statistics.
Definition: app_minivm.c:671
static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
Run external notification for voicemail message.
Definition: app_minivm.c:1643
static ast_mutex_t minivmlock
Definition: app_minivm.c:684
The structure that contains MWI state.
Definition: mwi.h:455
Say numbers and dates (maybe words one day too)
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_dsp_get_threshold_from_settings(enum threshold which)
Get silence threshold from dsp.conf.
Definition: dsp.c:2009
Asterisk module definitions.
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
static struct ast_flags globalflags
Definition: app_minivm.c:699
int ast_safe_execvp(int dualfork, const char *file, char *const argv[])
Safely spawn an external program while closing file descriptors.
Definition: asterisk.c:1222
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
unsigned char valid
TRUE if the number information is valid/present.
Definition: channel.h:297
static const char * ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *start, size_t preamble, size_t postamble)
Encode a string according to the MIME rules for encoding strings that are not 7-bit clean or contain ...
#define ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1558
int ast_stopstream(struct ast_channel *c)
Stops a stream.
Definition: file.c:222
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:640
static int apply_general_options(struct ast_variable *var)
Apply general configuration options.
Definition: app_minivm.c:2655
char serveremail[80]
Definition: app_minivm.c:613
static char global_logfile[PATH_MAX]
Definition: app_minivm.c:696
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
static void populate_defaults(struct ast_vm_user *vmu)
Sets default voicemail system options to a voicemail user.
int ast_mkdir(const char *path, int mode)
Recursively create directory path.
Definition: utils.c:2479
struct ast_party_number number
Subscriber phone number.
Definition: channel.h:342