2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \author Mark Spencer <markster@digium.com>
22 * \brief Comedian Mail - Voicemail System
24 * unixODBC (http://www.unixodbc.org/)
25 * A source distribution of University of Washington's IMAP c-client
26 * (http://www.washington.edu/imap/)
30 * \note For information about voicemail IMAP storage, https://wiki.asterisk.org/wiki/display/AST/IMAP+Voicemail+Storage
31 * \ingroup applications
32 * \todo This module requires res_adsi to load. This needs to be optional
35 * \todo This file is now almost impossible to work with, due to all \#ifdefs.
36 * Feels like the database code before realtime. Someone - please come up
37 * with a plan to clean this up.
40 /*! \li \ref app_voicemail.c uses configuration file \ref voicemail.conf
41 * \addtogroup configuration_file Configuration Files
45 * \page voicemail.conf voicemail.conf
46 * \verbinclude voicemail.conf.sample
50 <use type="module">res_adsi</use>
51 <use type="module">res_smdi</use>
52 <support_level>core</support_level>
56 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" touch_on_change="apps/app_voicemail.c apps/app_directory.c">
57 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
58 <conflict>ODBC_STORAGE</conflict>
59 <conflict>IMAP_STORAGE</conflict>
60 <defaultenabled>yes</defaultenabled>
61 <support_level>core</support_level>
63 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
64 <depend>generic_odbc</depend>
66 <conflict>IMAP_STORAGE</conflict>
67 <conflict>FILE_STORAGE</conflict>
68 <defaultenabled>no</defaultenabled>
69 <support_level>core</support_level>
71 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
72 <depend>imap_tk</depend>
73 <conflict>ODBC_STORAGE</conflict>
74 <conflict>FILE_STORAGE</conflict>
75 <use type="external">openssl</use>
76 <defaultenabled>no</defaultenabled>
77 <support_level>core</support_level>
88 #ifdef USE_SYSTEM_IMAP
89 #include <imap/c-client.h>
90 #include <imap/imap4r1.h>
91 #include <imap/linkage.h>
92 #elif defined (USE_SYSTEM_CCLIENT)
93 #include <c-client/c-client.h>
94 #include <c-client/imap4r1.h>
95 #include <c-client/linkage.h>
103 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
105 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
106 #include <sys/time.h>
107 #include <sys/stat.h>
108 #include <sys/mman.h>
111 #if defined(__FreeBSD__) || defined(__OpenBSD__)
112 #include <sys/wait.h>
115 #include "asterisk/logger.h"
116 #include "asterisk/lock.h"
117 #include "asterisk/file.h"
118 #include "asterisk/channel.h"
119 #include "asterisk/pbx.h"
120 #include "asterisk/config.h"
121 #include "asterisk/say.h"
122 #include "asterisk/module.h"
123 #include "asterisk/adsi.h"
124 #include "asterisk/app.h"
125 #include "asterisk/manager.h"
126 #include "asterisk/dsp.h"
127 #include "asterisk/localtime.h"
128 #include "asterisk/cli.h"
129 #include "asterisk/utils.h"
130 #include "asterisk/stringfields.h"
131 #include "asterisk/strings.h"
132 #include "asterisk/smdi.h"
133 #include "asterisk/astobj2.h"
134 #include "asterisk/taskprocessor.h"
135 #include "asterisk/test.h"
138 #include "asterisk/res_odbc.h"
142 #include "asterisk/threadstorage.h"
146 <application name="VoiceMail" language="en_US">
148 Leave a Voicemail message.
151 <parameter name="mailboxs" argsep="&" required="true">
152 <argument name="mailbox1" argsep="@" required="true">
153 <argument name="mailbox" required="true" />
154 <argument name="context" />
156 <argument name="mailbox2" argsep="@" multiple="true">
157 <argument name="mailbox" required="true" />
158 <argument name="context" />
161 <parameter name="options">
164 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
167 <argument name="c" />
168 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
169 if played during the greeting. Context defaults to the current context.</para>
172 <argument name="#" required="true" />
173 <para>Use the specified amount of gain when recording the voicemail
174 message. The units are whole-number decibels (dB). Only works on supported
175 technologies, which is DAHDI only.</para>
178 <para>Skip the playback of instructions for leaving a message to the
179 calling party.</para>
182 <para>Play the <literal>unavailable</literal> greeting.</para>
185 <para>Mark message as <literal>URGENT</literal>.</para>
188 <para>Mark message as <literal>PRIORITY</literal>.</para>
194 <para>This application allows the calling party to leave a message for the specified
195 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
196 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
198 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
201 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
204 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
207 <para>This application will set the following channel variable upon completion:</para>
209 <variable name="VMSTATUS">
210 <para>This indicates the status of the execution of the VoiceMail application.</para>
211 <value name="SUCCESS" />
212 <value name="USEREXIT" />
213 <value name="FAILED" />
218 <ref type="application">VoiceMailMain</ref>
221 <application name="VoiceMailMain" language="en_US">
223 Check Voicemail messages.
226 <parameter name="mailbox" required="true" argsep="@">
227 <argument name="mailbox" />
228 <argument name="context" />
230 <parameter name="options">
233 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
234 the mailbox that is entered by the caller.</para>
237 <argument name="#" required="true" />
238 <para>Use the specified amount of gain when recording a voicemail message.
239 The units are whole-number decibels (dB).</para>
242 <para>Skip checking the passcode for the mailbox.</para>
245 <argument name="folder" required="true" />
246 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
247 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
249 <enum name="0"><para>INBOX</para></enum>
250 <enum name="1"><para>Old</para></enum>
251 <enum name="2"><para>Work</para></enum>
252 <enum name="3"><para>Family</para></enum>
253 <enum name="4"><para>Friends</para></enum>
254 <enum name="5"><para>Cust1</para></enum>
255 <enum name="6"><para>Cust2</para></enum>
256 <enum name="7"><para>Cust3</para></enum>
257 <enum name="8"><para>Cust4</para></enum>
258 <enum name="9"><para>Cust5</para></enum>
265 <para>This application allows the calling party to check voicemail messages. A specific
266 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
267 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
268 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
269 <literal>default</literal> context will be used.</para>
270 <para>The VoiceMailMain application will exit if the following DTMF digit is entered as Mailbox
271 or Password, and the extension exists:</para>
274 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
279 <ref type="application">VoiceMail</ref>
282 <application name="MailboxExists" language="en_US">
284 Check to see if Voicemail mailbox exists.
287 <parameter name="mailbox" required="true" argsep="@">
288 <argument name="mailbox" required="true" />
289 <argument name="context" />
291 <parameter name="options">
292 <para>None options.</para>
296 <note><para>DEPRECATED. Use VM_INFO(mailbox[@context],exists) instead.</para></note>
297 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
298 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
300 <para>This application will set the following channel variable upon completion:</para>
302 <variable name="VMBOXEXISTSSTATUS">
303 <para>This will contain the status of the execution of the MailboxExists application.
304 Possible values include:</para>
305 <value name="SUCCESS" />
306 <value name="FAILED" />
311 <ref type="function">VM_INFO</ref>
314 <application name="VMAuthenticate" language="en_US">
316 Authenticate with Voicemail passwords.
319 <parameter name="mailbox" required="true" argsep="@">
320 <argument name="mailbox" />
321 <argument name="context" />
323 <parameter name="options">
326 <para>Skip playing the initial prompts.</para>
332 <para>This application behaves the same way as the Authenticate application, but the passwords
333 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
334 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
335 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
337 <para>The VMAuthenticate application will exit if the following DTMF digit is entered as Mailbox
338 or Password, and the extension exists:</para>
341 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
346 <application name="VoiceMailPlayMsg" language="en_US">
348 Play a single voice mail msg from a mailbox by msg id.
351 <parameter name="mailbox" required="true" argsep="@">
352 <argument name="mailbox" />
353 <argument name="context" />
355 <parameter name="msg_id" required="true">
356 <para>The msg id of the msg to play back. </para>
360 <para>This application sets the following channel variable upon completion:</para>
362 <variable name="VOICEMAIL_PLAYBACKSTATUS">
363 <para>The status of the playback attempt as a text string.</para>
364 <value name="SUCCESS"/>
365 <value name="FAILED"/>
370 <application name="VMSayName" language="en_US">
372 Play the name of a voicemail user
375 <parameter name="mailbox" required="true" argsep="@">
376 <argument name="mailbox" />
377 <argument name="context" />
381 <para>This application will say the recorded name of the voicemail user specified as the
382 argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
385 <function name="MAILBOX_EXISTS" language="en_US">
387 Tell if a mailbox is configured.
390 <parameter name="mailbox" required="true" />
391 <parameter name="context" />
394 <note><para>DEPRECATED. Use VM_INFO(mailbox[@context],exists) instead.</para></note>
395 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
396 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
400 <ref type="function">VM_INFO</ref>
403 <function name="VM_INFO" language="en_US">
405 Returns the selected attribute from a mailbox.
408 <parameter name="mailbox" argsep="@" required="true">
409 <argument name="mailbox" required="true" />
410 <argument name="context" />
412 <parameter name="attribute" required="true">
414 <option name="count">
415 <para>Count of messages in specified <replaceable>folder</replaceable>.
416 If <replaceable>folder</replaceable> is not specified, defaults to <literal>INBOX</literal>.</para>
418 <option name="email">
419 <para>E-mail address associated with the mailbox.</para>
421 <option name="exists">
422 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.</para>
424 <option name="fullname">
425 <para>Full name associated with the mailbox.</para>
427 <option name="language">
428 <para>Mailbox language if overridden, otherwise the language of the channel.</para>
430 <option name="locale">
431 <para>Mailbox locale if overridden, otherwise global locale.</para>
433 <option name="pager">
434 <para>Pager e-mail address associated with the mailbox.</para>
436 <option name="password">
437 <para>Mailbox access password.</para>
440 <para>Mailbox timezone if overridden, otherwise global timezone</para>
444 <parameter name="folder" required="false">
445 <para>If not specified, <literal>INBOX</literal> is assumed.</para>
449 <para>Returns the selected attribute from the specified <replaceable>mailbox</replaceable>.
450 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
451 context. Where the <replaceable>folder</replaceable> can be specified, common folders
452 include <literal>INBOX</literal>, <literal>Old</literal>, <literal>Work</literal>,
453 <literal>Family</literal> and <literal>Friends</literal>.</para>
456 <manager name="VoicemailUsersList" language="en_US">
458 List All Voicemail User Information.
461 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
466 <manager name="VoicemailRefresh" language="en_US">
468 Tell Asterisk to poll mailboxes for a change
471 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
472 <parameter name="Context" />
473 <parameter name="Mailbox" />
476 <para>Normally, MWI indicators are only sent when Asterisk itself
477 changes a mailbox. With external programs that modify the content
478 of a mailbox from outside the application, an option exists called
479 <literal>pollmailboxes</literal> that will cause voicemail to
480 continually scan all mailboxes on a system for changes. This can
481 cause a large amount of load on a system. This command allows
482 external applications to signal when a particular mailbox has
483 changed, thus permitting external applications to modify mailboxes
484 and MWI to work without introducing considerable CPU load.</para>
485 <para>If <replaceable>Context</replaceable> is not specified, all
486 mailboxes on the system will be polled for changes. If
487 <replaceable>Context</replaceable> is specified, but
488 <replaceable>Mailbox</replaceable> is omitted, then all mailboxes
489 within <replaceable>Context</replaceable> will be polled.
490 Otherwise, only a single mailbox will be polled for changes.</para>
496 static char imapserver[48];
497 static char imapport[8];
498 static char imapflags[128];
499 static char imapfolder[64];
500 static char imapparentfolder[64] = "\0";
501 static char greetingfolder[64];
502 static char authuser[32];
503 static char authpassword[42];
504 static int imapversion = 1;
506 static int expungeonhangup = 1;
507 static int imapgreetings = 0;
508 static char delimiter = '\0';
513 AST_THREADSTORAGE(ts_vmstate);
515 /* Forward declarations for IMAP */
516 static int init_mailstream(struct vm_state *vms, int box);
517 static void write_file(char *filename, char *buffer, unsigned long len);
518 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
519 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
520 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
521 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
522 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
523 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
524 static void vmstate_insert(struct vm_state *vms);
525 static void vmstate_delete(struct vm_state *vms);
526 static void set_update(MAILSTREAM * stream);
527 static void init_vm_state(struct vm_state *vms);
528 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
529 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream);
530 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
531 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
532 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id);
533 static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder);
534 static void update_messages_by_imapuser(const char *user, unsigned long number);
535 static int vm_delete(char *file);
537 static int imap_remove_file (char *dir, int msgnum);
538 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
539 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
540 static void check_quota(struct vm_state *vms, char *mailbox);
541 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
543 struct vm_state *vms;
544 AST_LIST_ENTRY(vmstate) list;
547 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
551 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
553 #define COMMAND_TIMEOUT 5000
554 /* Don't modify these here; set your umask at runtime instead */
555 #define VOICEMAIL_DIR_MODE 0777
556 #define VOICEMAIL_FILE_MODE 0666
557 #define CHUNKSIZE 65536
559 #define VOICEMAIL_CONFIG "voicemail.conf"
560 #define ASTERISK_USERNAME "asterisk"
562 /* Define fast-forward, pause, restart, and reverse keys
563 * while listening to a voicemail message - these are
564 * strings, not characters */
565 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
566 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
567 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
568 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
569 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
570 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
572 /* Default mail command to mail voicemail. Change it with the
573 * mailcmd= command in voicemail.conf */
574 #define SENDMAIL "/usr/sbin/sendmail -t"
576 #define INTRO "vm-intro"
579 #define MAXMSGLIMIT 9999
581 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
583 #define BASELINELEN 72
584 #define BASEMAXINLINE 256
591 #define MAX_DATETIME_FORMAT 512
592 #define MAX_NUM_CID_CONTEXTS 10
594 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
595 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
596 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
597 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
598 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
599 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
600 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
601 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
602 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
603 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
604 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
605 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
606 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
607 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
608 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
609 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
610 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
611 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
612 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
613 #define ERROR_LOCK_PATH -100
614 #define OPERATOR_EXIT 300
625 enum vm_option_flags {
626 OPT_SILENT = (1 << 0),
627 OPT_BUSY_GREETING = (1 << 1),
628 OPT_UNAVAIL_GREETING = (1 << 2),
629 OPT_RECORDGAIN = (1 << 3),
630 OPT_PREPEND_MAILBOX = (1 << 4),
631 OPT_AUTOPLAY = (1 << 6),
632 OPT_DTMFEXIT = (1 << 7),
633 OPT_MESSAGE_Urgent = (1 << 8),
634 OPT_MESSAGE_PRIORITY = (1 << 9)
637 enum vm_option_args {
638 OPT_ARG_RECORDGAIN = 0,
639 OPT_ARG_PLAYFOLDER = 1,
640 OPT_ARG_DTMFEXIT = 2,
641 /* This *must* be the last value in this enum! */
642 OPT_ARG_ARRAY_SIZE = 3,
645 enum vm_passwordlocation {
646 OPT_PWLOC_VOICEMAILCONF = 0,
647 OPT_PWLOC_SPOOLDIR = 1,
648 OPT_PWLOC_USERSCONF = 2,
651 AST_APP_OPTIONS(vm_app_options, {
652 AST_APP_OPTION('s', OPT_SILENT),
653 AST_APP_OPTION('b', OPT_BUSY_GREETING),
654 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
655 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
656 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
657 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
658 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
659 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
660 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
663 static const char * const mailbox_folders[] = {
682 static int load_config(int reload);
683 #ifdef TEST_FRAMEWORK
684 static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg);
686 static int actual_load_config(int reload, struct ast_config *cfg, struct ast_config *ucfg);
688 /*! \page vmlang Voicemail Language Syntaxes Supported
690 \par Syntaxes supported, not really language codes.
697 \arg \b pt - Portuguese
698 \arg \b pt_BR - Portuguese (Brazil)
700 \arg \b no - Norwegian
702 \arg \b tw - Chinese (Taiwan)
703 \arg \b ua - Ukrainian
705 German requires the following additional soundfile:
706 \arg \b 1F einE (feminine)
708 Spanish requires the following additional soundfile:
709 \arg \b 1M un (masculine)
711 Dutch, Portuguese & Spanish require the following additional soundfiles:
712 \arg \b vm-INBOXs singular of 'new'
713 \arg \b vm-Olds singular of 'old/heard/read'
716 \arg \b vm-INBOX nieuwe (nl)
717 \arg \b vm-Old oude (nl)
720 \arg \b vm-new-a 'new', feminine singular accusative
721 \arg \b vm-new-e 'new', feminine plural accusative
722 \arg \b vm-new-ych 'new', feminine plural genitive
723 \arg \b vm-old-a 'old', feminine singular accusative
724 \arg \b vm-old-e 'old', feminine plural accusative
725 \arg \b vm-old-ych 'old', feminine plural genitive
726 \arg \b digits/1-a 'one', not always same as 'digits/1'
727 \arg \b digits/2-ie 'two', not always same as 'digits/2'
730 \arg \b vm-nytt singular of 'new'
731 \arg \b vm-nya plural of 'new'
732 \arg \b vm-gammalt singular of 'old'
733 \arg \b vm-gamla plural of 'old'
734 \arg \b digits/ett 'one', not always same as 'digits/1'
737 \arg \b vm-ny singular of 'new'
738 \arg \b vm-nye plural of 'new'
739 \arg \b vm-gammel singular of 'old'
740 \arg \b vm-gamle plural of 'old'
748 Italian requires the following additional soundfile:
752 \arg \b vm-nuovi new plural
753 \arg \b vm-vecchio old
754 \arg \b vm-vecchi old plural
756 Chinese (Taiwan) requires the following additional soundfile:
757 \arg \b vm-tong A class-word for call (tong1)
758 \arg \b vm-ri A class-word for day (ri4)
759 \arg \b vm-you You (ni3)
760 \arg \b vm-haveno Have no (mei2 you3)
761 \arg \b vm-have Have (you3)
762 \arg \b vm-listen To listen (yao4 ting1)
765 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
766 spelled among others when you have to change folder. For the above reasons, vm-INBOX
767 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
776 unsigned char iobuf[BASEMAXINLINE];
779 /*! Structure for linked list of users
780 * Use ast_vm_user_destroy() to free one of these structures. */
782 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
783 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
784 char password[80]; /*!< Secret pin code, numbers only */
785 char fullname[80]; /*!< Full name, for directory app */
786 char email[80]; /*!< E-mail address */
787 char *emailsubject; /*!< E-mail subject */
788 char *emailbody; /*!< E-mail body */
789 char pager[80]; /*!< E-mail address to pager (no attachment) */
790 char serveremail[80]; /*!< From: Mail address */
791 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
792 char zonetag[80]; /*!< Time zone */
793 char locale[20]; /*!< The locale (for presentation of date/time) */
796 char uniqueid[80]; /*!< Unique integer identifier */
798 char attachfmt[20]; /*!< Attachment format */
799 unsigned int flags; /*!< VM_ flags */
801 int minsecs; /*!< Minimum number of seconds per message for this mailbox */
802 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
803 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
804 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
805 int passwordlocation; /*!< Storage location of the password */
807 char imapserver[48]; /*!< IMAP server address */
808 char imapport[8]; /*!< IMAP server port */
809 char imapflags[128]; /*!< IMAP optional flags */
810 char imapuser[80]; /*!< IMAP server login */
811 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
812 char imapfolder[64]; /*!< IMAP voicemail folder */
813 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
814 int imapversion; /*!< If configuration changes, use the new values */
816 double volgain; /*!< Volume gain for voicemails sent via email */
817 AST_LIST_ENTRY(ast_vm_user) list;
820 /*! Voicemail time zones */
822 AST_LIST_ENTRY(vm_zone) list;
825 char msg_format[512];
828 #define VMSTATE_MAX_MSG_ARRAY 256
830 /*! Voicemail mailbox state */
835 char curdir[PATH_MAX];
836 char vmbox[PATH_MAX];
838 char intro[PATH_MAX];
841 int dh_arraysize; /* used for deleted / heard allocation */
851 int updated; /*!< decremented on each mail check until 1 -allows delay */
852 long msgArray[VMSTATE_MAX_MSG_ARRAY];
853 MAILSTREAM *mailstream;
855 char imapuser[80]; /*!< IMAP server login */
856 char imapfolder[64]; /*!< IMAP voicemail folder */
857 char imapserver[48]; /*!< IMAP server address */
858 char imapport[8]; /*!< IMAP server port */
859 char imapflags[128]; /*!< IMAP optional flags */
862 char introfn[PATH_MAX]; /*!< Name of prepended file */
863 unsigned int quota_limit;
864 unsigned int quota_usage;
865 struct vm_state *persist_vms;
870 static char odbc_database[80];
871 static char odbc_table[80];
872 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
873 #define DISPOSE(a,b) remove_file(a,b)
874 #define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d)
875 #define EXISTS(a,b,c,d) (message_exists(a,b))
876 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
877 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
878 #define DELETE(a,b,c,d) (delete_file(a,b))
879 #define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c)))
882 #define DISPOSE(a,b) (imap_remove_file(a,b))
883 #define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k))
884 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
885 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
886 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
887 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
888 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
889 #define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f)))
891 #define RETRIEVE(a,b,c,d)
893 #define STORE(a,b,c,d,e,f,g,h,i,j,k)
894 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
895 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
896 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
897 #define DELETE(a,b,c,d) (vm_delete(c))
898 #define UPDATE_MSG_ID(a, b, c, d, e, f)
902 static char VM_SPOOL_DIR[PATH_MAX];
904 static char ext_pass_cmd[128];
905 static char ext_pass_check_cmd[128];
909 #define PWDCHANGE_INTERNAL (1 << 1)
910 #define PWDCHANGE_EXTERNAL (1 << 2)
911 static int pwdchange = PWDCHANGE_INTERNAL;
914 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
917 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
919 # define tdesc "Comedian Mail (Voicemail System)"
923 static char userscontext[AST_MAX_EXTENSION] = "default";
925 static char *addesc = "Comedian Mail";
927 /* Leave a message */
928 static char *app = "VoiceMail";
930 /* Check mail, control, etc */
931 static char *app2 = "VoiceMailMain";
933 static char *app3 = "MailboxExists";
934 static char *app4 = "VMAuthenticate";
936 static char *playmsg_app = "VoiceMailPlayMsg";
938 static char *sayname_app = "VMSayName";
940 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
941 static AST_LIST_HEAD_STATIC(zones, vm_zone);
942 static char zonetag[80];
943 static char locale[20];
944 static int maxsilence;
946 static int maxdeletedmsg;
947 static int silencethreshold = 128;
948 static char serveremail[80];
949 static char mailcmd[160]; /* Configurable mail cmd */
950 static char externnotify[160];
951 static struct ast_smdi_interface *smdi_iface = NULL;
952 static char vmfmts[80];
953 static double volgain;
954 static int vmminsecs;
955 static int vmmaxsecs;
958 static int maxlogins;
959 static int minpassword;
960 static int passwordlocation;
962 /*! Poll mailboxes for changes since there is something external to
963 * app_voicemail that may change them. */
964 static unsigned int poll_mailboxes;
966 /*! Polling frequency */
967 static unsigned int poll_freq;
968 /*! By default, poll every 30 seconds */
969 #define DEFAULT_POLL_FREQ 30
971 AST_MUTEX_DEFINE_STATIC(poll_lock);
972 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
973 static pthread_t poll_thread = AST_PTHREADT_NULL;
974 static unsigned char poll_thread_run;
976 /*! Subscription to MWI event subscription changes */
977 static struct stasis_subscription *mwi_sub_sub;
980 * \brief An MWI subscription
982 * This is so we can keep track of which mailboxes are subscribed to.
983 * This way, we know which mailboxes to poll when the pollmailboxes
984 * option is being used.
987 AST_RWLIST_ENTRY(mwi_sub) entry;
995 struct mwi_sub_task {
998 const char *uniqueid;
1001 static void mwi_sub_task_dtor(struct mwi_sub_task *mwist)
1003 ast_free((void *) mwist->mailbox);
1004 ast_free((void *) mwist->context);
1005 ast_free((void *) mwist->uniqueid);
1009 static struct ast_taskprocessor *mwi_subscription_tps;
1011 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
1013 /* custom audio control prompts for voicemail playback */
1014 static char listen_control_forward_key[12];
1015 static char listen_control_reverse_key[12];
1016 static char listen_control_pause_key[12];
1017 static char listen_control_restart_key[12];
1018 static char listen_control_stop_key[12];
1020 /* custom password sounds */
1021 static char vm_password[80] = "vm-password";
1022 static char vm_newpassword[80] = "vm-newpassword";
1023 static char vm_passchanged[80] = "vm-passchanged";
1024 static char vm_reenterpassword[80] = "vm-reenterpassword";
1025 static char vm_mismatch[80] = "vm-mismatch";
1026 static char vm_invalid_password[80] = "vm-invalid-password";
1027 static char vm_pls_try_again[80] = "vm-pls-try-again";
1030 * XXX If we have the time, motivation, etc. to fix up this prompt, one of the following would be appropriate:
1031 * 1. create a sound along the lines of "Please try again. When done, press the pound key" which could be spliced
1032 * from existing sound clips. This would require some programming changes in the area of vm_forward options and also
1033 * app.c's __ast_play_and_record function
1034 * 2. create a sound prompt saying "Please try again. When done recording, press any key to stop and send the prepended
1035 * message." At the time of this comment, I think this would require new voice work to be commissioned.
1036 * 3. Something way different like providing instructions before a time out or a post-recording menu. This would require
1037 * more effort than either of the other two.
1039 static char vm_prepend_timeout[80] = "vm-then-pound";
1041 static struct ast_flags globalflags = {0};
1043 static int saydurationminfo;
1045 static char dialcontext[AST_MAX_CONTEXT] = "";
1046 static char callcontext[AST_MAX_CONTEXT] = "";
1047 static char exitcontext[AST_MAX_CONTEXT] = "";
1049 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
1052 static char *emailbody = NULL;
1053 static char *emailsubject = NULL;
1054 static char *pagerbody = NULL;
1055 static char *pagersubject = NULL;
1056 static char fromstring[100];
1057 static char pagerfromstring[100];
1058 static char charset[32] = "ISO-8859-1";
1060 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
1061 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
1062 static int adsiver = 1;
1063 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
1064 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
1066 /* Forward declarations - generic */
1067 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
1068 static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
1069 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
1070 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
1071 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
1072 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
1073 signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id);
1074 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
1075 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
1076 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag);
1077 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag, const char *msg_id);
1078 static void apply_options(struct ast_vm_user *vmu, const char *options);
1079 static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
1080 static int is_valid_dtmf(const char *key);
1081 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
1082 static int write_password_to_file(const char *secretfn, const char *password);
1083 static const char *substitute_escapes(const char *value);
1084 static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu);
1086 * Place a message in the indicated folder
1088 * \param vmu Voicemail user
1089 * \param vms Current voicemail state for the user
1090 * \param msg The message number to save
1091 * \param box The folder into which the message should be saved
1092 * \param[out] newmsg The new message number of the saved message
1093 * \param move Tells whether to copy or to move the message
1095 * \note the "move" parameter is only honored for IMAP voicemail presently
1097 * \retval other Failure
1099 static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
1101 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_create(const char *mailbox, const char *context, const char *folder, int descending, enum ast_vm_snapshot_sort_val sort_val, int combine_INBOX_and_OLD);
1102 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
1104 static int vm_msg_forward(const char *from_mailbox, const char *from_context, const char *from_folder, const char *to_mailbox, const char *to_context, const char *to_folder, size_t num_msgs, const char *msg_ids[], int delete_old);
1105 static int vm_msg_move(const char *mailbox, const char *context, size_t num_msgs, const char *oldfolder, const char *old_msg_ids[], const char *newfolder);
1106 static int vm_msg_remove(const char *mailbox, const char *context, size_t num_msgs, const char *folder, const char *msgs[]);
1107 static int vm_msg_play(struct ast_channel *chan, const char *mailbox, const char *context, const char *folder, const char *msg_num, ast_vm_msg_play_cb cb);
1109 #ifdef TEST_FRAMEWORK
1110 static int vm_test_destroy_user(const char *context, const char *mailbox);
1111 static int vm_test_create_user(const char *context, const char *mailbox);
1114 struct ao2_container *inprocess_container;
1122 static int inprocess_hash_fn(const void *obj, const int flags)
1124 const struct inprocess *i = obj;
1125 return atoi(i->mailbox);
1128 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
1130 struct inprocess *i = obj, *j = arg;
1131 if (strcmp(i->mailbox, j->mailbox)) {
1134 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
1137 static int inprocess_count(const char *context, const char *mailbox, int delta)
1139 struct inprocess *i, *arg = ast_alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
1140 arg->context = arg->mailbox + strlen(mailbox) + 1;
1141 strcpy(arg->mailbox, mailbox); /* SAFE */
1142 strcpy(arg->context, context); /* SAFE */
1143 ao2_lock(inprocess_container);
1144 if ((i = ao2_find(inprocess_container, arg, 0))) {
1145 int ret = ast_atomic_fetchadd_int(&i->count, delta);
1146 ao2_unlock(inprocess_container);
1151 ast_log(LOG_WARNING, "BUG: ref count decrement on non-existing object???\n");
1153 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
1154 ao2_unlock(inprocess_container);
1157 i->context = i->mailbox + strlen(mailbox) + 1;
1158 strcpy(i->mailbox, mailbox); /* SAFE */
1159 strcpy(i->context, context); /* SAFE */
1161 ao2_link(inprocess_container, i);
1162 ao2_unlock(inprocess_container);
1167 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
1168 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
1172 * \brief Strips control and non 7-bit clean characters from input string.
1174 * \note To map control and none 7-bit characters to a 7-bit clean characters
1175 * please use ast_str_encode_mine().
1177 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
1180 for (; *input; input++) {
1185 if (bufptr == buf + buflen - 1) {
1195 * \brief Sets default voicemail system options to a voicemail user.
1197 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
1198 * - all the globalflags
1199 * - the saydurationminfo
1203 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
1205 * - emailsubject, emailbody set to NULL
1207 static void populate_defaults(struct ast_vm_user *vmu)
1209 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
1210 vmu->passwordlocation = passwordlocation;
1211 if (saydurationminfo) {
1212 vmu->saydurationm = saydurationminfo;
1214 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
1215 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
1216 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
1217 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
1218 ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
1220 vmu->minsecs = vmminsecs;
1223 vmu->maxsecs = vmmaxsecs;
1226 vmu->maxmsg = maxmsg;
1228 if (maxdeletedmsg) {
1229 vmu->maxdeletedmsg = maxdeletedmsg;
1231 vmu->volgain = volgain;
1232 ast_free(vmu->emailsubject);
1233 vmu->emailsubject = NULL;
1234 ast_free(vmu->emailbody);
1235 vmu->emailbody = NULL;
1237 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
1238 ast_copy_string(vmu->imapserver, imapserver, sizeof(vmu->imapserver));
1239 ast_copy_string(vmu->imapport, imapport, sizeof(vmu->imapport));
1240 ast_copy_string(vmu->imapflags, imapflags, sizeof(vmu->imapflags));
1245 * \brief Sets a a specific property value.
1246 * \param vmu The voicemail user object to work with.
1247 * \param var The name of the property to be set.
1248 * \param value The value to be set to the property.
1250 * The property name must be one of the understood properties. See the source for details.
1252 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1255 if (!strcasecmp(var, "attach")) {
1256 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1257 } else if (!strcasecmp(var, "attachfmt")) {
1258 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1259 } else if (!strcasecmp(var, "serveremail")) {
1260 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1261 } else if (!strcasecmp(var, "emailbody")) {
1262 ast_free(vmu->emailbody);
1263 vmu->emailbody = ast_strdup(substitute_escapes(value));
1264 } else if (!strcasecmp(var, "emailsubject")) {
1265 ast_free(vmu->emailsubject);
1266 vmu->emailsubject = ast_strdup(substitute_escapes(value));
1267 } else if (!strcasecmp(var, "language")) {
1268 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1269 } else if (!strcasecmp(var, "tz")) {
1270 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1271 } else if (!strcasecmp(var, "locale")) {
1272 ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
1274 } else if (!strcasecmp(var, "imapuser")) {
1275 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1276 vmu->imapversion = imapversion;
1277 } else if (!strcasecmp(var, "imapserver")) {
1278 ast_copy_string(vmu->imapserver, value, sizeof(vmu->imapserver));
1279 vmu->imapversion = imapversion;
1280 } else if (!strcasecmp(var, "imapport")) {
1281 ast_copy_string(vmu->imapport, value, sizeof(vmu->imapport));
1282 vmu->imapversion = imapversion;
1283 } else if (!strcasecmp(var, "imapflags")) {
1284 ast_copy_string(vmu->imapflags, value, sizeof(vmu->imapflags));
1285 vmu->imapversion = imapversion;
1286 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1287 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1288 vmu->imapversion = imapversion;
1289 } else if (!strcasecmp(var, "imapfolder")) {
1290 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1291 vmu->imapversion = imapversion;
1292 } else if (!strcasecmp(var, "imapvmshareid")) {
1293 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1294 vmu->imapversion = imapversion;
1296 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1297 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1298 } else if (!strcasecmp(var, "saycid")){
1299 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1300 } else if (!strcasecmp(var, "sendvoicemail")){
1301 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1302 } else if (!strcasecmp(var, "review")){
1303 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1304 } else if (!strcasecmp(var, "tempgreetwarn")){
1305 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1306 } else if (!strcasecmp(var, "messagewrap")){
1307 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1308 } else if (!strcasecmp(var, "operator")) {
1309 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1310 } else if (!strcasecmp(var, "envelope")){
1311 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1312 } else if (!strcasecmp(var, "moveheard")){
1313 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1314 } else if (!strcasecmp(var, "sayduration")){
1315 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1316 } else if (!strcasecmp(var, "saydurationm")){
1317 if (sscanf(value, "%30d", &x) == 1) {
1318 vmu->saydurationm = x;
1320 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1322 } else if (!strcasecmp(var, "forcename")){
1323 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1324 } else if (!strcasecmp(var, "forcegreetings")){
1325 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1326 } else if (!strcasecmp(var, "callback")) {
1327 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1328 } else if (!strcasecmp(var, "dialout")) {
1329 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1330 } else if (!strcasecmp(var, "exitcontext")) {
1331 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1332 } else if (!strcasecmp(var, "minsecs")) {
1333 if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1336 ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1337 vmu->minsecs = vmminsecs;
1339 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1340 vmu->maxsecs = atoi(value);
1341 if (vmu->maxsecs <= 0) {
1342 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1343 vmu->maxsecs = vmmaxsecs;
1345 vmu->maxsecs = atoi(value);
1347 if (!strcasecmp(var, "maxmessage"))
1348 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1349 } else if (!strcasecmp(var, "maxmsg")) {
1350 vmu->maxmsg = atoi(value);
1351 /* Accept maxmsg=0 (Greetings only voicemail) */
1352 if (vmu->maxmsg < 0) {
1353 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1354 vmu->maxmsg = MAXMSG;
1355 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1356 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1357 vmu->maxmsg = MAXMSGLIMIT;
1359 } else if (!strcasecmp(var, "nextaftercmd")) {
1360 ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
1361 } else if (!strcasecmp(var, "backupdeleted")) {
1362 if (sscanf(value, "%30d", &x) == 1)
1363 vmu->maxdeletedmsg = x;
1364 else if (ast_true(value))
1365 vmu->maxdeletedmsg = MAXMSG;
1367 vmu->maxdeletedmsg = 0;
1369 if (vmu->maxdeletedmsg < 0) {
1370 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1371 vmu->maxdeletedmsg = MAXMSG;
1372 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1373 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1374 vmu->maxdeletedmsg = MAXMSGLIMIT;
1376 } else if (!strcasecmp(var, "volgain")) {
1377 sscanf(value, "%30lf", &vmu->volgain);
1378 } else if (!strcasecmp(var, "passwordlocation")) {
1379 if (!strcasecmp(value, "spooldir")) {
1380 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1382 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1384 } else if (!strcasecmp(var, "options")) {
1385 apply_options(vmu, value);
1389 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1391 int fds[2], pid = 0;
1393 memset(buf, 0, len);
1396 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1399 pid = ast_safe_fork(0);
1405 snprintf(buf, len, "FAILURE: Fork failed");
1409 if (read(fds[0], buf, len) < 0) {
1410 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1415 AST_DECLARE_APP_ARGS(arg,
1418 char *mycmd = ast_strdupa(command);
1421 dup2(fds[1], STDOUT_FILENO);
1423 ast_close_fds_above_n(STDOUT_FILENO);
1425 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1427 execv(arg.v[0], arg.v);
1428 printf("FAILURE: %s", strerror(errno));
1436 * \brief Check that password meets minimum required length
1437 * \param vmu The voicemail user to change the password for.
1438 * \param password The password string to check
1440 * \return zero on ok, 1 on not ok.
1442 static int check_password(struct ast_vm_user *vmu, char *password)
1444 /* check minimum length */
1445 if (strlen(password) < minpassword)
1447 /* check that password does not contain '*' character */
1448 if (!ast_strlen_zero(password) && password[0] == '*')
1450 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1451 char cmd[255], buf[255];
1453 ast_debug(1, "Verify password policies for %s\n", password);
1455 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1456 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1457 ast_debug(5, "Result: %s\n", buf);
1458 if (!strncasecmp(buf, "VALID", 5)) {
1459 ast_debug(3, "Passed password check: '%s'\n", buf);
1461 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1462 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1465 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1474 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1475 * \param vmu The voicemail user to change the password for.
1476 * \param password The new value to be set to the password for this user.
1478 * This only works if there is a realtime engine configured.
1479 * This is called from the (top level) vm_change_password.
1481 * \return zero on success, -1 on error.
1483 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1486 if (!strcmp(vmu->password, password)) {
1487 /* No change (but an update would return 0 rows updated, so we opt out here) */
1491 if (strlen(password) > 10) {
1492 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1494 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1495 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: realtime engine updated with new password\r\nPasswordSource: realtime");
1496 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1503 * \brief Destructively Parse options and apply.
1505 static void apply_options(struct ast_vm_user *vmu, const char *options)
1510 stringp = ast_strdupa(options);
1511 while ((s = strsep(&stringp, "|"))) {
1513 if ((var = strsep(&value, "=")) && value) {
1514 apply_option(vmu, var, value);
1520 * \brief Loads the options specific to a voicemail user.
1522 * This is called when a vm_user structure is being set up, such as from load_options.
1524 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1526 for (; var; var = var->next) {
1527 if (!strcasecmp(var->name, "vmsecret")) {
1528 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1529 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1530 if (ast_strlen_zero(retval->password)) {
1531 if (!ast_strlen_zero(var->value) && var->value[0] == '*') {
1532 ast_log(LOG_WARNING, "Invalid password detected for mailbox %s. The password"
1533 "\n\tmust be reset in voicemail.conf.\n", retval->mailbox);
1535 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1538 } else if (!strcasecmp(var->name, "uniqueid")) {
1539 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1540 } else if (!strcasecmp(var->name, "pager")) {
1541 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1542 } else if (!strcasecmp(var->name, "email")) {
1543 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1544 } else if (!strcasecmp(var->name, "fullname")) {
1545 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1546 } else if (!strcasecmp(var->name, "context")) {
1547 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1548 } else if (!strcasecmp(var->name, "emailsubject")) {
1549 ast_free(retval->emailsubject);
1550 retval->emailsubject = ast_strdup(substitute_escapes(var->value));
1551 } else if (!strcasecmp(var->name, "emailbody")) {
1552 ast_free(retval->emailbody);
1553 retval->emailbody = ast_strdup(substitute_escapes(var->value));
1555 } else if (!strcasecmp(var->name, "imapuser")) {
1556 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1557 retval->imapversion = imapversion;
1558 } else if (!strcasecmp(var->name, "imapserver")) {
1559 ast_copy_string(retval->imapserver, var->value, sizeof(retval->imapserver));
1560 retval->imapversion = imapversion;
1561 } else if (!strcasecmp(var->name, "imapport")) {
1562 ast_copy_string(retval->imapport, var->value, sizeof(retval->imapport));
1563 retval->imapversion = imapversion;
1564 } else if (!strcasecmp(var->name, "imapflags")) {
1565 ast_copy_string(retval->imapflags, var->value, sizeof(retval->imapflags));
1566 retval->imapversion = imapversion;
1567 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1568 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1569 retval->imapversion = imapversion;
1570 } else if (!strcasecmp(var->name, "imapfolder")) {
1571 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1572 retval->imapversion = imapversion;
1573 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1574 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1575 retval->imapversion = imapversion;
1578 apply_option(retval, var->name, var->value);
1583 * \brief Determines if a DTMF key entered is valid.
1584 * \param key The character to be compared. expects a single character. Though is capable of handling a string, this is internally copies using ast_strdupa.
1586 * Tests the character entered against the set of valid DTMF characters.
1587 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1589 static int is_valid_dtmf(const char *key)
1592 char *local_key = ast_strdupa(key);
1594 for (i = 0; i < strlen(key); ++i) {
1595 if (!strchr(VALID_DTMF, *local_key)) {
1596 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1605 * \brief Finds a voicemail user from the realtime engine.
1610 * This is called as a fall through case when the normal find_user() was not able to find a user. That is, the default it so look in the usual voicemail users file first.
1612 * \return The ast_vm_user structure for the user that was found.
1614 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1616 struct ast_variable *var;
1617 struct ast_vm_user *retval;
1619 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1621 memset(retval, 0, sizeof(*retval));
1623 populate_defaults(retval);
1625 ast_set_flag(retval, VM_ALLOCED);
1628 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1630 if (!context && ast_test_flag((&globalflags), VM_SEARCH)) {
1631 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1633 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1636 apply_options_full(retval, var);
1637 ast_variables_destroy(var);
1648 * \brief Finds a voicemail user from the users file or the realtime engine.
1653 * \return The ast_vm_user structure for the user that was found.
1655 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1657 /* This function could be made to generate one from a database, too */
1658 struct ast_vm_user *vmu = NULL, *cur;
1659 AST_LIST_LOCK(&users);
1661 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1662 context = "default";
1664 AST_LIST_TRAVERSE(&users, cur, list) {
1666 if (cur->imapversion != imapversion) {
1670 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1672 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1676 /* Make a copy, so that on a reload, we have no race */
1677 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1680 vmu->emailbody = ast_strdup(cur->emailbody);
1681 vmu->emailsubject = ast_strdup(cur->emailsubject);
1683 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1684 AST_LIST_NEXT(vmu, list) = NULL;
1687 vmu = find_user_realtime(ivm, context, mailbox);
1688 AST_LIST_UNLOCK(&users);
1693 * \brief Resets a user password to a specified password.
1698 * This does the actual change password work, called by the vm_change_password() function.
1700 * \return zero on success, -1 on error.
1702 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1704 /* This function could be made to generate one from a database, too */
1705 struct ast_vm_user *cur;
1707 AST_LIST_LOCK(&users);
1708 AST_LIST_TRAVERSE(&users, cur, list) {
1709 if ((!context || !strcasecmp(context, cur->context)) &&
1710 (!strcasecmp(mailbox, cur->mailbox)))
1714 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1717 AST_LIST_UNLOCK(&users);
1722 * \brief Check if configuration file is valid
1724 static inline int valid_config(const struct ast_config *cfg)
1726 return cfg && cfg != CONFIG_STATUS_FILEINVALID;
1730 * \brief The handler for the change password option.
1731 * \param vmu The voicemail user to work with.
1732 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1733 * This is called when a new user logs in for the first time and the option to force them to change their password is set.
1734 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1736 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1738 struct ast_config *cfg = NULL;
1739 struct ast_variable *var = NULL;
1740 struct ast_category *cat = NULL;
1741 char *category = NULL, *value = NULL, *new = NULL;
1742 const char *tmp = NULL;
1743 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1744 char secretfn[PATH_MAX] = "";
1747 if (!change_password_realtime(vmu, newpassword))
1750 /* check if we should store the secret in the spool directory next to the messages */
1751 switch (vmu->passwordlocation) {
1752 case OPT_PWLOC_SPOOLDIR:
1753 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1754 if (write_password_to_file(secretfn, newpassword) == 0) {
1755 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: secret.conf updated with new password\r\nPasswordSource: secret.conf");
1756 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1757 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1758 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1761 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1764 case OPT_PWLOC_VOICEMAILCONF:
1765 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && valid_config(cfg)) {
1766 while ((category = ast_category_browse(cfg, category))) {
1767 if (!strcasecmp(category, vmu->context)) {
1768 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1769 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1772 value = strstr(tmp, ",");
1774 new = ast_alloca(strlen(newpassword)+1);
1775 sprintf(new, "%s", newpassword);
1777 new = ast_alloca((strlen(value) + strlen(newpassword) + 1));
1778 sprintf(new, "%s%s", newpassword, value);
1780 if (!(cat = ast_category_get(cfg, category))) {
1781 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1784 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1788 /* save the results */
1790 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: voicemail.conf updated with new password\r\nPasswordSource: voicemail.conf");
1791 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1792 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1793 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1794 ast_config_destroy(cfg);
1798 ast_config_destroy(cfg);
1801 case OPT_PWLOC_USERSCONF:
1802 /* check users.conf and update the password stored for the mailbox */
1803 /* if no vmsecret entry exists create one. */
1804 if ((cfg = ast_config_load("users.conf", config_flags)) && valid_config(cfg)) {
1805 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1806 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1807 ast_debug(4, "users.conf: %s\n", category);
1808 if (!strcasecmp(category, vmu->mailbox)) {
1809 if (!ast_variable_retrieve(cfg, category, "vmsecret")) {
1810 ast_debug(3, "looks like we need to make vmsecret!\n");
1811 var = ast_variable_new("vmsecret", newpassword, "");
1815 new = ast_alloca(strlen(newpassword) + 1);
1816 sprintf(new, "%s", newpassword);
1817 if (!(cat = ast_category_get(cfg, category))) {
1818 ast_debug(4, "failed to get category!\n");
1823 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1825 ast_variable_append(cat, var);
1831 /* save the results and clean things up */
1833 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: users.conf updated with new password\r\nPasswordSource: users.conf");
1834 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1835 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1836 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1839 ast_config_destroy(cfg);
1844 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1847 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1848 ast_debug(1, "External password: %s\n",buf);
1849 if (!ast_safe_system(buf)) {
1850 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: external script updated with new password\r\nPasswordSource: external");
1851 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1852 /* Reset the password in memory, too */
1853 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1858 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1859 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1860 * \param len The length of the path string that was written out.
1865 * The path is constructed as
1866 * VM_SPOOL_DIRcontext/ext/folder
1868 * \return zero on success, -1 on error.
1870 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1872 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1876 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1877 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1878 * \param len The length of the path string that was written out.
1882 * The path is constructed as
1883 * VM_SPOOL_DIRcontext/ext/folder
1885 * \return zero on success, -1 on error.
1887 static int make_file(char *dest, const int len, const char *dir, const int num)
1889 return snprintf(dest, len, "%s/msg%04d", dir, num);
1892 /* same as mkstemp, but return a FILE * */
1893 static FILE *vm_mkftemp(char *template)
1896 int pfd = mkstemp(template);
1897 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1899 p = fdopen(pfd, "w+");
1908 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1909 * \param dest String. base directory.
1910 * \param len Length of dest.
1911 * \param context String. Ignored if is null or empty string.
1912 * \param ext String. Ignored if is null or empty string.
1913 * \param folder String. Ignored if is null or empty string.
1914 * \return -1 on failure, 0 on success.
1916 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1918 mode_t mode = VOICEMAIL_DIR_MODE;
1921 make_dir(dest, len, context, ext, folder);
1922 if ((res = ast_mkdir(dest, mode))) {
1923 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1929 static const char *mbox(struct ast_vm_user *vmu, int id)
1932 if (vmu && id == 0) {
1933 return vmu->imapfolder;
1936 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1939 static const char *vm_index_to_foldername(int id)
1941 return mbox(NULL, id);
1945 static int get_folder_by_name(const char *name)
1949 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1950 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1958 static void free_user(struct ast_vm_user *vmu)
1960 if (ast_test_flag(vmu, VM_ALLOCED)) {
1962 ast_free(vmu->emailbody);
1963 vmu->emailbody = NULL;
1965 ast_free(vmu->emailsubject);
1966 vmu->emailsubject = NULL;
1972 static int vm_allocate_dh(struct vm_state *vms, struct ast_vm_user *vmu, int count_msg) {
1974 int arraysize = (vmu->maxmsg > count_msg ? vmu->maxmsg : count_msg);
1976 /* remove old allocation */
1978 ast_free(vms->deleted);
1979 vms->deleted = NULL;
1982 ast_free(vms->heard);
1985 vms->dh_arraysize = 0;
1987 if (arraysize > 0) {
1988 if (!(vms->deleted = ast_calloc(arraysize, sizeof(int)))) {
1991 if (!(vms->heard = ast_calloc(arraysize, sizeof(int)))) {
1992 ast_free(vms->deleted);
1993 vms->deleted = NULL;
1996 vms->dh_arraysize = arraysize;
2002 /* All IMAP-specific functions should go in this block. This
2003 * keeps them from being spread out all over the code */
2005 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
2008 struct vm_state *vms;
2009 unsigned long messageNum;
2011 /* If greetings aren't stored in IMAP, just delete the file */
2012 if (msgnum < 0 && !imapgreetings) {
2013 ast_filedelete(file, NULL);
2017 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2018 ast_log(LOG_WARNING, "Couldn't find a vm_state for mailbox %s. Unable to set \\DELETED flag for message %d\n", vmu->mailbox, msgnum);
2023 imap_delete_old_greeting(file, vms);
2027 /* find real message number based on msgnum */
2028 /* this may be an index into vms->msgArray based on the msgnum. */
2029 messageNum = vms->msgArray[msgnum];
2030 if (messageNum == 0) {
2031 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
2034 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
2035 /* delete message */
2036 snprintf (arg, sizeof(arg), "%lu", messageNum);
2037 ast_mutex_lock(&vms->lock);
2038 mail_setflag (vms->mailstream, arg, "\\DELETED");
2039 mail_expunge(vms->mailstream);
2040 ast_mutex_unlock(&vms->lock);
2043 static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder)
2045 struct ast_channel *chan;
2049 struct vm_state *vms;
2050 const char *duration_str;
2054 * First, get things initially set up. If any of this fails, then
2055 * back out before doing anything substantial
2057 vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0);
2062 if (open_mailbox(vms, vmu, folder)) {
2066 chan = ast_dummy_channel_alloc();
2068 close_mailbox(vms, vmu);
2073 * We need to make sure the new message we save has the same
2074 * callerid, flag, and duration as the original message
2076 cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
2078 if (!ast_strlen_zero(cid)) {
2079 ast_callerid_parse(cid, &cid_name, &cid_num);
2080 ast_party_caller_init(ast_channel_caller(chan));
2081 if (!ast_strlen_zero(cid_name)) {
2082 ast_channel_caller(chan)->id.name.valid = 1;
2083 ast_channel_caller(chan)->id.name.str = ast_strdup(cid_name);
2085 if (!ast_strlen_zero(cid_num)) {
2086 ast_channel_caller(chan)->id.number.valid = 1;
2087 ast_channel_caller(chan)->id.number.str = ast_strdup(cid_num);
2091 duration_str = ast_variable_retrieve(msg_cfg, "message", "duration");
2093 if (!ast_strlen_zero(duration_str)) {
2094 sscanf(duration_str, "%30d", &duration);
2098 * IMAP messages cannot be altered once delivered. So we have to delete the
2099 * current message and then re-add it with the updated message ID.
2101 * Furthermore, there currently is no atomic way to create a new message and to
2102 * store it in an arbitrary folder. So we have to save it to the INBOX and then
2103 * move to the appropriate folder.
2105 if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts,
2106 duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) {
2107 if (folder != NEW_FOLDER) {
2108 save_to_folder(vmu, vms, msgnum, folder, NULL, 1);
2110 vm_imap_delete(dir, msgnum, vmu);
2112 close_mailbox(vms, vmu);
2113 ast_channel_unref(chan);
2116 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
2118 struct vm_state *vms_p;
2119 char *file, *filename;
2124 /* This function is only used for retrieval of IMAP greetings
2125 * regular messages are not retrieved this way, nor are greetings
2126 * if they are stored locally*/
2127 if (msgnum > -1 || !imapgreetings) {
2130 file = strrchr(ast_strdupa(dir), '/');
2134 ast_debug(1, "Failed to procure file name from directory passed.\n");
2139 /* check if someone is accessing this box right now... */
2140 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
2141 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2142 /* Unlike when retrieving a message, it is reasonable not to be able to find a
2143 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
2144 * that's all we need to do.
2146 if (!(vms_p = create_vm_state_from_user(vmu))) {
2147 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
2152 /* Greetings will never have a prepended message */
2153 *vms_p->introfn = '\0';
2155 ast_mutex_lock(&vms_p->lock);
2156 if (init_mailstream(vms_p, GREETINGS_FOLDER) || !vms_p->mailstream) {
2157 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL or can't init_mailstream\n");
2158 ast_mutex_unlock(&vms_p->lock);
2162 /*XXX Yuck, this could probably be done a lot better */
2163 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
2164 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
2165 /* We have the body, now we extract the file name of the first attachment. */
2166 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
2167 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
2169 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
2170 ast_mutex_unlock(&vms_p->lock);
2173 filename = strsep(&attachment, ".");
2174 if (!strcmp(filename, file)) {
2175 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
2176 vms_p->msgArray[vms_p->curmsg] = i + 1;
2177 save_body(body, vms_p, "2", attachment, 0);
2178 ast_mutex_unlock(&vms_p->lock);
2182 ast_mutex_unlock(&vms_p->lock);
2187 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
2190 char *header_content;
2191 char *attachedfilefmt;
2193 struct vm_state *vms;
2194 char text_file[PATH_MAX];
2195 FILE *text_file_ptr;
2197 struct ast_vm_user *vmu;
2199 if (!(vmu = find_user(NULL, context, mailbox))) {
2200 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
2205 if (imapgreetings) {
2206 res = imap_retrieve_greeting(dir, msgnum, vmu);
2214 /* Before anything can happen, we need a vm_state so that we can
2215 * actually access the imap server through the vms->mailstream
2217 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2218 /* This should not happen. If it does, then I guess we'd
2219 * need to create the vm_state, extract which mailbox to
2220 * open, and then set up the msgArray so that the correct
2221 * IMAP message could be accessed. If I have seen correctly
2222 * though, the vms should be obtainable from the vmstates list
2223 * and should have its msgArray properly set up.
2225 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
2230 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
2231 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
2233 /* Don't try to retrieve a message from IMAP if it already is on the file system */
2234 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
2239 ast_debug(3, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
2240 if (vms->msgArray[msgnum] == 0) {
2241 ast_log(LOG_WARNING, "Trying to access unknown message\n");
2246 /* This will only work for new messages... */
2247 ast_mutex_lock(&vms->lock);
2248 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
2249 ast_mutex_unlock(&vms->lock);
2250 /* empty string means no valid header */
2251 if (ast_strlen_zero(header_content)) {
2252 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
2257 ast_mutex_lock(&vms->lock);
2258 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
2259 ast_mutex_unlock(&vms->lock);
2261 /* We have the body, now we extract the file name of the first attachment. */
2262 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
2263 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
2265 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
2270 /* Find the format of the attached file */
2272 strsep(&attachedfilefmt, ".");
2273 if (!attachedfilefmt) {
2274 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
2279 save_body(body, vms, "2", attachedfilefmt, 0);
2280 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
2281 *vms->introfn = '\0';
2284 /* Get info from headers!! */
2285 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
2287 if (!(text_file_ptr = fopen(text_file, "w"))) {
2288 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
2291 fprintf(text_file_ptr, "%s\n", "[message]");
2293 if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) {
2294 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
2296 if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) {
2297 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
2299 if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) {
2300 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
2302 if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) {
2303 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
2305 if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) {
2306 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
2308 if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) {
2309 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
2311 if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) {
2312 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
2314 if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) {
2315 fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, ""));
2317 fclose(text_file_ptr);
2324 static int folder_int(const char *folder)
2326 /*assume a NULL folder means INBOX*/
2330 if (!strcasecmp(folder, imapfolder)) {
2332 } else if (!strcasecmp(folder, "Old")) {
2334 } else if (!strcasecmp(folder, "Work")) {
2336 } else if (!strcasecmp(folder, "Family")) {
2338 } else if (!strcasecmp(folder, "Friends")) {
2340 } else if (!strcasecmp(folder, "Cust1")) {
2342 } else if (!strcasecmp(folder, "Cust2")) {
2344 } else if (!strcasecmp(folder, "Cust3")) {
2346 } else if (!strcasecmp(folder, "Cust4")) {
2348 } else if (!strcasecmp(folder, "Cust5")) {
2350 } else if (!strcasecmp(folder, "Urgent")) {
2352 } else { /*assume they meant INBOX if folder is not found otherwise*/
2357 static int __messagecount(const char *context, const char *mailbox, const char *folder)
2362 struct ast_vm_user *vmu, vmus;
2363 struct vm_state *vms_p;
2365 int fold = folder_int(folder);
2368 /* If URGENT, then look at INBOX */
2374 if (ast_strlen_zero(mailbox))
2377 /* We have to get the user before we can open the stream! */
2378 vmu = find_user(&vmus, context, mailbox);
2380 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
2383 /* No IMAP account available */
2384 if (vmu->imapuser[0] == '\0') {
2385 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2390 /* No IMAP account available */
2391 if (vmu->imapuser[0] == '\0') {
2392 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2397 /* check if someone is accessing this box right now... */
2398 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2400 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
2403 ast_debug(3, "Returning before search - user is logged in\n");
2404 if (fold == 0) { /* INBOX */
2405 return urgent ? vms_p->urgentmessages : vms_p->newmessages;
2407 if (fold == 1) { /* Old messages */
2408 return vms_p->oldmessages;
2412 /* add one if not there... */
2413 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2415 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2419 vms_p = create_vm_state_from_user(vmu);
2421 ret = init_mailstream(vms_p, fold);
2422 if (!vms_p->mailstream) {
2423 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2427 ast_mutex_lock(&vms_p->lock);
2428 pgm = mail_newsearchpgm ();
2429 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2430 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2432 if (fold != OLD_FOLDER) {
2436 /* In the special case where fold is 1 (old messages) we have to do things a bit
2437 * differently. Old messages are stored in the INBOX but are marked as "seen"
2443 /* look for urgent messages */
2444 if (fold == NEW_FOLDER) {
2456 vms_p->vmArrayIndex = 0;
2457 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2458 if (fold == 0 && urgent == 0)
2459 vms_p->newmessages = vms_p->vmArrayIndex;
2461 vms_p->oldmessages = vms_p->vmArrayIndex;
2462 if (fold == 0 && urgent == 1)
2463 vms_p->urgentmessages = vms_p->vmArrayIndex;
2464 /*Freeing the searchpgm also frees the searchhdr*/
2465 mail_free_searchpgm(&pgm);
2466 ast_mutex_unlock(&vms_p->lock);
2468 return vms_p->vmArrayIndex;
2470 ast_mutex_lock(&vms_p->lock);
2471 mail_ping(vms_p->mailstream);
2472 ast_mutex_unlock(&vms_p->lock);
2477 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2479 /* Check if mailbox is full */
2480 check_quota(vms, vmu->imapfolder);
2481 if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2482 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2484 ast_play_and_wait(chan, "vm-mailboxfull");
2489 /* Check if we have exceeded maxmsg */
2490 ast_debug(3, "Checking message number quota: mailbox has %d messages, maximum is set to %d, current messages %d\n", msgnum, vmu->maxmsg, inprocess_count(vmu->mailbox, vmu->context, 0));
2491 if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
2492 ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
2494 ast_play_and_wait(chan, "vm-mailboxfull");
2495 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2504 * \brief Gets the number of messages that exist in a mailbox folder.
2509 * This method is used when IMAP backend is used.
2510 * \return The number of messages in this mailbox folder (zero or more).
2512 static int messagecount(const char *context, const char *mailbox, const char *folder)
2514 if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2515 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2517 return __messagecount(context, mailbox, folder);
2521 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag, const char *msg_id)
2523 char *myserveremail = serveremail;
2525 char introfn[PATH_MAX];
2529 char tmp[80] = "/tmp/astmail-XXXXXX";
2534 int ret; /* for better error checking */
2535 char *imap_flags = NIL;
2536 int msgcount = (messagecount(vmu->context, vmu->mailbox, "INBOX") + messagecount(vmu->context, vmu->mailbox, "Old"));
2537 int box = NEW_FOLDER;
2539 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2541 if(!imapgreetings) {
2544 box = GREETINGS_FOLDER;
2548 if (imap_check_limits(chan, vms, vmu, msgcount)) {
2552 /* Set urgent flag for IMAP message */
2553 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2554 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2555 imap_flags = "\\FLAGGED";
2558 /* Attach only the first format */
2559 fmt = ast_strdupa(fmt);
2561 strsep(&stringp, "|");
2563 if (!ast_strlen_zero(vmu->serveremail))
2564 myserveremail = vmu->serveremail;
2567 make_file(fn, sizeof(fn), dir, msgnum);
2569 ast_copy_string (fn, dir, sizeof(fn));
2571 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2572 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2576 if (ast_strlen_zero(vmu->email)) {
2577 /* We need the vmu->email to be set when we call make_email_file, but
2578 * if we keep it set, a duplicate e-mail will be created. So at the end
2579 * of this function, we will revert back to an empty string if tempcopy
2582 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2586 if (!strcmp(fmt, "wav49"))
2588 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2590 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2592 if (!(p = vm_mkftemp(tmp))) {
2593 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2595 *(vmu->email) = '\0';
2599 if (msgnum < 0 && imapgreetings) {
2600 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2601 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2604 imap_delete_old_greeting(fn, vms);
2607 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
2608 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2609 S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
2610 fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id);
2611 /* read mail file to memory */
2614 if (!(buf = ast_malloc(len + 1))) {
2615 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2618 *(vmu->email) = '\0';
2621 if (fread(buf, len, 1, p) < len) {
2623 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2627 ((char *) buf)[len] = '\0';
2628 INIT(&str, mail_string, buf, len);
2629 ret = init_mailstream(vms, box);
2631 imap_mailbox_name(mailbox, sizeof(mailbox), vms, box, 1);
2632 ast_mutex_lock(&vms->lock);
2633 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2634 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2635 ast_mutex_unlock(&vms->lock);
2640 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2646 ast_debug(3, "%s stored\n", fn);
2649 *(vmu->email) = '\0';
2650 inprocess_count(vmu->mailbox, vmu->context, -1);
2656 * \brief Gets the number of messages that exist in the inbox folder.
2657 * \param mailbox_context
2658 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2659 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2660 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2662 * This method is used when IMAP backend is used.
2663 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2665 * \return zero on success, -1 on error.
2668 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2670 char tmp[PATH_MAX] = "";
2682 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2683 /* If no mailbox, return immediately */
2684 if (ast_strlen_zero(mailbox_context))
2687 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2688 context = strchr(tmp, '@');
2689 if (strchr(mailbox_context, ',')) {
2690 int tmpnew, tmpold, tmpurgent;
2691 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2693 while ((cur = strsep(&mb, ", "))) {
2694 if (!ast_strlen_zero(cur)) {
2695 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2703 *urgentmsgs += tmpurgent;
2714 context = "default";
2715 mailboxnc = (char *) mailbox_context;
2719 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2721 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2724 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2731 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2736 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2744 * \brief Determines if the given folder has messages.
2745 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2746 * \param folder the folder to look in
2748 * This function is used when the mailbox is stored in an IMAP back end.
2749 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2750 * \return 1 if the folder has one or more messages. zero otherwise.
2753 static int has_voicemail(const char *mailbox, const char *folder)
2755 char tmp[256], *tmp2, *box, *context;
2756 ast_copy_string(tmp, mailbox, sizeof(tmp));
2758 if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2759 while ((box = strsep(&tmp2, ",&"))) {
2760 if (!ast_strlen_zero(box)) {
2761 if (has_voicemail(box, folder)) {
2767 if ((context = strchr(tmp, '@'))) {
2770 context = "default";
2772 return __messagecount(context, tmp, folder) ? 1 : 0;
2776 * \brief Copies a message from one mailbox to another.
2786 * This works with IMAP storage based mailboxes.
2788 * \return zero on success, -1 on error.
2790 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag, const char *dest_folder)
2792 struct vm_state *sendvms = NULL;
2793 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2794 if (msgnum >= recip->maxmsg) {
2795 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2798 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2799 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2802 if (!get_vm_state_by_imapuser(recip->imapuser, 0)) {
2803 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2806 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2807 ast_mutex_lock(&sendvms->lock);
2808 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2809 ast_mutex_unlock(&sendvms->lock);
2812 ast_mutex_unlock(&sendvms->lock);
2813 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2817 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2819 char tmp[256], *t = tmp;
2820 size_t left = sizeof(tmp);
2822 if (box == OLD_FOLDER) {
2823 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2825 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2828 if (box == NEW_FOLDER) {
2829 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2831 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2834 /* Build up server information */
2835 ast_build_string(&t, &left, "{%s:%s/imap", S_OR(vms->imapserver, imapserver), S_OR(vms->imapport, imapport));
2837 /* Add authentication user if present */
2838 if (!ast_strlen_zero(authuser))
2839 ast_build_string(&t, &left, "/authuser=%s", authuser);
2841 /* Add flags if present */
2842 if (!ast_strlen_zero(imapflags) || !(ast_strlen_zero(vms->imapflags))) {
2843 ast_build_string(&t, &left, "/%s", S_OR(vms->imapflags, imapflags));
2846 /* End with username */
2848 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2850 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2852 if (box == NEW_FOLDER || box == OLD_FOLDER)
2853 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2854 else if (box == GREETINGS_FOLDER)
2855 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2856 else { /* Other folders such as Friends, Family, etc... */
2857 if (!ast_strlen_zero(imapparentfolder)) {
2858 /* imapparentfolder would typically be set to INBOX */
2859 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2861 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2866 static int init_mailstream(struct vm_state *vms, int box)
2868 MAILSTREAM *stream = NIL;
2873 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2876 ast_debug(3, "vm_state user is:%s\n", vms->imapuser);
2877 if (vms->mailstream == NIL || !vms->mailstream) {
2878 ast_debug(1, "mailstream not set.\n");
2880 stream = vms->mailstream;
2882 /* debug = T; user wants protocol telemetry? */
2883 debug = NIL; /* NO protocol telemetry? */
2885 if (delimiter == '\0') { /* did not probe the server yet */
2887 #ifdef USE_SYSTEM_IMAP
2888 #include <imap/linkage.c>
2889 #elif defined(USE_SYSTEM_CCLIENT)
2890 #include <c-client/linkage.c>
2892 #include "linkage.c"
2894 /* Connect to INBOX first to get folders delimiter */
2895 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2896 ast_mutex_lock(&vms->lock);
2897 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2898 ast_mutex_unlock(&vms->lock);
2899 if (stream == NIL) {
2900 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2903 get_mailbox_delimiter(vms, stream);
2904 /* update delimiter in imapfolder */
2905 for (cp = vms->imapfolder; *cp; cp++)
2909 /* Now connect to the target folder */
2910 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2911 ast_debug(3, "Before mail_open, server: %s, box:%d\n", tmp, box);
2912 ast_mutex_lock(&vms->lock);
2913 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2914 ast_mutex_unlock(&vms->lock);
2915 if (vms->mailstream == NIL) {
2922 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2928 /* If Urgent, then look at INBOX */
2934 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2935 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2936 ast_copy_string(vms->imapserver, vmu->imapserver, sizeof(vms->imapserver));
2937 ast_copy_string(vms->imapport, vmu->imapport, sizeof(vms->imapport));
2938 ast_copy_string(vms->imapflags, vmu->imapflags, sizeof(vms->imapflags));
2939 vms->imapversion = vmu->imapversion;
2940 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2942 if (init_mailstream(vms, box) || !vms->mailstream) {
2943 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2947 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2951 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2952 check_quota(vms, (char *) mbox(vmu, box));
2955 ast_mutex_lock(&vms->lock);
2956 pgm = mail_newsearchpgm();
2958 /* Check IMAP folder for Asterisk messages only... */
2959 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2960 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2965 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2966 if (box == NEW_FOLDER && urgent == 1) {
2971 } else if (box == NEW_FOLDER && urgent == 0) {
2976 } else if (box == OLD_FOLDER) {
2981 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2983 vms->vmArrayIndex = 0;
2984 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2985 vms->lastmsg = vms->vmArrayIndex - 1;
2986 mail_free_searchpgm(&pgm);
2987 /* Since IMAP storage actually stores both old and new messages in the same IMAP folder,
2988 * ensure to allocate enough space to account for all of them. Warn if old messages
2989 * have not been checked first as that is required.
2991 if (box == 0 && !vms->dh_arraysize) {
2992 ast_log(LOG_WARNING, "The code expects the old messages to be checked first, fix the code.\n");
2994 if (vm_allocate_dh(vms, vmu, box == 0 ? vms->vmArrayIndex + vms->oldmessages : vms->lastmsg)) {
2995 ast_mutex_unlock(&vms->lock);
2999 ast_mutex_unlock(&vms->lock);
3003 static void write_file(char *filename, char *buffer, unsigned long len)
3007 output = fopen (filename, "w");
3008 if (fwrite(buffer, len, 1, output) != 1) {
3009 if (ferror(output)) {
3010 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
3016 static void update_messages_by_imapuser(const char *user, unsigned long number)
3018 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
3020 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
3024 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
3025 vms->msgArray[vms->vmArrayIndex++] = number;
3028 void mm_searched(MAILSTREAM *stream, unsigned long number)
3030 char *mailbox = stream->mailbox, buf[1024] = "", *user;
3032 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
3035 update_messages_by_imapuser(user, number);
3038 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
3040 struct ast_variable *var;
3041 struct ast_vm_user *vmu;
3043 vmu = ast_calloc(1, sizeof *vmu);
3047 populate_defaults(vmu);
3048 ast_set_flag(vmu, VM_ALLOCED);
3050 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
3052 apply_options_full(vmu, var);
3053 ast_variables_destroy(var);