Asterisk - The Open Source Telephony Project  GIT-master-a24979a
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"
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 */
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 
575  OPT_SILENT = (1 << 0),
576  OPT_BUSY_GREETING = (1 << 1),
578  OPT_TEMP_GREETING = (1 << 3),
579  OPT_NAME_GREETING = (1 << 4),
580  OPT_RECORDGAIN = (1 << 5),
581 };
582 
586 };
587 
593 });
594 
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 */
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 */
682 static struct minivm_stats global_stats;
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 */
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 
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 
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 {
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;
926  while ((this = AST_LIST_REMOVE_HEAD(&minivm_accounts, list)))
927  ast_free(this);
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 
947  /* Is this the voicemail account we're looking for? */
948  if (!strcasecmp(domain, cur->domain) && !strcasecmp(username, cur->username))
949  break;
950  }
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();
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 
985  if (!retval)
986  return NULL;
987 
988  if (username)
989  ast_copy_string(retval->username, username, sizeof(retval->username));
990 
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);
1001 
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;
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 
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 
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);
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:macrocontext:exten:priority:callerchan:callerid:origdate:origtime:duration:durationstatus:accountcode" */
1882  "%s:%s:%s:%s:%d:%s:%s:%s:%s:%d:%s:%s\n",
1883  username,
1884  ast_channel_context(chan),
1886  ast_channel_exten(chan),
1887  ast_channel_priority(chan),
1888  ast_channel_name(chan),
1889  callerid,
1890  date,
1891  timebuf,
1892  duration,
1893  duration < global_vmminmessage ? "IGNORED" : "OK",
1894  vmu->accountcode
1895  );
1896  fprintf(txt, "%s", logbuf);
1897  if (minivmlogfile) {
1899  fprintf(minivmlogfile, "%s", logbuf);
1901  }
1902 
1903  if (sound_duration < global_vmminmessage) {
1904  ast_verb(3, "Recording was %d seconds long but needs to be at least %d - abandoning\n", sound_duration, global_vmminmessage);
1905  fclose(txt);
1906  ast_filedelete(tmptxtfile, NULL);
1907  unlink(tmptxtfile);
1908  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1909  return 0;
1910  }
1911  fclose(txt); /* Close log file */
1912  if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1913  ast_debug(1, "The recorded media file is gone, so we should remove the .txt file too!\n");
1914  unlink(tmptxtfile);
1915  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
1916  if(ast_test_flag(vmu, MVM_ALLOCED))
1917  free_user(vmu);
1918  return 0;
1919  }
1920 
1921  /* Set channel variables for the notify application */
1922  pbx_builtin_setvar_helper(chan, "MVM_FILENAME", tmptxtfile);
1923  snprintf(timebuf, sizeof(timebuf), "%d", duration);
1924  pbx_builtin_setvar_helper(chan, "MVM_DURATION", timebuf);
1925  pbx_builtin_setvar_helper(chan, "MVM_FORMAT", fmt);
1926 
1927  }
1930 #if 0
1931  /* Go ahead and delete audio files from system, they're not needed any more */
1932  if (ast_fileexists(tmptxtfile, NULL, NULL) <= 0) {
1933  ast_filedelete(tmptxtfile, NULL);
1934  /* Even not being used at the moment, it's better to convert ast_log to ast_debug anyway */
1935  ast_debug(2, "-_-_- Deleted audio file after notification :: %s \n", tmptxtfile);
1936  }
1937 #endif
1938 
1939  if (res > 0)
1940  res = 0;
1941 
1942  if(ast_test_flag(vmu, MVM_ALLOCED))
1943  free_user(vmu);
1944 
1945  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
1946  return res;
1947 }
1948 
1949 /*!\internal
1950  * \brief Queue a message waiting event */
1951 static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
1952 {
1953  char *mailbox, *context;
1954 
1955  mailbox = ast_strdupa(mbx);
1956  context = ast_strdupa(ctx);
1957  if (ast_strlen_zero(context)) {
1958  context = "default";
1959  }
1960 
1961  ast_publish_mwi_state_channel(mailbox, context, new + urgent, old, channel_id);
1962 }
1963 
1964 /*!\internal
1965  * \brief Send MWI using interal Asterisk event subsystem */
1966 static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
1967 {
1968  int argc;
1969  char *argv[4];
1970  int res = 0;
1971  char *tmpptr;
1972  char tmp[PATH_MAX];
1973  char *mailbox;
1974  char *domain;
1975  if (ast_strlen_zero(data)) {
1976  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
1977  return -1;
1978  }
1979  tmpptr = ast_strdupa((char *)data);
1980  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
1981  if (argc < 4) {
1982  ast_log(LOG_ERROR, "%d arguments passed to MiniVM_MWI, need 4.\n", argc);
1983  return -1;
1984  }
1985  ast_copy_string(tmp, argv[0], sizeof(tmp));
1986  mailbox = tmp;
1987  domain = strchr(tmp, '@');
1988  if (domain) {
1989  *domain = '\0';
1990  domain++;
1991  }
1993  ast_log(LOG_ERROR, "Need mailbox@context as argument. Sorry. Argument 0 %s\n", argv[0]);
1994  return -1;
1995  }
1996  queue_mwi_event(ast_channel_uniqueid(chan), mailbox, domain, atoi(argv[1]), atoi(argv[2]), atoi(argv[3]));
1997 
1998  return res;
1999 }
2000 
2001 
2002 /*!\internal
2003  * \brief Notify voicemail account owners - either generic template or user specific */
2004 static int minivm_notify_exec(struct ast_channel *chan, const char *data)
2005 {
2006  int argc;
2007  char *argv[2];
2008  int res = 0;
2009  char tmp[PATH_MAX];
2010  char *domain;
2011  char *tmpptr;
2012  struct minivm_account *vmu;
2013  char *username;
2014  const char *template = "";
2015  const char *filename;
2016  const char *format;
2017  const char *duration_string;
2018  if (ast_strlen_zero(data)) {
2019  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2020  return -1;
2021  }
2022  tmpptr = ast_strdupa((char *)data);
2023  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2024 
2025  if (argc == 2 && !ast_strlen_zero(argv[1]))
2026  template = argv[1];
2027 
2028  ast_copy_string(tmp, argv[0], sizeof(tmp));
2029  username = tmp;
2030  domain = strchr(tmp, '@');
2031  if (domain) {
2032  *domain = '\0';
2033  domain++;
2034  }
2035  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2036  ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2037  return -1;
2038  }
2039 
2040  if(!(vmu = find_account(domain, username, TRUE))) {
2041  /* We could not find user, let's exit */
2042  ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2043  pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", "FAILED");
2044  return -1;
2045  }
2046 
2047  ast_channel_lock(chan);
2048  if ((filename = pbx_builtin_getvar_helper(chan, "MVM_FILENAME"))) {
2049  filename = ast_strdupa(filename);
2050  }
2051  ast_channel_unlock(chan);
2052  /* Notify of new message to e-mail and pager */
2053  if (!ast_strlen_zero(filename)) {
2054  ast_channel_lock(chan);
2055  if ((format = pbx_builtin_getvar_helper(chan, "MVM_FORMAT"))) {
2057  }
2058  if ((duration_string = pbx_builtin_getvar_helper(chan, "MVM_DURATION"))) {
2059  duration_string = ast_strdupa(duration_string);
2060  }
2061  ast_channel_unlock(chan);
2062  res = notify_new_message(chan, template, vmu, filename, atoi(duration_string),
2063  format,
2064  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2065  S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL));
2066  }
2067 
2068  pbx_builtin_setvar_helper(chan, "MVM_NOTIFY_STATUS", res == 0 ? "SUCCESS" : "FAILED");
2069 
2070 
2071  if(ast_test_flag(vmu, MVM_ALLOCED))
2072  free_user(vmu);
2073 
2074  /* Ok, we're ready to rock and roll. Return to dialplan */
2075 
2076  return res;
2077 
2078 }
2079 
2080 /*!\internal
2081  * \brief Dialplan function to record voicemail */
2082 static int minivm_record_exec(struct ast_channel *chan, const char *data)
2083 {
2084  int res = 0;
2085  char *tmp;
2086  struct leave_vm_options leave_options;
2087  int argc;
2088  char *argv[2];
2089  struct ast_flags flags = { 0 };
2090  char *opts[OPT_ARG_ARRAY_SIZE];
2091 
2092  memset(&leave_options, 0, sizeof(leave_options));
2093 
2094  /* Answer channel if it's not already answered */
2095  if (ast_channel_state(chan) != AST_STATE_UP)
2096  ast_answer(chan);
2097 
2098  if (ast_strlen_zero(data)) {
2099  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2100  return -1;
2101  }
2102  tmp = ast_strdupa((char *)data);
2103  argc = ast_app_separate_args(tmp, ',', argv, ARRAY_LEN(argv));
2104  if (argc == 2) {
2105  if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1])) {
2106  return -1;
2107  }
2108  ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2109  if (ast_test_flag(&flags, OPT_RECORDGAIN)) {
2110  int gain;
2111 
2112  if (sscanf(opts[OPT_ARG_RECORDGAIN], "%30d", &gain) != 1) {
2113  ast_log(LOG_WARNING, "Invalid value '%s' provided for record gain option\n", opts[OPT_ARG_RECORDGAIN]);
2114  return -1;
2115  } else
2116  leave_options.record_gain = (signed char) gain;
2117  }
2118  }
2119 
2120  /* Now run the appliation and good luck to you! */
2121  res = leave_voicemail(chan, argv[0], &leave_options);
2122 
2123  if (res == ERROR_LOCK_PATH) {
2124  ast_log(LOG_ERROR, "Could not leave voicemail. The path is already locked.\n");
2125  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "FAILED");
2126  res = 0;
2127  }
2128  pbx_builtin_setvar_helper(chan, "MVM_RECORD_STATUS", "SUCCESS");
2129 
2130  return res;
2131 }
2132 
2133 /*!\internal
2134  * \brief Play voicemail prompts - either generic or user specific */
2135 static int minivm_greet_exec(struct ast_channel *chan, const char *data)
2136 {
2137  struct leave_vm_options leave_options = { 0, '\0'};
2138  int argc;
2139  char *argv[2];
2140  struct ast_flags flags = { 0 };
2141  char *opts[OPT_ARG_ARRAY_SIZE];
2142  int res = 0;
2143  int ausemacro = 0;
2144  int ousemacro = 0;
2145  int ouseexten = 0;
2146  char tmp[PATH_MAX];
2147  char dest[PATH_MAX];
2148  char prefile[PATH_MAX] = "";
2149  char tempfile[PATH_MAX] = "";
2150  char ext_context[256] = "";
2151  char *domain;
2152  char ecodes[16] = "#";
2153  char *tmpptr;
2154  struct minivm_account *vmu;
2155  char *username;
2156 
2157  if (ast_strlen_zero(data)) {
2158  ast_log(LOG_ERROR, "Minivm needs at least an account argument \n");
2159  return -1;
2160  }
2161  tmpptr = ast_strdupa((char *)data);
2162  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2163 
2164  if (argc == 2) {
2165  if (ast_app_parse_options(minivm_app_options, &flags, opts, argv[1]))
2166  return -1;
2167  ast_copy_flags(&leave_options, &flags, OPT_SILENT | OPT_BUSY_GREETING | OPT_UNAVAIL_GREETING );
2168  }
2169 
2170  ast_copy_string(tmp, argv[0], sizeof(tmp));
2171  username = tmp;
2172  domain = strchr(tmp, '@');
2173  if (domain) {
2174  *domain = '\0';
2175  domain++;
2176  }
2177  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2178  ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument: %s\n", argv[0]);
2179  return -1;
2180  }
2181  ast_debug(1, "Trying to find configuration for user %s in domain %s\n", username, domain);
2182 
2183  if (!(vmu = find_account(domain, username, TRUE))) {
2184  ast_log(LOG_ERROR, "Could not allocate memory. \n");
2185  return -1;
2186  }
2187 
2188  /* Answer channel if it's not already answered */
2189  if (ast_channel_state(chan) != AST_STATE_UP)
2190  ast_answer(chan);
2191 
2192  /* Setup pre-file if appropriate */
2193  if (strcmp(vmu->domain, "localhost"))
2194  snprintf(ext_context, sizeof(ext_context), "%s@%s", username, vmu->domain);
2195  else
2196  ast_copy_string(ext_context, vmu->domain, sizeof(ext_context));
2197 
2198  if (ast_test_flag(&leave_options, OPT_BUSY_GREETING)) {
2199  res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "busy");
2200  if (res)
2201  snprintf(prefile, sizeof(prefile), "%s%s/%s/busy", MVM_SPOOL_DIR, vmu->domain, username);
2202  } else if (ast_test_flag(&leave_options, OPT_UNAVAIL_GREETING)) {
2203  res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "unavail");
2204  if (res)
2205  snprintf(prefile, sizeof(prefile), "%s%s/%s/unavail", MVM_SPOOL_DIR, vmu->domain, username);
2206  }
2207  /* Check for temporary greeting - it overrides busy and unavail */
2208  snprintf(tempfile, sizeof(tempfile), "%s%s/%s/temp", MVM_SPOOL_DIR, vmu->domain, username);
2209  if (!(res = check_dirpath(dest, sizeof(dest), vmu->domain, username, "temp"))) {
2210  ast_debug(2, "Temporary message directory does not exist, using default (%s)\n", tempfile);
2211  ast_copy_string(prefile, tempfile, sizeof(prefile));
2212  }
2213  ast_debug(2, "Preparing to play message ...\n");
2214 
2215  /* Check current or macro-calling context for special extensions */
2216  if (ast_test_flag(vmu, MVM_OPERATOR)) {
2217  if (!ast_strlen_zero(vmu->exit)) {
2218  if (ast_exists_extension(chan, vmu->exit, "o", 1,
2219  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2220  strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2221  ouseexten = 1;
2222  }
2223  } else if (ast_exists_extension(chan, ast_channel_context(chan), "o", 1,
2224  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2225  strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2226  ouseexten = 1;
2227  }
2228  else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2229  && ast_exists_extension(chan, ast_channel_macrocontext(chan), "o", 1,
2230  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2231  strncat(ecodes, "0", sizeof(ecodes) - strlen(ecodes) - 1);
2232  ousemacro = 1;
2233  }
2234  }
2235 
2236  if (!ast_strlen_zero(vmu->exit)) {
2237  if (ast_exists_extension(chan, vmu->exit, "a", 1,
2238  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2239  strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2240  }
2241  } else if (ast_exists_extension(chan, ast_channel_context(chan), "a", 1,
2242  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2243  strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2244  } else if (!ast_strlen_zero(ast_channel_macrocontext(chan))
2245  && ast_exists_extension(chan, ast_channel_macrocontext(chan), "a", 1,
2246  S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL))) {
2247  strncat(ecodes, "*", sizeof(ecodes) - strlen(ecodes) - 1);
2248  ausemacro = 1;
2249  }
2250 
2251  res = 0; /* Reset */
2252  /* Play the beginning intro if desired */
2253  if (!ast_strlen_zero(prefile)) {
2254  if (ast_streamfile(chan, prefile, ast_channel_language(chan)) > -1)
2255  res = ast_waitstream(chan, ecodes);
2256  } else {
2257  ast_debug(2, "%s doesn't exist, doing what we can\n", prefile);
2258  res = invent_message(chan, vmu->domain, username, ast_test_flag(&leave_options, OPT_BUSY_GREETING), ecodes);
2259  }
2260  if (res < 0) {
2261  ast_debug(2, "Hang up during prefile playback\n");
2262  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2263  if(ast_test_flag(vmu, MVM_ALLOCED))
2264  free_user(vmu);
2265  return -1;
2266  }
2267  if (res == '#') {
2268  /* On a '#' we skip the instructions */
2269  ast_set_flag(&leave_options, OPT_SILENT);
2270  res = 0;
2271  }
2272  if (!res && !ast_test_flag(&leave_options, OPT_SILENT)) {
2273  res = ast_streamfile(chan, SOUND_INTRO, ast_channel_language(chan));
2274  if (!res)
2275  res = ast_waitstream(chan, ecodes);
2276  if (res == '#') {
2277  ast_set_flag(&leave_options, OPT_SILENT);
2278  res = 0;
2279  }
2280  }
2281  if (res > 0)
2282  ast_stopstream(chan);
2283  /* Check for a '*' here in case the caller wants to escape from voicemail to something
2284  other than the operator -- an automated attendant or mailbox login for example */
2285  if (res == '*') {
2286  ast_channel_exten_set(chan, "a");
2287  if (!ast_strlen_zero(vmu->exit)) {
2288  ast_channel_context_set(chan, vmu->exit);
2289  } else if (ausemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2291  }
2292  ast_channel_priority_set(chan, 0);
2293  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2294  res = 0;
2295  } else if (res == '0') { /* Check for a '0' here */
2296  if(ouseexten || ousemacro) {
2297  ast_channel_exten_set(chan, "o");
2298  if (!ast_strlen_zero(vmu->exit)) {
2299  ast_channel_context_set(chan, vmu->exit);
2300  } else if (ousemacro && !ast_strlen_zero(ast_channel_macrocontext(chan))) {
2302  }
2303  ast_play_and_wait(chan, "transfer");
2304  ast_channel_priority_set(chan, 0);
2305  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "USEREXIT");
2306  }
2307  res = 0;
2308  } else if (res < 0) {
2309  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "FAILED");
2310  res = -1;
2311  } else
2312  pbx_builtin_setvar_helper(chan, "MVM_GREET_STATUS", "SUCCESS");
2313 
2314  if(ast_test_flag(vmu, MVM_ALLOCED))
2315  free_user(vmu);
2316 
2317 
2318  /* Ok, we're ready to rock and roll. Return to dialplan */
2319  return res;
2320 
2321 }
2322 
2323 /*!\internal
2324  * \brief Dialplan application to delete voicemail */
2325 static int minivm_delete_exec(struct ast_channel *chan, const char *data)
2326 {
2327  int res = 0;
2328  char filename[BUFSIZ];
2329 
2330  if (!ast_strlen_zero(data)) {
2331  ast_copy_string(filename, (char *) data, sizeof(filename));
2332  } else {
2333  ast_channel_lock(chan);
2334  ast_copy_string(filename, pbx_builtin_getvar_helper(chan, "MVM_FILENAME"), sizeof(filename));
2335  ast_channel_unlock(chan);
2336  }
2337 
2338  if (ast_strlen_zero(filename)) {
2339  ast_log(LOG_ERROR, "No filename given in application arguments or channel variable MVM_FILENAME\n");
2340  return res;
2341  }
2342 
2343  /* Go ahead and delete audio files from system, they're not needed any more */
2344  /* We should look for both audio and text files here */
2345  if (ast_fileexists(filename, NULL, NULL) > 0) {
2346  res = vm_delete(filename);
2347  if (res) {
2348  ast_debug(2, "Can't delete file: %s\n", filename);
2349  pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2350  } else {
2351  ast_debug(2, "Deleted voicemail file :: %s \n", filename);
2352  pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "SUCCESS");
2353  }
2354  } else {
2355  ast_debug(2, "Filename does not exist: %s\n", filename);
2356  pbx_builtin_setvar_helper(chan, "MVM_DELETE_STATUS", "FAILED");
2357  }
2358 
2359  return res;
2360 }
2361 
2362 /*! \brief Record specific messages for voicemail account */
2363 static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
2364 {
2365  int argc = 0;
2366  char *argv[2];
2367  char filename[PATH_MAX];
2368  char tmp[PATH_MAX];
2369  char *domain;
2370  char *tmpptr = NULL;
2371  struct minivm_account *vmu;
2372  char *username;
2373  struct ast_flags flags = { 0 };
2374  char *opts[OPT_ARG_ARRAY_SIZE];
2375  int error = FALSE;
2376  char *message = NULL;
2377  char *prompt = NULL;
2378  int duration;
2379 
2380  if (ast_strlen_zero(data)) {
2381  ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2382  error = TRUE;
2383  } else {
2384  tmpptr = ast_strdupa((char *)data);
2385  argc = ast_app_separate_args(tmpptr, ',', argv, ARRAY_LEN(argv));
2386  }
2387 
2388  if (argc <=1) {
2389  ast_log(LOG_ERROR, "MinivmAccmess needs at least two arguments: account and option\n");
2390  error = TRUE;
2391  }
2392  if (!error && strlen(argv[1]) > 1) {
2393  ast_log(LOG_ERROR, "MinivmAccmess can only handle one option at a time. Bad option string: %s\n", argv[1]);
2394  error = TRUE;
2395  }
2396 
2397  if (!error && ast_app_parse_options(minivm_accmess_options, &flags, opts, argv[1])) {
2398  ast_log(LOG_ERROR, "Can't parse option %s\n", argv[1]);
2399  error = TRUE;
2400  }
2401 
2402  if (error) {
2403  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2404  return -1;
2405  }
2406 
2407  ast_copy_string(tmp, argv[0], sizeof(tmp));
2408  username = tmp;
2409  domain = strchr(tmp, '@');
2410  if (domain) {
2411  *domain = '\0';
2412  domain++;
2413  }
2414  if (ast_strlen_zero(domain) || ast_strlen_zero(username)) {
2415  ast_log(LOG_ERROR, "Need username@domain as argument. Sorry. Argument 0 %s\n", argv[0]);
2416  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2417  return -1;
2418  }
2419 
2420  if(!(vmu = find_account(domain, username, TRUE))) {
2421  /* We could not find user, let's exit */
2422  ast_log(LOG_WARNING, "Could not allocate temporary memory for '%s@%s'\n", username, domain);
2423  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "FAILED");
2424  return -1;
2425  }
2426 
2427  /* Answer channel if it's not already answered */
2428  if (ast_channel_state(chan) != AST_STATE_UP)
2429  ast_answer(chan);
2430 
2431  /* Here's where the action is */
2432  if (ast_test_flag(&flags, OPT_BUSY_GREETING)) {
2433  message = "busy";
2434  prompt = "vm-rec-busy";
2435  } else if (ast_test_flag(&flags, OPT_UNAVAIL_GREETING)) {
2436  message = "unavailable";
2437  prompt = "vm-rec-unv";
2438  } else if (ast_test_flag(&flags, OPT_TEMP_GREETING)) {
2439  message = "temp";
2440  prompt = "vm-rec-temp";
2441  } else if (ast_test_flag(&flags, OPT_NAME_GREETING)) {
2442  message = "greet";
2443  prompt = "vm-rec-name";
2444  }
2445  snprintf(filename,sizeof(filename), "%s%s/%s/%s", MVM_SPOOL_DIR, vmu->domain, vmu->username, message);
2446  /* Maybe we should check the result of play_record_review ? */
2447  play_record_review(chan, prompt, filename, global_maxgreet, default_vmformat, 0, vmu, &duration, NULL, NULL, FALSE);
2448 
2449  ast_debug(1, "Recorded new %s message in %s (duration %d)\n", message, filename, duration);
2450 
2451  if(ast_test_flag(vmu, MVM_ALLOCED))
2452  free_user(vmu);
2453 
2454  pbx_builtin_setvar_helper(chan, "MVM_ACCMESS_STATUS", "SUCCESS");
2455 
2456  /* Ok, we're ready to rock and roll. Return to dialplan */
2457  return 0;
2458 }
2459 
2460 /*! \brief Append new mailbox to mailbox list from configuration file */
2461 static int create_vmaccount(char *name, struct ast_variable *var, int realtime)
2462 {
2463  struct minivm_account *vmu;
2464  char *domain;
2465  char *username;
2466  char accbuf[BUFSIZ];
2467 
2468  ast_debug(3, "Creating %s account for [%s]\n", realtime ? "realtime" : "static", name);
2469 
2470  ast_copy_string(accbuf, name, sizeof(accbuf));
2471  username = accbuf;
2472  domain = strchr(accbuf, '@');
2473  if (domain) {
2474  *domain = '\0';
2475  domain++;
2476  }
2477  if (ast_strlen_zero(domain)) {
2478  ast_log(LOG_ERROR, "No domain given for mini-voicemail account %s. Not configured.\n", name);
2479  return 0;
2480  }
2481 
2482  ast_debug(3, "Creating static account for user %s domain %s\n", username, domain);
2483 
2484  /* Allocate user account */
2485  vmu = ast_calloc(1, sizeof(*vmu));
2486  if (!vmu)
2487  return 0;
2488 
2489  ast_copy_string(vmu->domain, domain, sizeof(vmu->domain));
2490  ast_copy_string(vmu->username, username, sizeof(vmu->username));
2491 
2492  populate_defaults(vmu);
2493 
2494  ast_debug(3, "...Configuring account %s\n", name);
2495 
2496  while (var) {
2497  ast_debug(3, "Configuring %s = \"%s\" for account %s\n", var->name, var->value, name);
2498  if (!strcasecmp(var->name, "serveremail")) {
2499  ast_copy_string(vmu->serveremail, var->value, sizeof(vmu->serveremail));
2500  } else if (!strcasecmp(var->name, "email")) {
2501  ast_copy_string(vmu->email, var->value, sizeof(vmu->email));
2502  } else if (!strcasecmp(var->name, "accountcode")) {
2503  ast_copy_string(vmu->accountcode, var->value, sizeof(vmu->accountcode));
2504  } else if (!strcasecmp(var->name, "pincode")) {
2505  ast_copy_string(vmu->pincode, var->value, sizeof(vmu->pincode));
2506  } else if (!strcasecmp(var->name, "domain")) {
2507  ast_copy_string(vmu->domain, var->value, sizeof(vmu->domain));
2508  } else if (!strcasecmp(var->name, "language")) {
2509  ast_copy_string(vmu->language, var->value, sizeof(vmu->language));
2510  } else if (!strcasecmp(var->name, "timezone")) {
2511  ast_copy_string(vmu->zonetag, var->value, sizeof(vmu->zonetag));
2512  } else if (!strcasecmp(var->name, "externnotify")) {
2513  ast_copy_string(vmu->externnotify, var->value, sizeof(vmu->externnotify));
2514  } else if (!strcasecmp(var->name, "etemplate")) {
2515  ast_copy_string(vmu->etemplate, var->value, sizeof(vmu->etemplate));
2516  } else if (!strcasecmp(var->name, "ptemplate")) {
2517  ast_copy_string(vmu->ptemplate, var->value, sizeof(vmu->ptemplate));
2518  } else if (!strcasecmp(var->name, "fullname")) {
2519  ast_copy_string(vmu->fullname, var->value, sizeof(vmu->fullname));
2520  } else if (!strcasecmp(var->name, "setvar")) {
2521  char *varval;
2522  char varname[strlen(var->value) + 1];
2523  struct ast_variable *tmpvar;
2524 
2525  strcpy(varname, var->value); /* safe */
2526  if ((varval = strchr(varname, '='))) {
2527  *varval = '\0';
2528  varval++;
2529  if ((tmpvar = ast_variable_new(varname, varval, ""))) {
2530  tmpvar->next = vmu->chanvars;
2531  vmu->chanvars = tmpvar;
2532  }
2533  }
2534  } else if (!strcasecmp(var->name, "pager")) {
2535  ast_copy_string(vmu->pager, var->value, sizeof(vmu->pager));
2536  } else if (!strcasecmp(var->name, "volgain")) {
2537  sscanf(var->value, "%30lf", &vmu->volgain);
2538  } else {
2539  ast_log(LOG_ERROR, "Unknown configuration option for minivm account %s : %s\n", name, var->name);
2540  }
2541  var = var->next;
2542  }
2543  ast_debug(3, "...Linking account %s\n", name);
2544 
2548 
2550 
2551  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)" : "");
2552  return 0;
2553 }
2554 
2555 /*! \brief Free Mini Voicemail timezone */
2556 static void free_zone(struct minivm_zone *z)
2557 {
2558  ast_free(z);
2559 }
2560 
2561 /*! \brief Clear list of timezones */
2562 static void timezone_destroy_list(void)
2563 {
2564  struct minivm_zone *this;
2565 
2567  while ((this = AST_LIST_REMOVE_HEAD(&minivm_zones, list)))
2568  free_zone(this);
2569 
2571 }
2572 
2573 /*! \brief Add time zone to memory list */
2574 static int timezone_add(const char *zonename, const char *config)
2575 {
2576  struct minivm_zone *newzone;
2577  char *msg_format, *timezone_str;
2578 
2579  newzone = ast_calloc(1, sizeof(*newzone));
2580  if (newzone == NULL)
2581  return 0;
2582 
2583  msg_format = ast_strdupa(config);
2584 
2585  timezone_str = strsep(&msg_format, "|");
2586  if (!msg_format) {
2587  ast_log(LOG_WARNING, "Invalid timezone definition : %s\n", zonename);
2588  ast_free(newzone);
2589  return 0;
2590  }
2591 
2592  ast_copy_string(newzone->name, zonename, sizeof(newzone->name));
2593  ast_copy_string(newzone->timezone, timezone_str, sizeof(newzone->timezone));
2594  ast_copy_string(newzone->msg_format, msg_format, sizeof(newzone->msg_format));
2595 
2599 
2601 
2602  return 0;
2603 }
2604 
2605 /*! \brief Read message template from file */
2606 static char *message_template_parse_filebody(const char *filename) {
2607  char buf[BUFSIZ * 6];
2608  char readbuf[BUFSIZ];
2609  char filenamebuf[BUFSIZ];
2610  char *writepos;
2611  char *messagebody;
2612  FILE *fi;
2613  int lines = 0;
2614 
2615  if (ast_strlen_zero(filename))
2616  return NULL;
2617  if (*filename == '/')
2618  ast_copy_string(filenamebuf, filename, sizeof(filenamebuf));
2619  else
2620  snprintf(filenamebuf, sizeof(filenamebuf), "%s/%s", ast_config_AST_CONFIG_DIR, filename);
2621 
2622  if (!(fi = fopen(filenamebuf, "r"))) {
2623  ast_log(LOG_ERROR, "Can't read message template from file: %s\n", filenamebuf);
2624  return NULL;
2625  }
2626  writepos = buf;
2627  while (fgets(readbuf, sizeof(readbuf), fi)) {
2628  lines ++;
2629  if (writepos != buf) {
2630  *writepos = '\n'; /* Replace EOL with new line */
2631  writepos++;
2632  }
2633  ast_copy_string(writepos, readbuf, sizeof(buf) - (writepos - buf));
2634  writepos += strlen(readbuf) - 1;
2635  }
2636  fclose(fi);
2637  messagebody = ast_calloc(1, strlen(buf + 1));
2638  ast_copy_string(messagebody, buf, strlen(buf) + 1);
2639  ast_debug(4, "---> Size of allocation %d\n", (int) strlen(buf + 1) );
2640  ast_debug(4, "---> Done reading message template : \n%s\n---- END message template--- \n", messagebody);
2641 
2642  return messagebody;
2643 }
2644 
2645 /*! \brief Parse emailbody template from configuration file */
2646 static char *message_template_parse_emailbody(const char *configuration)
2647 {
2648  char *tmpread, *tmpwrite;
2649  char *emailbody = ast_strdup(configuration);
2650 
2651  /* substitute strings \t and \n into the apropriate characters */
2652  tmpread = tmpwrite = emailbody;
2653  while ((tmpwrite = strchr(tmpread,'\\'))) {
2654  int len = strlen("\n");
2655  switch (tmpwrite[1]) {
2656  case 'n':
2657  memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2658  tmpwrite[0] = '\n';
2659  break;
2660  case 't':
2661  memmove(tmpwrite + len, tmpwrite + 2, strlen(tmpwrite + 2) + 1);
2662  tmpwrite[0] = '\t';
2663  break;
2664  default:
2665  ast_log(LOG_NOTICE, "Substitution routine does not support this character: %c\n", tmpwrite[1]);
2666  }
2667  tmpread = tmpwrite + len;
2668  }
2669  return emailbody;
2670 }
2671 
2672 /*! \brief Apply general configuration options */
2674 {
2675  int error = 0;
2676 
2677  while (var) {
2678  /* Mail command */
2679  if (!strcmp(var->name, "mailcmd")) {
2680  ast_copy_string(global_mailcmd, var->value, sizeof(global_mailcmd)); /* User setting */
2681  } else if (!strcmp(var->name, "maxgreet")) {
2682  global_maxgreet = atoi(var->value);
2683  } else if (!strcmp(var->name, "maxsilence")) {
2684  global_maxsilence = atoi(var->value);
2685  if (global_maxsilence > 0)
2686  global_maxsilence *= 1000;
2687  } else if (!strcmp(var->name, "logfile")) {
2688  if (!ast_strlen_zero(var->value) ) {
2689  if(*(var->value) == '/')
2691  else
2692  snprintf(global_logfile, sizeof(global_logfile), "%s/%s", ast_config_AST_LOG_DIR, var->value);
2693  }
2694  } else if (!strcmp(var->name, "externnotify")) {
2695  /* External voicemail notify application */
2697  } else if (!strcmp(var->name, "silencethreshold") || !strcmp(var->name, "silencetreshold")) {
2698  /* Silence treshold */
2699  global_silencethreshold = atoi(var->value);
2700  } else if (!strcmp(var->name, "maxmessage")) {
2701  int x;
2702  if (sscanf(var->value, "%30d", &x) == 1) {
2703  global_vmmaxmessage = x;
2704  } else {
2705  error ++;
2706  ast_log(LOG_WARNING, "Invalid max message time length\n");
2707  }
2708  } else if (!strcmp(var->name, "minmessage")) {
2709  int x;
2710  if (sscanf(var->value, "%30d", &x) == 1) {
2711  global_vmminmessage = x;
2713  ast_log(LOG_WARNING, "maxsilence should be less than minmessage or you may get empty messages\n");
2714  } else {
2715  error ++;
2716  ast_log(LOG_WARNING, "Invalid min message time length\n");
2717  }
2718  } else if (!strcmp(var->name, "format")) {
2720  } else if (!strcmp(var->name, "review")) {
2722  } else if (!strcmp(var->name, "operator")) {
2724  }
2725  var = var->next;
2726  }
2727  return error;
2728 }
2729 
2730 /*! \brief Load minivoicemail configuration */
2731 static int load_config(int reload)
2732 {
2733  struct ast_config *cfg;
2734  struct ast_variable *var;
2735  char *cat;
2736  const char *chanvar;
2737  int error = 0;
2738  struct minivm_template *template;
2739  struct ast_flags config_flags = { reload ? CONFIG_FLAG_FILEUNCHANGED : 0 };
2740 
2741  cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags);
2742  if (cfg == CONFIG_STATUS_FILEUNCHANGED) {
2743  return 0;
2744  } else if (cfg == CONFIG_STATUS_FILEINVALID) {
2745  ast_log(LOG_ERROR, "Config file " VOICEMAIL_CONFIG " is in an invalid format. Aborting.\n");
2746  return 0;
2747  }
2748 
2750 
2751  /* Destroy lists to reconfigure */
2752  message_destroy_list(); /* Destroy list of voicemail message templates */
2753  timezone_destroy_list(); /* Destroy list of timezones */
2754  vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
2755  ast_debug(2, "Destroyed memory objects...\n");
2756 
2757  /* First, set some default settings */
2758  global_externnotify[0] = '\0';
2759  global_logfile[0] = '\0';
2760  global_vmmaxmessage = 2000;
2761  global_maxgreet = 2000;
2762  global_vmminmessage = 0;
2763  strcpy(global_mailcmd, SENDMAIL);
2764  global_maxsilence = 0;
2769  /* Reset statistics */
2770  memset(&global_stats, 0, sizeof(global_stats));
2772 
2774 
2775  /* Make sure we could load configuration file */
2776  if (!cfg) {
2777  ast_log(LOG_WARNING, "Failed to load configuration file. Module activated with default settings.\n");
2779  return 0;
2780  }
2781 
2782  ast_debug(2, "Loaded configuration file, now parsing\n");
2783 
2784  /* General settings */
2785 
2786  cat = ast_category_browse(cfg, NULL);
2787  while (cat) {
2788  ast_debug(3, "Found configuration section [%s]\n", cat);
2789  if (!strcasecmp(cat, "general")) {
2790  /* Nothing right now */
2792  } else if (!strncasecmp(cat, "template-", 9)) {
2793  /* Template */
2794  char *name = cat + 9;
2795 
2796  /* Now build and link template to list */
2798  } else {
2799  var = ast_variable_browse(cfg, cat);
2800  if (!strcasecmp(cat, "zonemessages")) {
2801  /* Timezones in this context */
2802  while (var) {
2803  timezone_add(var->name, var->value);
2804  var = var->next;
2805  }
2806  } else {
2807  /* Create mailbox from this */
2808  error += create_vmaccount(cat, var, FALSE);
2809  }
2810  }
2811  /* Find next section in configuration file */
2812  cat = ast_category_browse(cfg, cat);
2813  }
2814 
2815  /* Configure the default email template */
2816  message_template_build("email-default", NULL);
2817  template = message_template_find("email-default");
2818 
2819  /* Load date format config for voicemail mail */
2820  if ((chanvar = ast_variable_retrieve(cfg, "general", "emaildateformat")))
2821  ast_copy_string(template->dateformat, chanvar, sizeof(template->dateformat));
2822  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailfromstring")))
2823  ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2824  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaaddress")))
2825  ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2826  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailaddress")))
2827  ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2828  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailcharset")))
2829  ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2830  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailsubject")))
2831  ast_copy_string(template->subject, chanvar, sizeof(template->subject));
2832  if ((chanvar = ast_variable_retrieve(cfg, "general", "emailbody")))
2833  template->body = message_template_parse_emailbody(chanvar);
2834  template->attachment = TRUE;
2835 
2836  message_template_build("pager-default", NULL);
2837  template = message_template_find("pager-default");
2838  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerfromstring")))
2839  ast_copy_string(template->fromaddress, chanvar, sizeof(template->fromaddress));
2840  if ((chanvar = ast_variable_retrieve(cfg, "general", "pageraddress")))
2841  ast_copy_string(template->serveremail, chanvar, sizeof(template->serveremail));
2842  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagercharset")))
2843  ast_copy_string(template->charset, chanvar, sizeof(template->charset));
2844  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagersubject")))
2845  ast_copy_string(template->subject, chanvar,sizeof(template->subject));
2846  if ((chanvar = ast_variable_retrieve(cfg, "general", "pagerbody")))
2847  template->body = message_template_parse_emailbody(chanvar);
2848  template->attachment = FALSE;
2849 
2850  if (error)
2851  ast_log(LOG_ERROR, "--- A total of %d errors found in mini-voicemail configuration\n", error);
2852 
2854  ast_config_destroy(cfg);
2855 
2856  /* Close log file if it's open and disabled */
2857  if(minivmlogfile)
2858  fclose(minivmlogfile);
2859 
2860  /* Open log file if it's enabled */
2862  minivmlogfile = fopen(global_logfile, "a");
2863  if(!minivmlogfile)
2864  ast_log(LOG_ERROR, "Failed to open minivm log file %s : %s\n", global_logfile, strerror(errno));
2865  if (minivmlogfile)
2866  ast_debug(3, "Opened log file %s \n", global_logfile);
2867  }
2868 
2869  return 0;
2870 }
2871 
2872 /*! \brief CLI routine for listing templates */
2873 static char *handle_minivm_list_templates(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2874 {
2875  struct minivm_template *this;
2876 #define HVLT_OUTPUT_FORMAT "%-15s %-10s %-10s %-15.15s %-50s\n"
2877  int count = 0;
2878 
2879  switch (cmd) {
2880  case CLI_INIT:
2881  e->command = "minivm list templates";
2882  e->usage =
2883  "Usage: minivm list templates\n"
2884  " Lists message templates for e-mail, paging and IM\n";
2885  return NULL;
2886  case CLI_GENERATE:
2887  return NULL;
2888  }
2889 
2890  if (a->argc > 3)
2891  return CLI_SHOWUSAGE;
2892 
2895  ast_cli(a->fd, "There are no message templates defined\n");
2897  return CLI_FAILURE;
2898  }
2899  ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "Template name", "Charset", "Locale", "Attach media", "Subject");
2900  ast_cli(a->fd, HVLT_OUTPUT_FORMAT, "-------------", "-------", "------", "------------", "-------");
2902  ast_cli(a->fd, HVLT_OUTPUT_FORMAT, this->name,
2903  S_OR(this->charset, "-"),
2904  S_OR(this->locale, "-"),
2905  this->attachment ? "Yes" : "No",
2906  S_OR(this->subject, "-"));
2907  count++;
2908  }
2910  ast_cli(a->fd, "\n * Total: %d minivoicemail message templates\n", count);
2911  return CLI_SUCCESS;
2912 }
2913 
2914 static char *complete_minivm_show_users(const char *line, const char *word, int pos, int state)
2915 {
2916  int which = 0;
2917  int wordlen;
2918  struct minivm_account *vmu;
2919  const char *domain = "";
2920 
2921  /* 0 - minivm; 1 - list; 2 - accounts; 3 - for; 4 - <domain> */
2922  if (pos > 4)
2923  return NULL;
2924  wordlen = strlen(word);
2926  if (!strncasecmp(word, vmu->domain, wordlen)) {
2927  if (domain && strcmp(domain, vmu->domain) && ++which > state)
2928  return ast_strdup(vmu->domain);
2929  /* ignore repeated domains ? */
2930  domain = vmu->domain;
2931  }
2932  }
2933  return NULL;
2934 }
2935 
2936 /*! \brief CLI command to list voicemail accounts */
2937 static char *handle_minivm_show_users(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2938 {
2939  struct minivm_account *vmu;
2940 #define HMSU_OUTPUT_FORMAT "%-23s %-15s %-15s %-10s %-10s %-50s\n"
2941  int count = 0;
2942 
2943  switch (cmd) {
2944  case CLI_INIT:
2945  e->command = "minivm list accounts [for]";
2946  e->usage =
2947  "Usage: minivm list accounts [for <domain>]\n"
2948  " Lists all mailboxes currently set up\n";
2949  return NULL;
2950  case CLI_GENERATE:
2951  return complete_minivm_show_users(a->line, a->word, a->pos, a->n);
2952  }
2953 
2954  if ((a->argc < 3) || (a->argc > 5) || (a->argc == 4))
2955  return CLI_SHOWUSAGE;
2956  if ((a->argc == 5) && strcmp(a->argv[3],"for"))
2957  return CLI_SHOWUSAGE;
2958 
2961  ast_cli(a->fd, "There are no voicemail users currently defined\n");
2963  return CLI_FAILURE;
2964  }
2965  ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "User", "E-Template", "P-template", "Zone", "Format", "Full name");
2966  ast_cli(a->fd, HMSU_OUTPUT_FORMAT, "----", "----------", "----------", "----", "------", "---------");
2968  char tmp[256] = "";
2969  if ((a->argc == 3) || ((a->argc == 5) && !strcmp(a->argv[4], vmu->domain))) {
2970  count++;
2971  snprintf(tmp, sizeof(tmp), "%s@%s", vmu->username, vmu->domain);
2972  ast_cli(a->fd, HMSU_OUTPUT_FORMAT, tmp, S_OR(vmu->etemplate, "-"),
2973  S_OR(vmu->ptemplate, "-"),
2974  S_OR(vmu->zonetag, "-"),
2975  S_OR(vmu->attachfmt, "-"),
2976  vmu->fullname);
2977  }
2978  }
2980  ast_cli(a->fd, "\n * Total: %d minivoicemail accounts\n", count);
2981  return CLI_SUCCESS;
2982 }
2983 
2984 /*! \brief Show a list of voicemail zones in the CLI */
2985 static char *handle_minivm_show_zones(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2986 {
2987  struct minivm_zone *zone;
2988 #define HMSZ_OUTPUT_FORMAT "%-15s %-20s %-45s\n"
2989  char *res = CLI_SUCCESS;
2990 
2991  switch (cmd) {
2992  case CLI_INIT:
2993  e->command = "minivm list zones";
2994  e->usage =
2995  "Usage: minivm list zones\n"
2996  " Lists zone message formats\n";
2997  return NULL;
2998  case CLI_GENERATE:
2999  return NULL;
3000  }
3001 
3002  if (a->argc != e->args)
3003  return CLI_SHOWUSAGE;
3004 
3006  if (!AST_LIST_EMPTY(&minivm_zones)) {
3007  ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "Zone", "Timezone", "Message Format");
3008  ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, "----", "--------", "--------------");
3010  ast_cli(a->fd, HMSZ_OUTPUT_FORMAT, zone->name, zone->timezone, zone->msg_format);
3011  }
3012  } else {
3013  ast_cli(a->fd, "There are no voicemail zones currently defined\n");
3014  res = CLI_FAILURE;
3015  }
3017 
3018  return res;
3019 }
3020 
3021 /*! \brief CLI Show settings */
3022 static char *handle_minivm_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3023 {
3024  switch (cmd) {
3025  case CLI_INIT:
3026  e->command = "minivm show settings";
3027  e->usage =
3028  "Usage: minivm show settings\n"
3029  " Display Mini-Voicemail general settings\n";
3030  return NULL;
3031  case CLI_GENERATE:
3032  return NULL;
3033  }
3034 
3035  ast_cli(a->fd, "* Mini-Voicemail general settings\n");
3036  ast_cli(a->fd, " -------------------------------\n");
3037  ast_cli(a->fd, "\n");
3038  ast_cli(a->fd, " Mail command (shell): %s\n", global_mailcmd);
3039  ast_cli(a->fd, " Max silence: %d\n", global_maxsilence);
3040  ast_cli(a->fd, " Silence threshold: %d\n", global_silencethreshold);
3041  ast_cli(a->fd, " Max message length (secs): %d\n", global_vmmaxmessage);
3042  ast_cli(a->fd, " Min message length (secs): %d\n", global_vmminmessage);
3043  ast_cli(a->fd, " Default format: %s\n", default_vmformat);
3044  ast_cli(a->fd, " Extern notify (shell): %s\n", global_externnotify);
3045  ast_cli(a->fd, " Logfile: %s\n", global_logfile[0] ? global_logfile : "<disabled>");
3046  ast_cli(a->fd, " Operator exit: %s\n", ast_test_flag(&globalflags, MVM_OPERATOR) ? "Yes" : "No");
3047  ast_cli(a->fd, " Message review: %s\n", ast_test_flag(&globalflags, MVM_REVIEW) ? "Yes" : "No");
3048 
3049  ast_cli(a->fd, "\n");
3050  return CLI_SUCCESS;
3051 }
3052 
3053 /*! \brief Show stats */
3054 static char *handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3055 {
3056  struct ast_tm timebuf;
3057  char buf[BUFSIZ];
3058 
3059  switch (cmd) {
3060 
3061  case CLI_INIT:
3062  e->command = "minivm show stats";
3063  e->usage =
3064  "Usage: minivm show stats\n"
3065  " Display Mini-Voicemail counters\n";
3066  return NULL;
3067  case CLI_GENERATE:
3068  return NULL;
3069  }
3070 
3071  ast_cli(a->fd, "* Mini-Voicemail statistics\n");
3072  ast_cli(a->fd, " -------------------------\n");
3073  ast_cli(a->fd, "\n");
3074  ast_cli(a->fd, " Voicemail accounts: %5d\n", global_stats.voicemailaccounts);
3075  ast_cli(a->fd, " Templates: %5d\n", global_stats.templates);
3076  ast_cli(a->fd, " Timezones: %5d\n", global_stats.timezones);
3077  if (global_stats.receivedmessages == 0) {
3078  ast_cli(a->fd, " Received messages since last reset: <none>\n");
3079  } else {
3080  ast_cli(a->fd, " Received messages since last reset: %d\n", global_stats.receivedmessages);
3082  ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
3083  ast_cli(a->fd, " Last received voicemail: %s\n", buf);
3084  }
3085  ast_localtime(&global_stats.reset, &timebuf, NULL);
3086  ast_strftime(buf, sizeof(buf), "%a %b %e %r %Z %Y", &timebuf);
3087  ast_cli(a->fd, " Last reset: %s\n", buf);
3088 
3089  ast_cli(a->fd, "\n");
3090  return CLI_SUCCESS;
3091 }
3092 
3093 /*! \brief ${MINIVMACCOUNT()} Dialplan function - reads account data */
3094 static int minivm_account_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3095 {
3096  struct minivm_account *vmu;
3097  char *username, *domain, *colname;
3098 
3099  username = ast_strdupa(data);
3100 
3101  if ((colname = strchr(username, ':'))) {
3102  *colname = '\0';
3103  colname++;
3104  } else {
3105  colname = "path";
3106  }
3107  if ((domain = strchr(username, '@'))) {
3108  *domain = '\0';
3109  domain++;
3110  }
3111  if (ast_strlen_zero(username) || ast_strlen_zero(domain)) {
3112  ast_log(LOG_ERROR, "This function needs a username and a domain: username@domain\n");
3113  return 0;
3114  }
3115 
3116  if (!(vmu = find_account(domain, username, TRUE)))
3117  return 0;
3118 
3119  if (!strcasecmp(colname, "hasaccount")) {
3120  ast_copy_string(buf, (ast_test_flag(vmu, MVM_ALLOCED) ? "0" : "1"), len);
3121  } else if (!strcasecmp(colname, "fullname")) {
3122  ast_copy_string(buf, vmu->fullname, len);
3123  } else if (!strcasecmp(colname, "email")) {
3124  if (!ast_strlen_zero(vmu->email))
3125  ast_copy_string(buf, vmu->email, len);
3126  else
3127  snprintf(buf, len, "%s@%s", vmu->username, vmu->domain);
3128  } else if (!strcasecmp(colname, "pager")) {
3129  ast_copy_string(buf, vmu->pager, len);
3130  } else if (!strcasecmp(colname, "etemplate")) {
3131  if (!ast_strlen_zero(vmu->etemplate))
3132  ast_copy_string(buf, vmu->etemplate, len);
3133  else
3134  ast_copy_string(buf, "email-default", len);
3135  } else if (!strcasecmp(colname, "language")) {
3136  ast_copy_string(buf, vmu->language, len);
3137  } else if (!strcasecmp(colname, "timezone")) {
3138  ast_copy_string(buf, vmu->zonetag, len);
3139  } else if (!strcasecmp(colname, "ptemplate")) {
3140  if (!ast_strlen_zero(vmu->ptemplate))
3141  ast_copy_string(buf, vmu->ptemplate, len);
3142  else
3143  ast_copy_string(buf, "email-default", len);
3144  } else if (!strcasecmp(colname, "accountcode")) {
3146  } else if (!strcasecmp(colname, "pincode")) {
3147  ast_copy_string(buf, vmu->pincode, len);
3148  } else if (!strcasecmp(colname, "path")) {
3149  check_dirpath(buf, len, vmu->domain, vmu->username, NULL);
3150  } else { /* Look in channel variables */
3151  struct ast_variable *var;
3152 
3153  for (var = vmu->chanvars ; var ; var = var->next)
3154  if (!strcmp(var->name, colname)) {
3155  ast_copy_string(buf, var->value, len);
3156  break;
3157  }
3158  }
3159 
3160  if(ast_test_flag(vmu, MVM_ALLOCED))
3161  free_user(vmu);
3162 
3163  return 0;
3164 }
3165 
3166 /*! \brief lock directory
3167 
3168  only return failure if ast_lock_path returns 'timeout',
3169  not if the path does not exist or any other reason
3170 */
3171 static int vm_lock_path(const char *path)
3172 {
3173  switch (ast_lock_path(path)) {
3174  case AST_LOCK_TIMEOUT:
3175  return -1;
3176  default:
3177  return 0;
3178  }
3179 }
3180 
3181 /*! \brief Access counter file, lock directory, read and possibly write it again changed
3182  \param directory Directory to crate file in
3183  \param countername filename
3184  \param value If set to zero, we only read the variable
3185  \param operand 0 to read, 1 to set new value, 2 to change
3186  \return -1 on error, otherwise counter value
3187 */
3188 static int access_counter_file(char *directory, char *countername, int value, int operand)
3189 {
3190  char filename[BUFSIZ];
3191  char readbuf[BUFSIZ];
3192  FILE *counterfile;
3193  int old = 0, counter = 0;
3194 
3195  /* Lock directory */
3196  if (vm_lock_path(directory)) {
3197  return -1; /* Could not lock directory */
3198  }
3199  snprintf(filename, sizeof(filename), "%s/%s.counter", directory, countername);
3200  if (operand != 1) {
3201  counterfile = fopen(filename, "r");
3202  if (counterfile) {
3203  if(fgets(readbuf, sizeof(readbuf), counterfile)) {
3204  ast_debug(3, "Read this string from counter file: %s\n", readbuf);
3205  old = counter = atoi(readbuf);
3206  }
3207  fclose(counterfile);
3208  }
3209  }
3210  switch (operand) {
3211  case 0: /* Read only */
3212  ast_unlock_path(directory);
3213  ast_debug(2, "MINIVM Counter %s/%s: Value %d\n", directory, countername, counter);
3214  return counter;
3215  break;
3216  case 1: /* Set new value */
3217  counter = value;
3218  break;
3219  case 2: /* Change value */
3220  counter += value;
3221  if (counter < 0) /* Don't allow counters to fall below zero */
3222  counter = 0;
3223  break;
3224  }
3225 
3226  /* Now, write the new value to the file */
3227  counterfile = fopen(filename, "w");
3228  if (!counterfile) {
3229  ast_log(LOG_ERROR, "Could not open counter file for writing : %s - %s\n", filename, strerror(errno));
3230  ast_unlock_path(directory);
3231  return -1; /* Could not open file for writing */
3232  }
3233  fprintf(counterfile, "%d\n\n", counter);
3234  fclose(counterfile);
3235  ast_unlock_path(directory);
3236  ast_debug(2, "MINIVM Counter %s/%s: Old value %d New value %d\n", directory, countername, old, counter);
3237  return counter;
3238 }
3239 
3240 /*! \brief ${MINIVMCOUNTER()} Dialplan function - read counters */
3241 static int minivm_counter_func_read(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
3242 {
3243  char *username, *domain, *countername;
3244  char userpath[BUFSIZ];
3245  int res;
3246 
3247  *buf = '\0';
3248 
3249  username = ast_strdupa(data);
3250 
3251  if ((countername = strchr(username, ':'))) {
3252  *countername = '\0';
3253  countername++;
3254  }
3255 
3256  if ((domain = strchr(username, '@'))) {
3257  *domain = '\0';
3258  domain++;
3259  }
3260 
3261  /* If we have neither username nor domain now, let's give up */
3262  if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3263  ast_log(LOG_ERROR, "No account given\n");
3264  return -1;
3265  }
3266 
3267  if (ast_strlen_zero(countername)) {
3268  ast_log(LOG_ERROR, "This function needs two arguments: Account:countername\n");
3269  return -1;
3270  }
3271 
3272  /* We only have a domain, no username */
3273  if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3274  domain = username;
3275  username = NULL;
3276  }
3277 
3278  /* If we can't find account or if the account is temporary, return. */
3279  if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
3280  ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3281  return 0;
3282  }
3283 
3284  create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3285 
3286  /* We have the path, now read the counter file */
3287  res = access_counter_file(userpath, countername, 0, 0);
3288  if (res >= 0)
3289  snprintf(buf, len, "%d", res);
3290  return 0;
3291 }
3292 
3293 /*! \brief ${MINIVMCOUNTER()} Dialplan function - changes counter data */
3294 static int minivm_counter_func_write(struct ast_channel *chan, const char *cmd, char *data, const char *value)
3295 {
3296  char *username, *domain, *countername, *operand;
3297  char userpath[BUFSIZ];
3298  int change = 0;
3299  int operation = 0;
3300 
3301  if(!value)
3302  return -1;
3303  change = atoi(value);
3304 
3305  username = ast_strdupa(data);
3306 
3307  if ((countername = strchr(username, ':'))) {
3308  *countername = '\0';
3309  countername++;
3310  }
3311  if ((operand = strchr(countername, ':'))) {
3312  *operand = '\0';
3313  operand++;
3314  }
3315 
3316  if ((domain = strchr(username, '@'))) {
3317  *domain = '\0';
3318  domain++;
3319  }
3320 
3321  /* If we have neither username nor domain now, let's give up */
3322  if (ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3323  ast_log(LOG_ERROR, "No account given\n");
3324  return -1;
3325  }
3326 
3327  /* We only have a domain, no username */
3328  if (!ast_strlen_zero(username) && ast_strlen_zero(domain)) {
3329  domain = username;
3330  username = NULL;
3331  }
3332 
3333  if (ast_strlen_zero(operand) || ast_strlen_zero(countername)) {
3334  ast_log(LOG_ERROR, "Writing to this function requires three arguments: Account:countername:operand\n");
3335  return -1;
3336  }
3337 
3338  /* If we can't find account or if the account is temporary, return. */
3339  if (!ast_strlen_zero(username) && !find_account(domain, username, FALSE)) {
3340  ast_log(LOG_ERROR, "Minivm account does not exist: %s@%s\n", username, domain);
3341  return 0;
3342  }
3343 
3344  create_dirpath(userpath, sizeof(userpath), domain, username, NULL);
3345  /* Now, find out our operator */
3346  if (*operand == 'i') /* Increment */
3347  operation = 2;
3348  else if (*operand == 'd') {
3349  change = change * -1;
3350  operation = 2;
3351  } else if (*operand == 's')
3352  operation = 1;
3353  else {
3354  ast_log(LOG_ERROR, "Unknown operator: %s\n", operand);
3355  return -1;
3356  }
3357 
3358  /* We have the path, now read the counter file */
3359  access_counter_file(userpath, countername, change, operation);
3360  return 0;
3361 }
3362 
3363 
3364 /*! \brief CLI commands for Mini-voicemail */
3365 static struct ast_cli_entry cli_minivm[] = {
3366  AST_CLI_DEFINE(handle_minivm_show_users, "List defined mini-voicemail boxes"),
3367  AST_CLI_DEFINE(handle_minivm_show_zones, "List zone message formats"),
3368  AST_CLI_DEFINE(handle_minivm_list_templates, "List message templates"),
3369  AST_CLI_DEFINE(handle_minivm_reload, "Reload Mini-voicemail configuration"),
3370  AST_CLI_DEFINE(handle_minivm_show_stats, "Show some mini-voicemail statistics"),
3371  AST_CLI_DEFINE(handle_minivm_show_settings, "Show mini-voicemail general settings"),
3372 };
3373 
3375  .name = "MINIVMCOUNTER",
3376  .read = minivm_counter_func_read,
3377  .write = minivm_counter_func_write,
3378 };
3379 
3381  .name = "MINIVMACCOUNT",
3382  .read = minivm_account_func_read,
3383 };
3384 
3385 /*! \brief Load mini voicemail module */
3386 static int load_module(void)
3387 {
3388  int res;
3389 
3396 
3399  if (res)
3400  return(res);
3401 
3402  if ((res = load_config(0)))
3403  return(res);
3404 
3406 
3407  /* compute the location of the voicemail spool directory */
3408  snprintf(MVM_SPOOL_DIR, sizeof(MVM_SPOOL_DIR), "%s/voicemail/", ast_config_AST_SPOOL_DIR);
3409 
3410  return res;
3411 }
3412 
3413 /*! \brief Reload mini voicemail module */
3414 static int reload(void)
3415 {
3416  return(load_config(1));
3417 }
3418 
3419 /*! \brief Reload cofiguration */
3420 static char *handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
3421 {
3422 
3423  switch (cmd) {
3424  case CLI_INIT:
3425  e->command = "minivm reload";
3426  e->usage =
3427  "Usage: minivm reload\n"
3428  " Reload mini-voicemail configuration and reset statistics\n";
3429  return NULL;
3430  case CLI_GENERATE:
3431  return NULL;
3432  }
3433 
3434  reload();
3435  ast_cli(a->fd, "\n-- Mini voicemail re-configured \n");
3436  return CLI_SUCCESS;
3437 }
3438 
3439 /*! \brief Unload mini voicemail module */
3440 static int unload_module(void)
3441 {
3442  int res;
3443 
3450 
3454 
3455  message_destroy_list(); /* Destroy list of voicemail message templates */
3456  timezone_destroy_list(); /* Destroy list of timezones */
3457  vmaccounts_destroy_list(); /* Destroy list of voicemail accounts */
3458 
3459  return res;
3460 }
3461 
3462 
3463 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Mini VoiceMail (A minimal Voicemail e-mail System)",
3464  .support_level = AST_MODULE_SUPPORT_EXTENDED,
3465  .load = load_module,
3466  .unload = unload_module,
3467  .reload = reload,
3468 );
const char * str
Definition: app_jack.c:147
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:2937
static int global_maxsilence
Definition: app_minivm.c:691
#define DEFAULT_DATEFORMAT
Definition: app_minivm.c:706
static FILE * minivmlogfile
Definition: app_minivm.c:687
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:2461
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)
Definition: app_minivm.c:869
static void message_destroy_list(void)
Definition: app_minivm.c:833
static int make_dir(char *dest, int len, const char *domain, const char *username, const char *folder)
Definition: app_minivm.c:1391
static void free_user(struct minivm_account *vmu)
Definition: app_minivm.c:856
static int vm_delete(char *file)
Definition: app_minivm.c:1497
#define HMSU_OUTPUT_FORMAT
#define ERROR_LOCK_PATH
Definition: app_minivm.c:549
static char * handle_minivm_show_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Show stats.
Definition: app_minivm.c:3054
static char default_vmformat[80]
Definition: app_minivm.c:697
static void queue_mwi_event(const char *channel_id, const char *mbx, const char *ctx, int urgent, int new, int old)
Definition: app_minivm.c:1951
#define MVM_REVIEW
Definition: app_minivm.c:531
static int minivm_record_exec(struct ast_channel *chan, const char *data)
Definition: app_minivm.c:2082
static int minivm_notify_exec(struct ast_channel *chan, const char *data)
Definition: app_minivm.c:2004
static void populate_defaults(struct minivm_account *vmu)
Definition: app_minivm.c:898
static struct minivm_template * message_template_find(const char *name)
Definition: app_minivm.c:811
static char * app_minivm_delete
Definition: app_minivm.c:568
static char * app_minivm_greet
Definition: app_minivm.c:566
static char * message_template_parse_filebody(const char *filename)
Read message template from file.
Definition: app_minivm.c:2606
static int apply_general_options(struct ast_variable *var)
Apply general configuration options.
Definition: app_minivm.c:2673
static struct ast_flags globalflags
Definition: app_minivm.c:699
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:3094
static int check_mime(const char *str)
Definition: app_minivm.c:1011
static struct ast_custom_function minivm_account_function
Definition: app_minivm.c:3380
static void timezone_destroy_list(void)
Clear list of timezones.
Definition: app_minivm.c:2562
static ast_mutex_t minivmloglock
Definition: app_minivm.c:685
minivm_option_flags
Definition: app_minivm.c:574
@ OPT_NAME_GREETING
Definition: app_minivm.c:579
@ OPT_TEMP_GREETING
Definition: app_minivm.c:578
@ OPT_BUSY_GREETING
Definition: app_minivm.c:576
@ OPT_RECORDGAIN
Definition: app_minivm.c:580
@ OPT_UNAVAIL_GREETING
Definition: app_minivm.c:577
@ OPT_SILENT
Definition: app_minivm.c:575
#define EOL
Definition: app_minivm.c:544
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:3241
static int timezone_add(const char *zonename, const char *config)
Add time zone to memory list.
Definition: app_minivm.c:2574
static char * complete_minivm_show_users(const char *line, const char *word, int pos, int state)
Definition: app_minivm.c:2914
static int minivm_delete_exec(struct ast_channel *chan, const char *data)
Definition: app_minivm.c:2325
#define VOICEMAIL_CONFIG
Definition: app_minivm.c:552
#define DEFAULT_CHARSET
Definition: app_minivm.c:707
static int minivm_mwi_exec(struct ast_channel *chan, const char *data)
Definition: app_minivm.c:1966
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:2873
static char * message_template_parse_emailbody(const char *body)
Parse emailbody template from configuration file.
Definition: app_minivm.c:2646
static int global_maxgreet
Definition: app_minivm.c:692
static int global_vmmaxmessage
Definition: app_minivm.c:690
static char * handle_minivm_reload(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
Reload cofiguration.
Definition: app_minivm.c:3420
static void message_template_free(struct minivm_template *template)
Definition: app_minivm.c:738
static int vm_lock_path(const char *path)
lock directory
Definition: app_minivm.c:3171
static int minivm_accmess_exec(struct ast_channel *chan, const char *data)
Record specific messages for voicemail account.
Definition: app_minivm.c:2363
#define HVLT_OUTPUT_FORMAT
static int message_template_build(const char *name, struct ast_variable *var)
Definition: app_minivm.c:748
static char global_mailcmd[160]
Definition: app_minivm.c:694
static void free_zone(struct minivm_zone *z)
Free Mini Voicemail timezone.
Definition: app_minivm.c:2556
#define SOUND_INTRO
Definition: app_minivm.c:543
static const struct ast_app_option minivm_accmess_options[128]
Definition: app_minivm.c:600
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)
Definition: app_minivm.c:1678
#define SENDMAIL
Default mail command to mail voicemail. Change it with the mailcmd= command in voicemail....
Definition: app_minivm.c:541
minivm_option_args
Definition: app_minivm.c:583
@ OPT_ARG_RECORDGAIN
Definition: app_minivm.c:584
@ OPT_ARG_ARRAY_SIZE
Definition: app_minivm.c:585
static void vmaccounts_destroy_list(void)
Definition: app_minivm.c:922
static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime, char *fmt, int outsidecaller, struct minivm_account *vmu, int *duration, int *sound_duration, const char *unlockdir, signed char record_gain)
Definition: app_minivm.c:1511
static int check_dirpath(char *dest, int len, char *domain, char *username, char *folder)
Definition: app_minivm.c:1405
static struct minivm_template * message_template_create(const char *name)
Definition: app_minivm.c:718
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:2985
static const char * ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
Definition: app_minivm.c:1081
static int global_silencethreshold
Definition: app_minivm.c:693
static char * app_minivm_notify
Definition: app_minivm.c:567
static int global_saydurationminfo
Definition: app_minivm.c:700
static struct minivm_account * mvm_user_alloc(void)
Definition: app_minivm.c:907
#define TRUE
Definition: app_minivm.c:524
#define FALSE
Definition: app_minivm.c:527
#define HMSZ_OUTPUT_FORMAT
static int invent_message(struct ast_channel *chan, char *domain, char *username, int busy, char *ecodes)
Definition: app_minivm.c:1440
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)
Definition: app_minivm.c:1040
static double global_volgain
Definition: app_minivm.c:702
static int leave_voicemail(struct ast_channel *chan, char *username, struct leave_vm_options *options)
Definition: app_minivm.c:1779
static char MVM_SPOOL_DIR[PATH_MAX]
Definition: app_minivm.c:562
static struct ast_cli_entry cli_minivm[]
CLI commands for Mini-voicemail.
Definition: app_minivm.c:3365
static char * app_minivm_record
Definition: app_minivm.c:565
mvm_messagetype
Message types for notification.
Definition: app_minivm.c:556
@ MVM_MESSAGE_EMAIL
Definition: app_minivm.c:557
@ MVM_MESSAGE_PAGE
Definition: app_minivm.c:558
static char * app_minivm_accmess
Definition: app_minivm.c:569
#define MVM_OPERATOR
Definition: app_minivm.c:532
#define MVM_ALLOCED
Definition: app_minivm.c:537
static struct minivm_account * find_account(const char *domain, const char *username, int createtemp)
Definition: app_minivm.c:934
static int load_module(void)
Load mini voicemail module.
Definition: app_minivm.c:3386
static ast_mutex_t minivmlock
Definition: app_minivm.c:684
static int create_dirpath(char *dest, int len, char *domain, char *username, char *folder)
Definition: app_minivm.c:1424
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)
Definition: app_minivm.c:1101
static char global_externnotify[160]
Definition: app_minivm.c:695
static const struct ast_app_option minivm_app_options[128]
Definition: app_minivm.c:593
static void run_externnotify(struct ast_channel *chan, struct minivm_account *vmu)
Run external notification for voicemail message.
Definition: app_minivm.c:1643
static int minivm_greet_exec(struct ast_channel *chan, const char *data)
Definition: app_minivm.c:2135
static int unload_module(void)
Unload mini voicemail module.
Definition: app_minivm.c:3440
static int load_config(int reload)
Load minivoicemail configuration.
Definition: app_minivm.c:2731
static int reload(void)
Reload mini voicemail module.
Definition: app_minivm.c:3414
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:3022
static int global_vmminmessage
Definition: app_minivm.c:689
static struct ast_custom_function minivm_counter_function
Definition: app_minivm.c:3374
static char global_logfile[PATH_MAX]
Definition: app_minivm.c:696
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:3294
static int get_date(char *s, int len)
Definition: app_minivm.c:844
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:3188
static struct minivm_account * find_user_realtime(const char *domain, const char *username)
Definition: app_minivm.c:978
static char * app_minivm_mwi
Definition: app_minivm.c:570
static struct minivm_stats global_stats
Statistics for voicemail.
Definition: app_minivm.c:682
static char * chanvar
Definition: app_system.c:113
static char * emailbody
static char locale[20]
#define var
Definition: ast_expr2f.c:614
static struct ast_str * prompt
Definition: asterisk.c:2761
Asterisk main include file. File version handling, generic pbx functions.
#define PATH_MAX
Definition: asterisk.h:40
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
static int tmp()
Definition: bt_open.c:389
CallerID (and other GR30) management and generation Includes code and algorithms from the Zapata libr...
char * ast_callerid_merge(char *buf, int bufsiz, const char *name, const char *num, const char *unknown)
Definition: callerid.c:1073
static snd_pcm_format_t format
Definition: chan_alsa.c:106
static char context[AST_MAX_CONTEXT]
Definition: chan_alsa.c:120
static char mailbox[AST_MAX_MAILBOX_UNIQUEID]
Definition: chan_mgcp.c:207
static const char type[]
Definition: chan_ooh323.c:109
static const char config[]
Definition: chan_ooh323.c:111
charset
Definition: chan_unistim.c:336
General Asterisk PBX channel definitions.
void ast_channel_exten_set(struct ast_channel *chan, const char *value)
int ast_waitfordigit(struct ast_channel *c, int ms)
Waits for a digit.
Definition: channel.c:3176
const char * ast_channel_uniqueid(const struct ast_channel *chan)
#define ast_channel_lock(chan)
Definition: channel.h:2922
const char * ast_channel_context(const struct ast_channel *chan)
int ast_channel_priority(const struct ast_channel *chan)
const char * ast_channel_name(const struct ast_channel *chan)
#define MAX_LANGUAGE
Definition: channel.h:172
const char * ast_channel_language(const struct ast_channel *chan)
#define AST_MAX_ACCOUNT_CODE
Definition: channel.h:170
const char * ast_channel_exten(const struct ast_channel *chan)
#define ast_channel_unref(c)
Decrease channel reference count.
Definition: channel.h:2958
#define AST_MAX_CONTEXT
Definition: channel.h:135
struct ast_party_caller * ast_channel_caller(struct ast_channel *chan)
void ast_channel_context_set(struct ast_channel *chan, const char *value)
#define ast_dummy_channel_alloc()
Create a fake channel structure.
Definition: channel.h:1282
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:7505
const char * ast_channel_macrocontext(const struct ast_channel *chan)
void ast_channel_priority_set(struct ast_channel *chan, int value)
int ast_answer(struct ast_channel *chan)
Answer a channel.
Definition: channel.c:2806
#define ast_channel_unlock(chan)
Definition: channel.h:2923
ast_channel_state
ast_channel states
Definition: channelstate.h:35
@ AST_STATE_UP
Definition: channelstate.h:42
Standard Command Line Interface.
#define CLI_SHOWUSAGE
Definition: cli.h:45
#define CLI_SUCCESS
Definition: cli.h:44
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define CLI_FAILURE
Definition: cli.h:46
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
short word
#define SENTINEL
Definition: compiler.h:87
Convenient Signal Processing routines.
@ THRESHOLD_SILENCE
Definition: dsp.h:73
int ast_dsp_get_threshold_from_settings(enum threshold which)
Get silence threshold from dsp.conf.
Definition: dsp.c:1999
char * end
Definition: eagi_proxy.c:73
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
Generic File Format Support. Should be included by clients of the file handling routines....
int ast_stopstream(struct ast_channel *c)
Stops a stream.
Definition: file.c:222
int ast_streamfile(struct ast_channel *c, const char *filename, const char *preflang)
Streams a file.
Definition: file.c:1291
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:1855
int ast_fileexists(const char *filename, const char *fmt, const char *preflang)
Checks for the existence of a given file.
Definition: file.c:1127
int ast_filedelete(const char *filename, const char *fmt)
Deletes a file.
Definition: file.c:1139
#define AST_DIGIT_ANY
Definition: file.h:48
int ast_waitstream(struct ast_channel *c, const char *breakon)
Waits for a stream to stop or digit to be pressed.
Definition: file.c:1817
static const char name[]
Definition: format_mp3.c:68
static int len(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t buflen)
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,...
static ENTRY retval
Definition: hsearch.c:50
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
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:2209
@ AST_LOCK_TIMEOUT
int ast_safe_execvp(int dualfork, const char *file, char *const argv[])
Safely spawn an external program while closing file descriptors.
Definition: asterisk.c:1216
#define AST_APP_OPTIONS(holder, options...)
Declares an array of options for an application.
#define ast_app_separate_args(a, b, c, d)
enum AST_LOCK_RESULT ast_lock_path(const char *path)
Lock a filesystem path.
Definition: main/app.c:2674
#define AST_APP_OPTION_ARG(option, flagno, argno)
Declares an application option that accepts an argument.
int ast_safe_system(const char *s)
Safely spawn an OS shell command while closing file descriptors.
Definition: extconf.c:829
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:1686
@ AST_RECORD_IF_EXISTS_OVERWRITE
#define AST_APP_OPTION(option, flagno)
Declares an application option that does not accept an argument.
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:3126
int ast_unlock_path(const char *path)
Unlock a path.
Definition: main/app.c:2690
char * mkdtemp(char *template_s)
char * strsep(char **str, const char *delims)
Configuration File Parser.
#define ast_config_load(filename, flags)
Load a config file.
struct ast_variable * ast_load_realtime(const char *family,...) attribute_sentinel
Definition: main/config.c:3405
char * ast_category_browse(struct ast_config *config, const char *prev_name)
Browse categories.
Definition: extconf.c:3327
#define ast_variable_new(name, value, filename)
@ CONFIG_FLAG_FILEUNCHANGED
#define CONFIG_STATUS_FILEUNCHANGED
#define CONFIG_STATUS_FILEINVALID
void ast_config_destroy(struct ast_config *cfg)
Destroys a config.
Definition: extconf.c:1289
const char * ast_variable_retrieve(struct ast_config *config, const char *category, const char *variable)
Definition: main/config.c:768
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
struct ast_variable * ast_variable_browse(const struct ast_config *config, const char *category_name)
Definition: extconf.c:1215
#define AST_OPTION_RXGAIN
#define DEBUG_ATLEAST(level)
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define ast_verb(level,...)
#define LOG_NOTICE
#define LOG_WARNING
Asterisk JSON abstraction layer.
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_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:591
A set of macros to manage forward-linked lists.
#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
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_EMPTY(head)
Checks whether the specified list contains any entries.
Definition: linkedlists.h:450
#define AST_LIST_INSERT_TAIL(head, elm, field)
Appends a list entry to the tail of a list.
Definition: linkedlists.h:731
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
#define AST_LIST_REMOVE_HEAD(head, field)
Removes and returns the head entry from a list.
Definition: linkedlists.h:833
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
Custom localtime functions for multiple timezones.
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
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
Asterisk locking-related definitions:
#define ast_mutex_unlock(a)
Definition: lock.h:188
#define ast_mutex_lock(a)
Definition: lock.h:187
#define AST_MUTEX_DEFINE_STATIC(mutex)
Definition: lock.h:518
int errno
Asterisk module definitions.
@ AST_MODFLAG_DEFAULT
Definition: module.h:315
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:543
@ AST_MODULE_SUPPORT_EXTENDED
Definition: module.h:122
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
int ast_unregister_application(const char *app)
Unregister an application.
Definition: pbx_app.c:392
#define ast_register_application_xml(app, execute)
Register an application using XML documentation.
Definition: module.h:626
Asterisk MWI API.
struct stasis_message_type * ast_mwi_vm_app_type(void)
Get the Stasis Message Bus API message type for voicemail application specific messages.
struct ast_mwi_state * ast_mwi_create(const char *mailbox, const char *context)
Create a ast_mwi_state object.
Definition: mwi.c:152
#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
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 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
#define MAXHOSTNAMELEN
Definition: network.h:69
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_CONFIG_DIR
Definition: options.c:151
const char * ast_config_AST_SPOOL_DIR
Definition: options.c:154
const char * ast_config_AST_LOG_DIR
Definition: options.c:159
Core PBX routines and definitions.
void ast_str_substitute_variables(struct ast_str **buf, ssize_t maxlen, struct ast_channel *chan, const char *templ)
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:4182
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 ast_custom_function_register(acf)
Register a custom function.
Definition: pbx.h:1543
int ast_custom_function_unregister(struct ast_custom_function *acf)
Unregister a custom function.
const char * pbx_builtin_getvar_helper(struct ast_channel *chan, const char *name)
Return a pointer to the value of the corresponding channel variable.
static char email[80]
Definition: pbx_dundi.c:206
#define NULL
Definition: resample.c:96
Say numbers and dates (maybe words one day too)
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:8342
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
void stasis_publish(struct stasis_topic *topic, struct stasis_message *message)
Publish a message to a topic's subscribers.
Definition: stasis.c:1513
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:1117
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:739
#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
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: main/utils.c:2097
#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 force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
#define ast_str_alloca(init_len)
Definition: strings.h:826
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:674
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:640
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:1091
size_t ast_str_strlen(const struct ast_str *buf)
Returns the current length of the string stored within buf.
Definition: strings.h:711
void ast_copy_string(char *dst, const char *src, size_t size)
Size-limited null-terminating string copy.
Definition: strings.h:406
Main Channel structure associated with a channel.
descriptor for a cli entry.
Definition: cli.h:171
int args
This gets set in ast_cli_register()
Definition: cli.h:185
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
Data structure associated with a custom dialplan function.
Definition: pbx.h:118
const char * name
Definition: pbx.h:119
Structure used to handle boolean flags.
Definition: utils.h:199
Abstract JSON element (object, array, string, int, ...).
The structure that contains MWI state.
Definition: mwi.h:455
Caller Party information.
Definition: channel.h:418
struct ast_party_id id
Caller party ID.
Definition: channel.h:420
struct ast_party_name name
Subscriber name.
Definition: channel.h:340
struct ast_party_number number
Subscriber phone number.
Definition: channel.h:342
unsigned char valid
TRUE if the name information is valid/present.
Definition: channel.h:279
char * str
Subscriber name (Malloced)
Definition: channel.h:264
unsigned char valid
TRUE if the number information is valid/present.
Definition: channel.h:297
char * str
Subscriber phone number (Malloced)
Definition: channel.h:291
Support for dynamic strings.
Definition: strings.h:604
Structure for variables, used for configurations and for channel variables.
struct ast_variable * next
structure for queuing ARI channel variable setting
Definition: control.c:685
Domain data structure.
Definition: sip.h:887
char domain[MAXHOSTNAMELEN]
Definition: sip.h:888
Options for leaving voicemail with the voicemail() application.
Definition: app_minivm.c:654
signed char record_gain
Definition: app_minivm.c:656
unsigned int flags
Definition: app_minivm.c:655
The list of e-mail templates.
Definition: app_minivm.c:651
char uniqueid[20]
Definition: app_minivm.c:617
struct ast_variable * chanvars
Definition: app_minivm.c:623
char serveremail[80]
Definition: app_minivm.c:613
char pager[80]
Definition: app_minivm.c:611
char zonetag[80]
Definition: app_minivm.c:616
char username[AST_MAX_CONTEXT]
Definition: app_minivm.c:605
char ptemplate[80]
Definition: app_minivm.c:621
struct minivm_account::@51 list
double volgain
Definition: app_minivm.c:624
char externnotify[160]
Definition: app_minivm.c:614
char exit[80]
Definition: app_minivm.c:618
char email[80]
Definition: app_minivm.c:610
char fullname[120]
Definition: app_minivm.c:609
unsigned int flags
Definition: app_minivm.c:622
char etemplate[80]
Definition: app_minivm.c:620
char language[MAX_LANGUAGE]
Definition: app_minivm.c:615
char accountcode[AST_MAX_ACCOUNT_CODE]
Definition: app_minivm.c:612
char pincode[10]
Definition: app_minivm.c:608
char domain[AST_MAX_CONTEXT]
Definition: app_minivm.c:606
char attachfmt[80]
Definition: app_minivm.c:619
Structure for gathering statistics.
Definition: app_minivm.c:671
struct timeval lastreceived
Definition: app_minivm.c:678
int receivedmessages
Definition: app_minivm.c:677
struct timeval reset
Definition: app_minivm.c:676
int voicemailaccounts
Definition: app_minivm.c:672
char serveremail[80]
Definition: app_minivm.c:641
struct minivm_template::@52 list
char name[80]
Definition: app_minivm.c:638
char fromaddress[100]
Definition: app_minivm.c:640
char dateformat[80]
Definition: app_minivm.c:645
char subject[100]
Definition: app_minivm.c:642
char locale[20]
Definition: app_minivm.c:644
Voicemail time zones.
Definition: app_minivm.c:660
char msg_format[BUFSIZ]
Definition: app_minivm.c:663
char name[80]
Definition: app_minivm.c:661
struct minivm_zone::@53 list
char timezone[80]
Definition: app_minivm.c:662
The list of e-mail time zones.
Definition: app_minivm.c:668
Number structure.
Definition: app_followme.c:154
int value
Definition: syslog.c:37
static struct test_options options
static struct test_val a
Time-related functions and macros.
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:157
FILE * out
Definition: utils/frame.c:33
int error(const char *format,...)
Definition: utils/frame.c:999
Utility functions.
#define ast_test_flag(p, flag)
Definition: utils.h:63
#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:936
#define ast_set2_flag(p, value, flag)
Definition: utils.h:94
long int ast_random(void)
Definition: main/utils.c:2210
int ast_mkdir(const char *path, int mode)
Recursively create directory path.
Definition: main/utils.c:2377
#define ast_set_flag(p, flag)
Definition: utils.h:70
#define ARRAY_LEN(a)
Definition: utils.h:661
#define ast_copy_flags(dest, src, flagz)
Definition: utils.h:84
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: main/utils.c:702
#define AST_FLAGS_ALL
Definition: utils.h:196