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/event.h"
135 #include "asterisk/taskprocessor.h"
136 #include "asterisk/test.h"
139 #include "asterisk/res_odbc.h"
143 #include "asterisk/threadstorage.h"
147 <application name="VoiceMail" language="en_US">
149 Leave a Voicemail message.
152 <parameter name="mailboxs" argsep="&" required="true">
153 <argument name="mailbox1" argsep="@" required="true">
154 <argument name="mailbox" required="true" />
155 <argument name="context" />
157 <argument name="mailbox2" argsep="@" multiple="true">
158 <argument name="mailbox" required="true" />
159 <argument name="context" />
162 <parameter name="options">
165 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
168 <argument name="c" />
169 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
170 if played during the greeting. Context defaults to the current context.</para>
173 <argument name="#" required="true" />
174 <para>Use the specified amount of gain when recording the voicemail
175 message. The units are whole-number decibels (dB). Only works on supported
176 technologies, which is DAHDI only.</para>
179 <para>Skip the playback of instructions for leaving a message to the
180 calling party.</para>
183 <para>Play the <literal>unavailable</literal> greeting.</para>
186 <para>Mark message as <literal>URGENT</literal>.</para>
189 <para>Mark message as <literal>PRIORITY</literal>.</para>
195 <para>This application allows the calling party to leave a message for the specified
196 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
197 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
199 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
202 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
205 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
208 <para>This application will set the following channel variable upon completion:</para>
210 <variable name="VMSTATUS">
211 <para>This indicates the status of the execution of the VoiceMail application.</para>
212 <value name="SUCCESS" />
213 <value name="USEREXIT" />
214 <value name="FAILED" />
219 <ref type="application">VoiceMailMain</ref>
222 <application name="VoiceMailMain" language="en_US">
224 Check Voicemail messages.
227 <parameter name="mailbox" required="true" argsep="@">
228 <argument name="mailbox" />
229 <argument name="context" />
231 <parameter name="options">
234 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
235 the mailbox that is entered by the caller.</para>
238 <argument name="#" required="true" />
239 <para>Use the specified amount of gain when recording a voicemail message.
240 The units are whole-number decibels (dB).</para>
243 <para>Skip checking the passcode for the mailbox.</para>
246 <argument name="folder" required="true" />
247 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
248 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
250 <enum name="0"><para>INBOX</para></enum>
251 <enum name="1"><para>Old</para></enum>
252 <enum name="2"><para>Work</para></enum>
253 <enum name="3"><para>Family</para></enum>
254 <enum name="4"><para>Friends</para></enum>
255 <enum name="5"><para>Cust1</para></enum>
256 <enum name="6"><para>Cust2</para></enum>
257 <enum name="7"><para>Cust3</para></enum>
258 <enum name="8"><para>Cust4</para></enum>
259 <enum name="9"><para>Cust5</para></enum>
266 <para>This application allows the calling party to check voicemail messages. A specific
267 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
268 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
269 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
270 <literal>default</literal> context will be used.</para>
271 <para>The VoiceMailMain application will exit if the following DTMF digit is entered as Mailbox
272 or Password, and the extension exists:</para>
275 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
280 <ref type="application">VoiceMail</ref>
283 <application name="MailboxExists" language="en_US">
285 Check to see if Voicemail mailbox exists.
288 <parameter name="mailbox" required="true" argsep="@">
289 <argument name="mailbox" required="true" />
290 <argument name="context" />
292 <parameter name="options">
293 <para>None options.</para>
297 <note><para>DEPRECATED. Use VM_INFO(mailbox[@context],exists) instead.</para></note>
298 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
299 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
301 <para>This application will set the following channel variable upon completion:</para>
303 <variable name="VMBOXEXISTSSTATUS">
304 <para>This will contain the status of the execution of the MailboxExists application.
305 Possible values include:</para>
306 <value name="SUCCESS" />
307 <value name="FAILED" />
312 <ref type="function">VM_INFO</ref>
315 <application name="VMAuthenticate" language="en_US">
317 Authenticate with Voicemail passwords.
320 <parameter name="mailbox" required="true" argsep="@">
321 <argument name="mailbox" />
322 <argument name="context" />
324 <parameter name="options">
327 <para>Skip playing the initial prompts.</para>
333 <para>This application behaves the same way as the Authenticate application, but the passwords
334 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
335 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
336 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
338 <para>The VMAuthenticate application will exit if the following DTMF digit is entered as Mailbox
339 or Password, and the extension exists:</para>
342 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
347 <application name="VoiceMailPlayMsg" language="en_US">
349 Play a single voice mail msg from a mailbox by msg id.
352 <parameter name="mailbox" required="true" argsep="@">
353 <argument name="mailbox" />
354 <argument name="context" />
356 <parameter name="msg_id" required="true">
357 <para>The msg id of the msg to play back. </para>
361 <para>This application sets the following channel variable upon completion:</para>
363 <variable name="VOICEMAIL_PLAYBACKSTATUS">
364 <para>The status of the playback attempt as a text string.</para>
365 <value name="SUCCESS"/>
366 <value name="FAILED"/>
371 <application name="VMSayName" language="en_US">
373 Play the name of a voicemail user
376 <parameter name="mailbox" required="true" argsep="@">
377 <argument name="mailbox" />
378 <argument name="context" />
382 <para>This application will say the recorded name of the voicemail user specified as the
383 argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
386 <function name="MAILBOX_EXISTS" language="en_US">
388 Tell if a mailbox is configured.
391 <parameter name="mailbox" required="true" />
392 <parameter name="context" />
395 <note><para>DEPRECATED. Use VM_INFO(mailbox[@context],exists) instead.</para></note>
396 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
397 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
401 <ref type="function">VM_INFO</ref>
404 <function name="VM_INFO" language="en_US">
406 Returns the selected attribute from a mailbox.
409 <parameter name="mailbox" argsep="@" required="true">
410 <argument name="mailbox" required="true" />
411 <argument name="context" />
413 <parameter name="attribute" required="true">
415 <option name="count">
416 <para>Count of messages in specified <replaceable>folder</replaceable>.
417 If <replaceable>folder</replaceable> is not specified, defaults to <literal>INBOX</literal>.</para>
419 <option name="email">
420 <para>E-mail address associated with the mailbox.</para>
422 <option name="exists">
423 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.</para>
425 <option name="fullname">
426 <para>Full name associated with the mailbox.</para>
428 <option name="language">
429 <para>Mailbox language if overridden, otherwise the language of the channel.</para>
431 <option name="locale">
432 <para>Mailbox locale if overridden, otherwise global locale.</para>
434 <option name="pager">
435 <para>Pager e-mail address associated with the mailbox.</para>
437 <option name="password">
438 <para>Mailbox access password.</para>
441 <para>Mailbox timezone if overridden, otherwise global timezone</para>
445 <parameter name="folder" required="false">
446 <para>If not specified, <literal>INBOX</literal> is assumed.</para>
450 <para>Returns the selected attribute from the specified <replaceable>mailbox</replaceable>.
451 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
452 context. Where the <replaceable>folder</replaceable> can be specified, common folders
453 include <literal>INBOX</literal>, <literal>Old</literal>, <literal>Work</literal>,
454 <literal>Family</literal> and <literal>Friends</literal>.</para>
457 <manager name="VoicemailUsersList" language="en_US">
459 List All Voicemail User Information.
462 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
467 <manager name="VoicemailRefresh" language="en_US">
469 Tell Asterisk to poll mailboxes for a change
472 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
473 <parameter name="Context" />
474 <parameter name="Mailbox" />
477 <para>Normally, MWI indicators are only sent when Asterisk itself
478 changes a mailbox. With external programs that modify the content
479 of a mailbox from outside the application, an option exists called
480 <literal>pollmailboxes</literal> that will cause voicemail to
481 continually scan all mailboxes on a system for changes. This can
482 cause a large amount of load on a system. This command allows
483 external applications to signal when a particular mailbox has
484 changed, thus permitting external applications to modify mailboxes
485 and MWI to work without introducing considerable CPU load.</para>
486 <para>If <replaceable>Context</replaceable> is not specified, all
487 mailboxes on the system will be polled for changes. If
488 <replaceable>Context</replaceable> is specified, but
489 <replaceable>Mailbox</replaceable> is omitted, then all mailboxes
490 within <replaceable>Context</replaceable> will be polled.
491 Otherwise, only a single mailbox will be polled for changes.</para>
497 static char imapserver[48];
498 static char imapport[8];
499 static char imapflags[128];
500 static char imapfolder[64];
501 static char imapparentfolder[64] = "\0";
502 static char greetingfolder[64];
503 static char authuser[32];
504 static char authpassword[42];
505 static int imapversion = 1;
507 static int expungeonhangup = 1;
508 static int imapgreetings = 0;
509 static char delimiter = '\0';
514 AST_THREADSTORAGE(ts_vmstate);
516 /* Forward declarations for IMAP */
517 static int init_mailstream(struct vm_state *vms, int box);
518 static void write_file(char *filename, char *buffer, unsigned long len);
519 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
520 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
521 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
522 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
523 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
524 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
525 static void vmstate_insert(struct vm_state *vms);
526 static void vmstate_delete(struct vm_state *vms);
527 static void set_update(MAILSTREAM * stream);
528 static void init_vm_state(struct vm_state *vms);
529 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
530 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream);
531 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
532 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
533 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);
534 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);
535 static void update_messages_by_imapuser(const char *user, unsigned long number);
536 static int vm_delete(char *file);
538 static int imap_remove_file (char *dir, int msgnum);
539 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
540 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
541 static void check_quota(struct vm_state *vms, char *mailbox);
542 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
544 struct vm_state *vms;
545 AST_LIST_ENTRY(vmstate) list;
548 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
552 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
554 #define COMMAND_TIMEOUT 5000
555 /* Don't modify these here; set your umask at runtime instead */
556 #define VOICEMAIL_DIR_MODE 0777
557 #define VOICEMAIL_FILE_MODE 0666
558 #define CHUNKSIZE 65536
560 #define VOICEMAIL_CONFIG "voicemail.conf"
561 #define ASTERISK_USERNAME "asterisk"
563 /* Define fast-forward, pause, restart, and reverse keys
564 * while listening to a voicemail message - these are
565 * strings, not characters */
566 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
567 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
568 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
569 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
570 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
571 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
573 /* Default mail command to mail voicemail. Change it with the
574 * mailcmd= command in voicemail.conf */
575 #define SENDMAIL "/usr/sbin/sendmail -t"
577 #define INTRO "vm-intro"
580 #define MAXMSGLIMIT 9999
582 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
584 #define BASELINELEN 72
585 #define BASEMAXINLINE 256
592 #define MAX_DATETIME_FORMAT 512
593 #define MAX_NUM_CID_CONTEXTS 10
595 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
596 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
597 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
598 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
599 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
600 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
601 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
602 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
603 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
604 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
605 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
606 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
607 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
608 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
609 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
610 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
611 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
612 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
613 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
614 #define ERROR_LOCK_PATH -100
615 #define OPERATOR_EXIT 300
626 enum vm_option_flags {
627 OPT_SILENT = (1 << 0),
628 OPT_BUSY_GREETING = (1 << 1),
629 OPT_UNAVAIL_GREETING = (1 << 2),
630 OPT_RECORDGAIN = (1 << 3),
631 OPT_PREPEND_MAILBOX = (1 << 4),
632 OPT_AUTOPLAY = (1 << 6),
633 OPT_DTMFEXIT = (1 << 7),
634 OPT_MESSAGE_Urgent = (1 << 8),
635 OPT_MESSAGE_PRIORITY = (1 << 9)
638 enum vm_option_args {
639 OPT_ARG_RECORDGAIN = 0,
640 OPT_ARG_PLAYFOLDER = 1,
641 OPT_ARG_DTMFEXIT = 2,
642 /* This *must* be the last value in this enum! */
643 OPT_ARG_ARRAY_SIZE = 3,
646 enum vm_passwordlocation {
647 OPT_PWLOC_VOICEMAILCONF = 0,
648 OPT_PWLOC_SPOOLDIR = 1,
649 OPT_PWLOC_USERSCONF = 2,
652 AST_APP_OPTIONS(vm_app_options, {
653 AST_APP_OPTION('s', OPT_SILENT),
654 AST_APP_OPTION('b', OPT_BUSY_GREETING),
655 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
656 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
657 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
658 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
659 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
660 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
661 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
664 static const char * const mailbox_folders[] = {
683 static int load_config(int reload);
684 #ifdef TEST_FRAMEWORK
685 static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg);
687 static int actual_load_config(int reload, struct ast_config *cfg, struct ast_config *ucfg);
689 /*! \page vmlang Voicemail Language Syntaxes Supported
691 \par Syntaxes supported, not really language codes.
698 \arg \b pt - Portuguese
699 \arg \b pt_BR - Portuguese (Brazil)
701 \arg \b no - Norwegian
703 \arg \b tw - Chinese (Taiwan)
704 \arg \b ua - Ukrainian
706 German requires the following additional soundfile:
707 \arg \b 1F einE (feminine)
709 Spanish requires the following additional soundfile:
710 \arg \b 1M un (masculine)
712 Dutch, Portuguese & Spanish require the following additional soundfiles:
713 \arg \b vm-INBOXs singular of 'new'
714 \arg \b vm-Olds singular of 'old/heard/read'
717 \arg \b vm-INBOX nieuwe (nl)
718 \arg \b vm-Old oude (nl)
721 \arg \b vm-new-a 'new', feminine singular accusative
722 \arg \b vm-new-e 'new', feminine plural accusative
723 \arg \b vm-new-ych 'new', feminine plural genitive
724 \arg \b vm-old-a 'old', feminine singular accusative
725 \arg \b vm-old-e 'old', feminine plural accusative
726 \arg \b vm-old-ych 'old', feminine plural genitive
727 \arg \b digits/1-a 'one', not always same as 'digits/1'
728 \arg \b digits/2-ie 'two', not always same as 'digits/2'
731 \arg \b vm-nytt singular of 'new'
732 \arg \b vm-nya plural of 'new'
733 \arg \b vm-gammalt singular of 'old'
734 \arg \b vm-gamla plural of 'old'
735 \arg \b digits/ett 'one', not always same as 'digits/1'
738 \arg \b vm-ny singular of 'new'
739 \arg \b vm-nye plural of 'new'
740 \arg \b vm-gammel singular of 'old'
741 \arg \b vm-gamle plural of 'old'
749 Italian requires the following additional soundfile:
753 \arg \b vm-nuovi new plural
754 \arg \b vm-vecchio old
755 \arg \b vm-vecchi old plural
757 Chinese (Taiwan) requires the following additional soundfile:
758 \arg \b vm-tong A class-word for call (tong1)
759 \arg \b vm-ri A class-word for day (ri4)
760 \arg \b vm-you You (ni3)
761 \arg \b vm-haveno Have no (mei2 you3)
762 \arg \b vm-have Have (you3)
763 \arg \b vm-listen To listen (yao4 ting1)
766 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
767 spelled among others when you have to change folder. For the above reasons, vm-INBOX
768 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
777 unsigned char iobuf[BASEMAXINLINE];
780 /*! Structure for linked list of users
781 * Use ast_vm_user_destroy() to free one of these structures. */
783 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
784 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
785 char password[80]; /*!< Secret pin code, numbers only */
786 char fullname[80]; /*!< Full name, for directory app */
787 char email[80]; /*!< E-mail address */
788 char *emailsubject; /*!< E-mail subject */
789 char *emailbody; /*!< E-mail body */
790 char pager[80]; /*!< E-mail address to pager (no attachment) */
791 char serveremail[80]; /*!< From: Mail address */
792 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
793 char zonetag[80]; /*!< Time zone */
794 char locale[20]; /*!< The locale (for presentation of date/time) */
797 char uniqueid[80]; /*!< Unique integer identifier */
799 char attachfmt[20]; /*!< Attachment format */
800 unsigned int flags; /*!< VM_ flags */
802 int minsecs; /*!< Minimum number of seconds per message for this mailbox */
803 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
804 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
805 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
806 int passwordlocation; /*!< Storage location of the password */
808 char imapserver[48]; /*!< IMAP server address */
809 char imapport[8]; /*!< IMAP server port */
810 char imapflags[128]; /*!< IMAP optional flags */
811 char imapuser[80]; /*!< IMAP server login */
812 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
813 char imapfolder[64]; /*!< IMAP voicemail folder */
814 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
815 int imapversion; /*!< If configuration changes, use the new values */
817 double volgain; /*!< Volume gain for voicemails sent via email */
818 AST_LIST_ENTRY(ast_vm_user) list;
821 /*! Voicemail time zones */
823 AST_LIST_ENTRY(vm_zone) list;
826 char msg_format[512];
829 #define VMSTATE_MAX_MSG_ARRAY 256
831 /*! Voicemail mailbox state */
836 char curdir[PATH_MAX];
837 char vmbox[PATH_MAX];
839 char intro[PATH_MAX];
842 int dh_arraysize; /* used for deleted / heard allocation */
852 int updated; /*!< decremented on each mail check until 1 -allows delay */
853 long msgArray[VMSTATE_MAX_MSG_ARRAY];
854 MAILSTREAM *mailstream;
856 char imapuser[80]; /*!< IMAP server login */
857 char imapfolder[64]; /*!< IMAP voicemail folder */
858 char imapserver[48]; /*!< IMAP server address */
859 char imapport[8]; /*!< IMAP server port */
860 char imapflags[128]; /*!< IMAP optional flags */
863 char introfn[PATH_MAX]; /*!< Name of prepended file */
864 unsigned int quota_limit;
865 unsigned int quota_usage;
866 struct vm_state *persist_vms;
871 static char odbc_database[80];
872 static char odbc_table[80];
873 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
874 #define DISPOSE(a,b) remove_file(a,b)
875 #define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d)
876 #define EXISTS(a,b,c,d) (message_exists(a,b))
877 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
878 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
879 #define DELETE(a,b,c,d) (delete_file(a,b))
880 #define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c)))
883 #define DISPOSE(a,b) (imap_remove_file(a,b))
884 #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))
885 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
886 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
887 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
888 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
889 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
890 #define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f)))
892 #define RETRIEVE(a,b,c,d)
894 #define STORE(a,b,c,d,e,f,g,h,i,j,k)
895 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
896 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
897 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
898 #define DELETE(a,b,c,d) (vm_delete(c))
899 #define UPDATE_MSG_ID(a, b, c, d, e, f)
903 static char VM_SPOOL_DIR[PATH_MAX];
905 static char ext_pass_cmd[128];
906 static char ext_pass_check_cmd[128];
910 #define PWDCHANGE_INTERNAL (1 << 1)
911 #define PWDCHANGE_EXTERNAL (1 << 2)
912 static int pwdchange = PWDCHANGE_INTERNAL;
915 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
918 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
920 # define tdesc "Comedian Mail (Voicemail System)"
924 static char userscontext[AST_MAX_EXTENSION] = "default";
926 static char *addesc = "Comedian Mail";
928 /* Leave a message */
929 static char *app = "VoiceMail";
931 /* Check mail, control, etc */
932 static char *app2 = "VoiceMailMain";
934 static char *app3 = "MailboxExists";
935 static char *app4 = "VMAuthenticate";
937 static char *playmsg_app = "VoiceMailPlayMsg";
939 static char *sayname_app = "VMSayName";
941 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
942 static AST_LIST_HEAD_STATIC(zones, vm_zone);
943 static char zonetag[80];
944 static char locale[20];
945 static int maxsilence;
947 static int maxdeletedmsg;
948 static int silencethreshold = 128;
949 static char serveremail[80];
950 static char mailcmd[160]; /* Configurable mail cmd */
951 static char externnotify[160];
952 static struct ast_smdi_interface *smdi_iface = NULL;
953 static char vmfmts[80];
954 static double volgain;
955 static int vmminsecs;
956 static int vmmaxsecs;
959 static int maxlogins;
960 static int minpassword;
961 static int passwordlocation;
963 /*! Poll mailboxes for changes since there is something external to
964 * app_voicemail that may change them. */
965 static unsigned int poll_mailboxes;
967 /*! Polling frequency */
968 static unsigned int poll_freq;
969 /*! By default, poll every 30 seconds */
970 #define DEFAULT_POLL_FREQ 30
972 AST_MUTEX_DEFINE_STATIC(poll_lock);
973 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
974 static pthread_t poll_thread = AST_PTHREADT_NULL;
975 static unsigned char poll_thread_run;
977 /*! Subscription to ... MWI event subscriptions */
978 static struct ast_event_sub *mwi_sub_sub;
979 /*! Subscription to ... MWI event un-subscriptions */
980 static struct ast_event_sub *mwi_unsub_sub;
983 * \brief An MWI subscription
985 * This is so we can keep track of which mailboxes are subscribed to.
986 * This way, we know which mailboxes to poll when the pollmailboxes
987 * option is being used.
990 AST_RWLIST_ENTRY(mwi_sub) entry;
998 struct mwi_sub_task {
1000 const char *context;
1004 static struct ast_taskprocessor *mwi_subscription_tps;
1006 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
1008 /* custom audio control prompts for voicemail playback */
1009 static char listen_control_forward_key[12];
1010 static char listen_control_reverse_key[12];
1011 static char listen_control_pause_key[12];
1012 static char listen_control_restart_key[12];
1013 static char listen_control_stop_key[12];
1015 /* custom password sounds */
1016 static char vm_password[80] = "vm-password";
1017 static char vm_newpassword[80] = "vm-newpassword";
1018 static char vm_passchanged[80] = "vm-passchanged";
1019 static char vm_reenterpassword[80] = "vm-reenterpassword";
1020 static char vm_mismatch[80] = "vm-mismatch";
1021 static char vm_invalid_password[80] = "vm-invalid-password";
1022 static char vm_pls_try_again[80] = "vm-pls-try-again";
1025 * XXX If we have the time, motivation, etc. to fix up this prompt, one of the following would be appropriate:
1026 * 1. create a sound along the lines of "Please try again. When done, press the pound key" which could be spliced
1027 * from existing sound clips. This would require some programming changes in the area of vm_forward options and also
1028 * app.c's __ast_play_and_record function
1029 * 2. create a sound prompt saying "Please try again. When done recording, press any key to stop and send the prepended
1030 * message." At the time of this comment, I think this would require new voice work to be commissioned.
1031 * 3. Something way different like providing instructions before a time out or a post-recording menu. This would require
1032 * more effort than either of the other two.
1034 static char vm_prepend_timeout[80] = "vm-then-pound";
1036 static struct ast_flags globalflags = {0};
1038 static int saydurationminfo;
1040 static char dialcontext[AST_MAX_CONTEXT] = "";
1041 static char callcontext[AST_MAX_CONTEXT] = "";
1042 static char exitcontext[AST_MAX_CONTEXT] = "";
1044 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
1047 static char *emailbody = NULL;
1048 static char *emailsubject = NULL;
1049 static char *pagerbody = NULL;
1050 static char *pagersubject = NULL;
1051 static char fromstring[100];
1052 static char pagerfromstring[100];
1053 static char charset[32] = "ISO-8859-1";
1055 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
1056 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
1057 static int adsiver = 1;
1058 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
1059 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
1061 /* Forward declarations - generic */
1062 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
1063 static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
1064 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);
1065 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
1066 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
1067 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
1068 signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id);
1069 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
1070 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
1071 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);
1072 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);
1073 static void apply_options(struct ast_vm_user *vmu, const char *options);
1074 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);
1075 static int is_valid_dtmf(const char *key);
1076 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
1077 static int write_password_to_file(const char *secretfn, const char *password);
1078 static const char *substitute_escapes(const char *value);
1079 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);
1081 * Place a message in the indicated folder
1083 * \param vmu Voicemail user
1084 * \param vms Current voicemail state for the user
1085 * \param msg The message number to save
1086 * \param box The folder into which the message should be saved
1087 * \param[out] newmsg The new message number of the saved message
1088 * \param move Tells whether to copy or to move the message
1090 * \note the "move" parameter is only honored for IMAP voicemail presently
1092 * \retval other Failure
1094 static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
1096 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);
1097 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
1099 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);
1100 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);
1101 static int vm_msg_remove(const char *mailbox, const char *context, size_t num_msgs, const char *folder, const char *msgs[]);
1102 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);
1104 #ifdef TEST_FRAMEWORK
1105 static int vm_test_destroy_user(const char *context, const char *mailbox);
1106 static int vm_test_create_user(const char *context, const char *mailbox);
1109 struct ao2_container *inprocess_container;
1117 static int inprocess_hash_fn(const void *obj, const int flags)
1119 const struct inprocess *i = obj;
1120 return atoi(i->mailbox);
1123 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
1125 struct inprocess *i = obj, *j = arg;
1126 if (strcmp(i->mailbox, j->mailbox)) {
1129 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
1132 static int inprocess_count(const char *context, const char *mailbox, int delta)
1134 struct inprocess *i, *arg = ast_alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
1135 arg->context = arg->mailbox + strlen(mailbox) + 1;
1136 strcpy(arg->mailbox, mailbox); /* SAFE */
1137 strcpy(arg->context, context); /* SAFE */
1138 ao2_lock(inprocess_container);
1139 if ((i = ao2_find(inprocess_container, arg, 0))) {
1140 int ret = ast_atomic_fetchadd_int(&i->count, delta);
1141 ao2_unlock(inprocess_container);
1146 ast_log(LOG_WARNING, "BUG: ref count decrement on non-existing object???\n");
1148 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
1149 ao2_unlock(inprocess_container);
1152 i->context = i->mailbox + strlen(mailbox) + 1;
1153 strcpy(i->mailbox, mailbox); /* SAFE */
1154 strcpy(i->context, context); /* SAFE */
1156 ao2_link(inprocess_container, i);
1157 ao2_unlock(inprocess_container);
1162 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
1163 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
1167 * \brief Strips control and non 7-bit clean characters from input string.
1169 * \note To map control and none 7-bit characters to a 7-bit clean characters
1170 * please use ast_str_encode_mine().
1172 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
1175 for (; *input; input++) {
1180 if (bufptr == buf + buflen - 1) {
1190 * \brief Sets default voicemail system options to a voicemail user.
1192 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
1193 * - all the globalflags
1194 * - the saydurationminfo
1198 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
1200 * - emailsubject, emailbody set to NULL
1202 static void populate_defaults(struct ast_vm_user *vmu)
1204 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
1205 vmu->passwordlocation = passwordlocation;
1206 if (saydurationminfo) {
1207 vmu->saydurationm = saydurationminfo;
1209 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
1210 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
1211 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
1212 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
1213 ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
1215 vmu->minsecs = vmminsecs;
1218 vmu->maxsecs = vmmaxsecs;
1221 vmu->maxmsg = maxmsg;
1223 if (maxdeletedmsg) {
1224 vmu->maxdeletedmsg = maxdeletedmsg;
1226 vmu->volgain = volgain;
1227 ast_free(vmu->emailsubject);
1228 vmu->emailsubject = NULL;
1229 ast_free(vmu->emailbody);
1230 vmu->emailbody = NULL;
1232 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
1233 ast_copy_string(vmu->imapserver, imapserver, sizeof(vmu->imapserver));
1234 ast_copy_string(vmu->imapport, imapport, sizeof(vmu->imapport));
1235 ast_copy_string(vmu->imapflags, imapflags, sizeof(vmu->imapflags));
1240 * \brief Sets a a specific property value.
1241 * \param vmu The voicemail user object to work with.
1242 * \param var The name of the property to be set.
1243 * \param value The value to be set to the property.
1245 * The property name must be one of the understood properties. See the source for details.
1247 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1250 if (!strcasecmp(var, "attach")) {
1251 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1252 } else if (!strcasecmp(var, "attachfmt")) {
1253 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1254 } else if (!strcasecmp(var, "serveremail")) {
1255 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1256 } else if (!strcasecmp(var, "emailbody")) {
1257 ast_free(vmu->emailbody);
1258 vmu->emailbody = ast_strdup(substitute_escapes(value));
1259 } else if (!strcasecmp(var, "emailsubject")) {
1260 ast_free(vmu->emailsubject);
1261 vmu->emailsubject = ast_strdup(substitute_escapes(value));
1262 } else if (!strcasecmp(var, "language")) {
1263 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1264 } else if (!strcasecmp(var, "tz")) {
1265 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1266 } else if (!strcasecmp(var, "locale")) {
1267 ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
1269 } else if (!strcasecmp(var, "imapuser")) {
1270 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1271 vmu->imapversion = imapversion;
1272 } else if (!strcasecmp(var, "imapserver")) {
1273 ast_copy_string(vmu->imapserver, value, sizeof(vmu->imapserver));
1274 vmu->imapversion = imapversion;
1275 } else if (!strcasecmp(var, "imapport")) {
1276 ast_copy_string(vmu->imapport, value, sizeof(vmu->imapport));
1277 vmu->imapversion = imapversion;
1278 } else if (!strcasecmp(var, "imapflags")) {
1279 ast_copy_string(vmu->imapflags, value, sizeof(vmu->imapflags));
1280 vmu->imapversion = imapversion;
1281 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1282 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1283 vmu->imapversion = imapversion;
1284 } else if (!strcasecmp(var, "imapfolder")) {
1285 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1286 vmu->imapversion = imapversion;
1287 } else if (!strcasecmp(var, "imapvmshareid")) {
1288 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1289 vmu->imapversion = imapversion;
1291 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1292 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1293 } else if (!strcasecmp(var, "saycid")){
1294 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1295 } else if (!strcasecmp(var, "sendvoicemail")){
1296 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1297 } else if (!strcasecmp(var, "review")){
1298 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1299 } else if (!strcasecmp(var, "tempgreetwarn")){
1300 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1301 } else if (!strcasecmp(var, "messagewrap")){
1302 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1303 } else if (!strcasecmp(var, "operator")) {
1304 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1305 } else if (!strcasecmp(var, "envelope")){
1306 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1307 } else if (!strcasecmp(var, "moveheard")){
1308 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1309 } else if (!strcasecmp(var, "sayduration")){
1310 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1311 } else if (!strcasecmp(var, "saydurationm")){
1312 if (sscanf(value, "%30d", &x) == 1) {
1313 vmu->saydurationm = x;
1315 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1317 } else if (!strcasecmp(var, "forcename")){
1318 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1319 } else if (!strcasecmp(var, "forcegreetings")){
1320 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1321 } else if (!strcasecmp(var, "callback")) {
1322 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1323 } else if (!strcasecmp(var, "dialout")) {
1324 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1325 } else if (!strcasecmp(var, "exitcontext")) {
1326 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1327 } else if (!strcasecmp(var, "minsecs")) {
1328 if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1331 ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1332 vmu->minsecs = vmminsecs;
1334 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1335 vmu->maxsecs = atoi(value);
1336 if (vmu->maxsecs <= 0) {
1337 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1338 vmu->maxsecs = vmmaxsecs;
1340 vmu->maxsecs = atoi(value);
1342 if (!strcasecmp(var, "maxmessage"))
1343 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1344 } else if (!strcasecmp(var, "maxmsg")) {
1345 vmu->maxmsg = atoi(value);
1346 /* Accept maxmsg=0 (Greetings only voicemail) */
1347 if (vmu->maxmsg < 0) {
1348 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1349 vmu->maxmsg = MAXMSG;
1350 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1351 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1352 vmu->maxmsg = MAXMSGLIMIT;
1354 } else if (!strcasecmp(var, "nextaftercmd")) {
1355 ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
1356 } else if (!strcasecmp(var, "backupdeleted")) {
1357 if (sscanf(value, "%30d", &x) == 1)
1358 vmu->maxdeletedmsg = x;
1359 else if (ast_true(value))
1360 vmu->maxdeletedmsg = MAXMSG;
1362 vmu->maxdeletedmsg = 0;
1364 if (vmu->maxdeletedmsg < 0) {
1365 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1366 vmu->maxdeletedmsg = MAXMSG;
1367 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1368 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1369 vmu->maxdeletedmsg = MAXMSGLIMIT;
1371 } else if (!strcasecmp(var, "volgain")) {
1372 sscanf(value, "%30lf", &vmu->volgain);
1373 } else if (!strcasecmp(var, "passwordlocation")) {
1374 if (!strcasecmp(value, "spooldir")) {
1375 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1377 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1379 } else if (!strcasecmp(var, "options")) {
1380 apply_options(vmu, value);
1384 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1386 int fds[2], pid = 0;
1388 memset(buf, 0, len);
1391 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1394 pid = ast_safe_fork(0);
1400 snprintf(buf, len, "FAILURE: Fork failed");
1404 if (read(fds[0], buf, len) < 0) {
1405 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1410 AST_DECLARE_APP_ARGS(arg,
1413 char *mycmd = ast_strdupa(command);
1416 dup2(fds[1], STDOUT_FILENO);
1418 ast_close_fds_above_n(STDOUT_FILENO);
1420 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1422 execv(arg.v[0], arg.v);
1423 printf("FAILURE: %s", strerror(errno));
1431 * \brief Check that password meets minimum required length
1432 * \param vmu The voicemail user to change the password for.
1433 * \param password The password string to check
1435 * \return zero on ok, 1 on not ok.
1437 static int check_password(struct ast_vm_user *vmu, char *password)
1439 /* check minimum length */
1440 if (strlen(password) < minpassword)
1442 /* check that password does not contain '*' character */
1443 if (!ast_strlen_zero(password) && password[0] == '*')
1445 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1446 char cmd[255], buf[255];
1448 ast_debug(1, "Verify password policies for %s\n", password);
1450 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1451 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1452 ast_debug(5, "Result: %s\n", buf);
1453 if (!strncasecmp(buf, "VALID", 5)) {
1454 ast_debug(3, "Passed password check: '%s'\n", buf);
1456 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1457 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1460 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1469 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1470 * \param vmu The voicemail user to change the password for.
1471 * \param password The new value to be set to the password for this user.
1473 * This only works if there is a realtime engine configured.
1474 * This is called from the (top level) vm_change_password.
1476 * \return zero on success, -1 on error.
1478 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1481 if (!strcmp(vmu->password, password)) {
1482 /* No change (but an update would return 0 rows updated, so we opt out here) */
1486 if (strlen(password) > 10) {
1487 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1489 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1490 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: realtime engine updated with new password\r\nPasswordSource: realtime");
1491 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1498 * \brief Destructively Parse options and apply.
1500 static void apply_options(struct ast_vm_user *vmu, const char *options)
1505 stringp = ast_strdupa(options);
1506 while ((s = strsep(&stringp, "|"))) {
1508 if ((var = strsep(&value, "=")) && value) {
1509 apply_option(vmu, var, value);
1515 * \brief Loads the options specific to a voicemail user.
1517 * This is called when a vm_user structure is being set up, such as from load_options.
1519 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1521 for (; var; var = var->next) {
1522 if (!strcasecmp(var->name, "vmsecret")) {
1523 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1524 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1525 if (ast_strlen_zero(retval->password)) {
1526 if (!ast_strlen_zero(var->value) && var->value[0] == '*') {
1527 ast_log(LOG_WARNING, "Invalid password detected for mailbox %s. The password"
1528 "\n\tmust be reset in voicemail.conf.\n", retval->mailbox);
1530 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1533 } else if (!strcasecmp(var->name, "uniqueid")) {
1534 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1535 } else if (!strcasecmp(var->name, "pager")) {
1536 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1537 } else if (!strcasecmp(var->name, "email")) {
1538 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1539 } else if (!strcasecmp(var->name, "fullname")) {
1540 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1541 } else if (!strcasecmp(var->name, "context")) {
1542 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1543 } else if (!strcasecmp(var->name, "emailsubject")) {
1544 ast_free(retval->emailsubject);
1545 retval->emailsubject = ast_strdup(substitute_escapes(var->value));
1546 } else if (!strcasecmp(var->name, "emailbody")) {
1547 ast_free(retval->emailbody);
1548 retval->emailbody = ast_strdup(substitute_escapes(var->value));
1550 } else if (!strcasecmp(var->name, "imapuser")) {
1551 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1552 retval->imapversion = imapversion;
1553 } else if (!strcasecmp(var->name, "imapserver")) {
1554 ast_copy_string(retval->imapserver, var->value, sizeof(retval->imapserver));
1555 retval->imapversion = imapversion;
1556 } else if (!strcasecmp(var->name, "imapport")) {
1557 ast_copy_string(retval->imapport, var->value, sizeof(retval->imapport));
1558 retval->imapversion = imapversion;
1559 } else if (!strcasecmp(var->name, "imapflags")) {
1560 ast_copy_string(retval->imapflags, var->value, sizeof(retval->imapflags));
1561 retval->imapversion = imapversion;
1562 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1563 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1564 retval->imapversion = imapversion;
1565 } else if (!strcasecmp(var->name, "imapfolder")) {
1566 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1567 retval->imapversion = imapversion;
1568 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1569 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1570 retval->imapversion = imapversion;
1573 apply_option(retval, var->name, var->value);
1578 * \brief Determines if a DTMF key entered is valid.
1579 * \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.
1581 * Tests the character entered against the set of valid DTMF characters.
1582 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1584 static int is_valid_dtmf(const char *key)
1587 char *local_key = ast_strdupa(key);
1589 for (i = 0; i < strlen(key); ++i) {
1590 if (!strchr(VALID_DTMF, *local_key)) {
1591 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1600 * \brief Finds a voicemail user from the realtime engine.
1605 * 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.
1607 * \return The ast_vm_user structure for the user that was found.
1609 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1611 struct ast_variable *var;
1612 struct ast_vm_user *retval;
1614 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1616 memset(retval, 0, sizeof(*retval));
1618 populate_defaults(retval);
1620 ast_set_flag(retval, VM_ALLOCED);
1623 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1625 if (!context && ast_test_flag((&globalflags), VM_SEARCH)) {
1626 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1628 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1631 apply_options_full(retval, var);
1632 ast_variables_destroy(var);
1643 * \brief Finds a voicemail user from the users file or the realtime engine.
1648 * \return The ast_vm_user structure for the user that was found.
1650 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1652 /* This function could be made to generate one from a database, too */
1653 struct ast_vm_user *vmu = NULL, *cur;
1654 AST_LIST_LOCK(&users);
1656 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1657 context = "default";
1659 AST_LIST_TRAVERSE(&users, cur, list) {
1661 if (cur->imapversion != imapversion) {
1665 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1667 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1671 /* Make a copy, so that on a reload, we have no race */
1672 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1675 vmu->emailbody = ast_strdup(cur->emailbody);
1676 vmu->emailsubject = ast_strdup(cur->emailsubject);
1678 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1679 AST_LIST_NEXT(vmu, list) = NULL;
1682 vmu = find_user_realtime(ivm, context, mailbox);
1683 AST_LIST_UNLOCK(&users);
1688 * \brief Resets a user password to a specified password.
1693 * This does the actual change password work, called by the vm_change_password() function.
1695 * \return zero on success, -1 on error.
1697 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1699 /* This function could be made to generate one from a database, too */
1700 struct ast_vm_user *cur;
1702 AST_LIST_LOCK(&users);
1703 AST_LIST_TRAVERSE(&users, cur, list) {
1704 if ((!context || !strcasecmp(context, cur->context)) &&
1705 (!strcasecmp(mailbox, cur->mailbox)))
1709 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1712 AST_LIST_UNLOCK(&users);
1717 * \brief The handler for the change password option.
1718 * \param vmu The voicemail user to work with.
1719 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1720 * 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.
1721 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1723 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1725 struct ast_config *cfg = NULL;
1726 struct ast_variable *var = NULL;
1727 struct ast_category *cat = NULL;
1728 char *category = NULL, *value = NULL, *new = NULL;
1729 const char *tmp = NULL;
1730 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1731 char secretfn[PATH_MAX] = "";
1734 if (!change_password_realtime(vmu, newpassword))
1737 /* check if we should store the secret in the spool directory next to the messages */
1738 switch (vmu->passwordlocation) {
1739 case OPT_PWLOC_SPOOLDIR:
1740 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1741 if (write_password_to_file(secretfn, newpassword) == 0) {
1742 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: secret.conf updated with new password\r\nPasswordSource: secret.conf");
1743 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1744 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1745 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1748 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1751 case OPT_PWLOC_VOICEMAILCONF:
1752 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1753 while ((category = ast_category_browse(cfg, category))) {
1754 if (!strcasecmp(category, vmu->context)) {
1755 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1756 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1759 value = strstr(tmp, ",");
1761 new = ast_alloca(strlen(newpassword)+1);
1762 sprintf(new, "%s", newpassword);
1764 new = ast_alloca((strlen(value) + strlen(newpassword) + 1));
1765 sprintf(new, "%s%s", newpassword, value);
1767 if (!(cat = ast_category_get(cfg, category))) {
1768 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1771 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1775 /* save the results */
1777 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: voicemail.conf updated with new password\r\nPasswordSource: voicemail.conf");
1778 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1779 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1780 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1785 case OPT_PWLOC_USERSCONF:
1786 /* check users.conf and update the password stored for the mailbox */
1787 /* if no vmsecret entry exists create one. */
1788 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1789 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1790 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1791 ast_debug(4, "users.conf: %s\n", category);
1792 if (!strcasecmp(category, vmu->mailbox)) {
1793 if (!ast_variable_retrieve(cfg, category, "vmsecret")) {
1794 ast_debug(3, "looks like we need to make vmsecret!\n");
1795 var = ast_variable_new("vmsecret", newpassword, "");
1799 new = ast_alloca(strlen(newpassword) + 1);
1800 sprintf(new, "%s", newpassword);
1801 if (!(cat = ast_category_get(cfg, category))) {
1802 ast_debug(4, "failed to get category!\n");
1807 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1809 ast_variable_append(cat, var);
1815 /* save the results and clean things up */
1817 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: users.conf updated with new password\r\nPasswordSource: users.conf");
1818 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1819 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1820 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1826 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1829 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1830 ast_debug(1, "External password: %s\n",buf);
1831 if (!ast_safe_system(buf)) {
1832 ast_test_suite_event_notify("PASSWORDCHANGED", "Message: external script updated with new password\r\nPasswordSource: external");
1833 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1834 /* Reset the password in memory, too */
1835 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1840 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1841 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1842 * \param len The length of the path string that was written out.
1847 * The path is constructed as
1848 * VM_SPOOL_DIRcontext/ext/folder
1850 * \return zero on success, -1 on error.
1852 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1854 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
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.
1864 * The path is constructed as
1865 * VM_SPOOL_DIRcontext/ext/folder
1867 * \return zero on success, -1 on error.
1869 static int make_file(char *dest, const int len, const char *dir, const int num)
1871 return snprintf(dest, len, "%s/msg%04d", dir, num);
1874 /* same as mkstemp, but return a FILE * */
1875 static FILE *vm_mkftemp(char *template)
1878 int pfd = mkstemp(template);
1879 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1881 p = fdopen(pfd, "w+");
1890 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1891 * \param dest String. base directory.
1892 * \param len Length of dest.
1893 * \param context String. Ignored if is null or empty string.
1894 * \param ext String. Ignored if is null or empty string.
1895 * \param folder String. Ignored if is null or empty string.
1896 * \return -1 on failure, 0 on success.
1898 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1900 mode_t mode = VOICEMAIL_DIR_MODE;
1903 make_dir(dest, len, context, ext, folder);
1904 if ((res = ast_mkdir(dest, mode))) {
1905 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1911 static const char *mbox(struct ast_vm_user *vmu, int id)
1914 if (vmu && id == 0) {
1915 return vmu->imapfolder;
1918 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1921 static const char *vm_index_to_foldername(int id)
1923 return mbox(NULL, id);
1927 static int get_folder_by_name(const char *name)
1931 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1932 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1940 static void free_user(struct ast_vm_user *vmu)
1942 if (ast_test_flag(vmu, VM_ALLOCED)) {
1944 ast_free(vmu->emailbody);
1945 vmu->emailbody = NULL;
1947 ast_free(vmu->emailsubject);
1948 vmu->emailsubject = NULL;
1954 static int vm_allocate_dh(struct vm_state *vms, struct ast_vm_user *vmu, int count_msg) {
1956 int arraysize = (vmu->maxmsg > count_msg ? vmu->maxmsg : count_msg);
1958 /* remove old allocation */
1960 ast_free(vms->deleted);
1961 vms->deleted = NULL;
1964 ast_free(vms->heard);
1967 vms->dh_arraysize = 0;
1969 if (arraysize > 0) {
1970 if (!(vms->deleted = ast_calloc(arraysize, sizeof(int)))) {
1973 if (!(vms->heard = ast_calloc(arraysize, sizeof(int)))) {
1974 ast_free(vms->deleted);
1975 vms->deleted = NULL;
1978 vms->dh_arraysize = arraysize;
1984 /* All IMAP-specific functions should go in this block. This
1985 * keeps them from being spread out all over the code */
1987 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1990 struct vm_state *vms;
1991 unsigned long messageNum;
1993 /* If greetings aren't stored in IMAP, just delete the file */
1994 if (msgnum < 0 && !imapgreetings) {
1995 ast_filedelete(file, NULL);
1999 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2000 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);
2005 imap_delete_old_greeting(file, vms);
2009 /* find real message number based on msgnum */
2010 /* this may be an index into vms->msgArray based on the msgnum. */
2011 messageNum = vms->msgArray[msgnum];
2012 if (messageNum == 0) {
2013 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
2016 ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
2017 /* delete message */
2018 snprintf (arg, sizeof(arg), "%lu", messageNum);
2019 ast_mutex_lock(&vms->lock);
2020 mail_setflag (vms->mailstream, arg, "\\DELETED");
2021 mail_expunge(vms->mailstream);
2022 ast_mutex_unlock(&vms->lock);
2025 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)
2027 struct ast_channel *chan;
2031 struct vm_state *vms;
2032 const char *duration_str;
2036 * First, get things initially set up. If any of this fails, then
2037 * back out before doing anything substantial
2039 vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0);
2044 if (open_mailbox(vms, vmu, folder)) {
2048 chan = ast_dummy_channel_alloc();
2050 close_mailbox(vms, vmu);
2055 * We need to make sure the new message we save has the same
2056 * callerid, flag, and duration as the original message
2058 cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
2060 if (!ast_strlen_zero(cid)) {
2061 ast_callerid_parse(cid, &cid_name, &cid_num);
2062 ast_party_caller_init(ast_channel_caller(chan));
2063 if (!ast_strlen_zero(cid_name)) {
2064 ast_channel_caller(chan)->id.name.valid = 1;
2065 ast_channel_caller(chan)->id.name.str = ast_strdup(cid_name);
2067 if (!ast_strlen_zero(cid_num)) {
2068 ast_channel_caller(chan)->id.number.valid = 1;
2069 ast_channel_caller(chan)->id.number.str = ast_strdup(cid_num);
2073 duration_str = ast_variable_retrieve(msg_cfg, "message", "duration");
2075 if (!ast_strlen_zero(duration_str)) {
2076 sscanf(duration_str, "%30d", &duration);
2080 * IMAP messages cannot be altered once delivered. So we have to delete the
2081 * current message and then re-add it with the updated message ID.
2083 * Furthermore, there currently is no atomic way to create a new message and to
2084 * store it in an arbitrary folder. So we have to save it to the INBOX and then
2085 * move to the appropriate folder.
2087 if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts,
2088 duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) {
2089 if (folder != NEW_FOLDER) {
2090 save_to_folder(vmu, vms, msgnum, folder, NULL, 1);
2092 vm_imap_delete(dir, msgnum, vmu);
2094 close_mailbox(vms, vmu);
2095 ast_channel_unref(chan);
2098 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
2100 struct vm_state *vms_p;
2101 char *file, *filename;
2106 /* This function is only used for retrieval of IMAP greetings
2107 * regular messages are not retrieved this way, nor are greetings
2108 * if they are stored locally*/
2109 if (msgnum > -1 || !imapgreetings) {
2112 file = strrchr(ast_strdupa(dir), '/');
2116 ast_debug(1, "Failed to procure file name from directory passed.\n");
2121 /* check if someone is accessing this box right now... */
2122 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
2123 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2124 /* Unlike when retrieving a message, it is reasonable not to be able to find a
2125 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
2126 * that's all we need to do.
2128 if (!(vms_p = create_vm_state_from_user(vmu))) {
2129 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
2134 /* Greetings will never have a prepended message */
2135 *vms_p->introfn = '\0';
2137 ast_mutex_lock(&vms_p->lock);
2138 if (init_mailstream(vms_p, GREETINGS_FOLDER) || !vms_p->mailstream) {
2139 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL or can't init_mailstream\n");
2140 ast_mutex_unlock(&vms_p->lock);
2144 /*XXX Yuck, this could probably be done a lot better */
2145 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
2146 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
2147 /* We have the body, now we extract the file name of the first attachment. */
2148 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
2149 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
2151 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
2152 ast_mutex_unlock(&vms_p->lock);
2155 filename = strsep(&attachment, ".");
2156 if (!strcmp(filename, file)) {
2157 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
2158 vms_p->msgArray[vms_p->curmsg] = i + 1;
2159 save_body(body, vms_p, "2", attachment, 0);
2160 ast_mutex_unlock(&vms_p->lock);
2164 ast_mutex_unlock(&vms_p->lock);
2169 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
2172 char *header_content;
2173 char *attachedfilefmt;
2175 struct vm_state *vms;
2176 char text_file[PATH_MAX];
2177 FILE *text_file_ptr;
2179 struct ast_vm_user *vmu;
2181 if (!(vmu = find_user(NULL, context, mailbox))) {
2182 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
2187 if (imapgreetings) {
2188 res = imap_retrieve_greeting(dir, msgnum, vmu);
2196 /* Before anything can happen, we need a vm_state so that we can
2197 * actually access the imap server through the vms->mailstream
2199 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2200 /* This should not happen. If it does, then I guess we'd
2201 * need to create the vm_state, extract which mailbox to
2202 * open, and then set up the msgArray so that the correct
2203 * IMAP message could be accessed. If I have seen correctly
2204 * though, the vms should be obtainable from the vmstates list
2205 * and should have its msgArray properly set up.
2207 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
2212 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
2213 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
2215 /* Don't try to retrieve a message from IMAP if it already is on the file system */
2216 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
2221 ast_debug(3, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
2222 if (vms->msgArray[msgnum] == 0) {
2223 ast_log(LOG_WARNING, "Trying to access unknown message\n");
2228 /* This will only work for new messages... */
2229 ast_mutex_lock(&vms->lock);
2230 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
2231 ast_mutex_unlock(&vms->lock);
2232 /* empty string means no valid header */
2233 if (ast_strlen_zero(header_content)) {
2234 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
2239 ast_mutex_lock(&vms->lock);
2240 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
2241 ast_mutex_unlock(&vms->lock);
2243 /* We have the body, now we extract the file name of the first attachment. */
2244 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
2245 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
2247 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
2252 /* Find the format of the attached file */
2254 strsep(&attachedfilefmt, ".");
2255 if (!attachedfilefmt) {
2256 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
2261 save_body(body, vms, "2", attachedfilefmt, 0);
2262 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
2263 *vms->introfn = '\0';
2266 /* Get info from headers!! */
2267 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
2269 if (!(text_file_ptr = fopen(text_file, "w"))) {
2270 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
2273 fprintf(text_file_ptr, "%s\n", "[message]");
2275 if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) {
2276 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
2278 if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) {
2279 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
2281 if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) {
2282 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
2284 if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) {
2285 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
2287 if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) {
2288 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
2290 if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) {
2291 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
2293 if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) {
2294 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
2296 if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) {
2297 fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, ""));
2299 fclose(text_file_ptr);
2306 static int folder_int(const char *folder)
2308 /*assume a NULL folder means INBOX*/
2312 if (!strcasecmp(folder, imapfolder)) {
2314 } else if (!strcasecmp(folder, "Old")) {
2316 } else if (!strcasecmp(folder, "Work")) {
2318 } else if (!strcasecmp(folder, "Family")) {
2320 } else if (!strcasecmp(folder, "Friends")) {
2322 } else if (!strcasecmp(folder, "Cust1")) {
2324 } else if (!strcasecmp(folder, "Cust2")) {
2326 } else if (!strcasecmp(folder, "Cust3")) {
2328 } else if (!strcasecmp(folder, "Cust4")) {
2330 } else if (!strcasecmp(folder, "Cust5")) {
2332 } else if (!strcasecmp(folder, "Urgent")) {
2334 } else { /*assume they meant INBOX if folder is not found otherwise*/
2339 static int __messagecount(const char *context, const char *mailbox, const char *folder)
2344 struct ast_vm_user *vmu, vmus;
2345 struct vm_state *vms_p;
2347 int fold = folder_int(folder);
2350 /* If URGENT, then look at INBOX */
2356 if (ast_strlen_zero(mailbox))
2359 /* We have to get the user before we can open the stream! */
2360 vmu = find_user(&vmus, context, mailbox);
2362 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
2365 /* No IMAP account available */
2366 if (vmu->imapuser[0] == '\0') {
2367 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2372 /* No IMAP account available */
2373 if (vmu->imapuser[0] == '\0') {
2374 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2379 /* check if someone is accessing this box right now... */
2380 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2382 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
2385 ast_debug(3, "Returning before search - user is logged in\n");
2386 if (fold == 0) { /* INBOX */
2387 return urgent ? vms_p->urgentmessages : vms_p->newmessages;
2389 if (fold == 1) { /* Old messages */
2390 return vms_p->oldmessages;
2394 /* add one if not there... */
2395 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2397 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2401 vms_p = create_vm_state_from_user(vmu);
2403 ret = init_mailstream(vms_p, fold);
2404 if (!vms_p->mailstream) {
2405 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2409 ast_mutex_lock(&vms_p->lock);
2410 pgm = mail_newsearchpgm ();
2411 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2412 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2414 if (fold != OLD_FOLDER) {
2418 /* In the special case where fold is 1 (old messages) we have to do things a bit
2419 * differently. Old messages are stored in the INBOX but are marked as "seen"
2425 /* look for urgent messages */
2426 if (fold == NEW_FOLDER) {
2438 vms_p->vmArrayIndex = 0;
2439 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2440 if (fold == 0 && urgent == 0)
2441 vms_p->newmessages = vms_p->vmArrayIndex;
2443 vms_p->oldmessages = vms_p->vmArrayIndex;
2444 if (fold == 0 && urgent == 1)
2445 vms_p->urgentmessages = vms_p->vmArrayIndex;
2446 /*Freeing the searchpgm also frees the searchhdr*/
2447 mail_free_searchpgm(&pgm);
2448 ast_mutex_unlock(&vms_p->lock);
2450 return vms_p->vmArrayIndex;
2452 ast_mutex_lock(&vms_p->lock);
2453 mail_ping(vms_p->mailstream);
2454 ast_mutex_unlock(&vms_p->lock);
2459 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2461 /* Check if mailbox is full */
2462 check_quota(vms, vmu->imapfolder);
2463 if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2464 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2466 ast_play_and_wait(chan, "vm-mailboxfull");
2471 /* Check if we have exceeded maxmsg */
2472 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));
2473 if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
2474 ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
2476 ast_play_and_wait(chan, "vm-mailboxfull");
2477 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2486 * \brief Gets the number of messages that exist in a mailbox folder.
2491 * This method is used when IMAP backend is used.
2492 * \return The number of messages in this mailbox folder (zero or more).
2494 static int messagecount(const char *context, const char *mailbox, const char *folder)
2496 if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2497 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2499 return __messagecount(context, mailbox, folder);
2503 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)
2505 char *myserveremail = serveremail;
2507 char introfn[PATH_MAX];
2511 char tmp[80] = "/tmp/astmail-XXXXXX";
2516 int ret; /* for better error checking */
2517 char *imap_flags = NIL;
2518 int msgcount = (messagecount(vmu->context, vmu->mailbox, "INBOX") + messagecount(vmu->context, vmu->mailbox, "Old"));
2519 int box = NEW_FOLDER;
2521 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2523 if(!imapgreetings) {
2526 box = GREETINGS_FOLDER;
2530 if (imap_check_limits(chan, vms, vmu, msgcount)) {
2534 /* Set urgent flag for IMAP message */
2535 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2536 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2537 imap_flags = "\\FLAGGED";
2540 /* Attach only the first format */
2541 fmt = ast_strdupa(fmt);
2543 strsep(&stringp, "|");
2545 if (!ast_strlen_zero(vmu->serveremail))
2546 myserveremail = vmu->serveremail;
2549 make_file(fn, sizeof(fn), dir, msgnum);
2551 ast_copy_string (fn, dir, sizeof(fn));
2553 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2554 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2558 if (ast_strlen_zero(vmu->email)) {
2559 /* We need the vmu->email to be set when we call make_email_file, but
2560 * if we keep it set, a duplicate e-mail will be created. So at the end
2561 * of this function, we will revert back to an empty string if tempcopy
2564 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2568 if (!strcmp(fmt, "wav49"))
2570 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2572 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2574 if (!(p = vm_mkftemp(tmp))) {
2575 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2577 *(vmu->email) = '\0';
2581 if (msgnum < 0 && imapgreetings) {
2582 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2583 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2586 imap_delete_old_greeting(fn, vms);
2589 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
2590 S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL),
2591 S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL),
2592 fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id);
2593 /* read mail file to memory */
2596 if (!(buf = ast_malloc(len + 1))) {
2597 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2600 *(vmu->email) = '\0';
2603 if (fread(buf, len, 1, p) < len) {
2605 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2609 ((char *) buf)[len] = '\0';
2610 INIT(&str, mail_string, buf, len);
2611 ret = init_mailstream(vms, box);
2613 imap_mailbox_name(mailbox, sizeof(mailbox), vms, box, 1);
2614 ast_mutex_lock(&vms->lock);
2615 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2616 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2617 ast_mutex_unlock(&vms->lock);
2622 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2628 ast_debug(3, "%s stored\n", fn);
2631 *(vmu->email) = '\0';
2632 inprocess_count(vmu->mailbox, vmu->context, -1);
2638 * \brief Gets the number of messages that exist in the inbox folder.
2639 * \param mailbox_context
2640 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2641 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2642 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2644 * This method is used when IMAP backend is used.
2645 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2647 * \return zero on success, -1 on error.
2650 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2652 char tmp[PATH_MAX] = "";
2664 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2665 /* If no mailbox, return immediately */
2666 if (ast_strlen_zero(mailbox_context))
2669 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2670 context = strchr(tmp, '@');
2671 if (strchr(mailbox_context, ',')) {
2672 int tmpnew, tmpold, tmpurgent;
2673 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2675 while ((cur = strsep(&mb, ", "))) {
2676 if (!ast_strlen_zero(cur)) {
2677 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2685 *urgentmsgs += tmpurgent;
2696 context = "default";
2697 mailboxnc = (char *) mailbox_context;
2701 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2703 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2706 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2713 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2718 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2726 * \brief Determines if the given folder has messages.
2727 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2728 * \param folder the folder to look in
2730 * This function is used when the mailbox is stored in an IMAP back end.
2731 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2732 * \return 1 if the folder has one or more messages. zero otherwise.
2735 static int has_voicemail(const char *mailbox, const char *folder)
2737 char tmp[256], *tmp2, *box, *context;
2738 ast_copy_string(tmp, mailbox, sizeof(tmp));
2740 if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2741 while ((box = strsep(&tmp2, ",&"))) {
2742 if (!ast_strlen_zero(box)) {
2743 if (has_voicemail(box, folder)) {
2749 if ((context = strchr(tmp, '@'))) {
2752 context = "default";
2754 return __messagecount(context, tmp, folder) ? 1 : 0;
2758 * \brief Copies a message from one mailbox to another.
2768 * This works with IMAP storage based mailboxes.
2770 * \return zero on success, -1 on error.
2772 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)
2774 struct vm_state *sendvms = NULL;
2775 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2776 if (msgnum >= recip->maxmsg) {
2777 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2780 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2781 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2784 if (!get_vm_state_by_imapuser(recip->imapuser, 0)) {
2785 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2788 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2789 ast_mutex_lock(&sendvms->lock);
2790 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2791 ast_mutex_unlock(&sendvms->lock);
2794 ast_mutex_unlock(&sendvms->lock);
2795 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2799 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2801 char tmp[256], *t = tmp;
2802 size_t left = sizeof(tmp);
2804 if (box == OLD_FOLDER) {
2805 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2807 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2810 if (box == NEW_FOLDER) {
2811 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2813 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2816 /* Build up server information */
2817 ast_build_string(&t, &left, "{%s:%s/imap", S_OR(vms->imapserver, imapserver), S_OR(vms->imapport, imapport));
2819 /* Add authentication user if present */
2820 if (!ast_strlen_zero(authuser))
2821 ast_build_string(&t, &left, "/authuser=%s", authuser);
2823 /* Add flags if present */
2824 if (!ast_strlen_zero(imapflags) || !(ast_strlen_zero(vms->imapflags))) {
2825 ast_build_string(&t, &left, "/%s", S_OR(vms->imapflags, imapflags));
2828 /* End with username */
2830 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2832 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2834 if (box == NEW_FOLDER || box == OLD_FOLDER)
2835 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2836 else if (box == GREETINGS_FOLDER)
2837 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2838 else { /* Other folders such as Friends, Family, etc... */
2839 if (!ast_strlen_zero(imapparentfolder)) {
2840 /* imapparentfolder would typically be set to INBOX */
2841 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2843 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2848 static int init_mailstream(struct vm_state *vms, int box)
2850 MAILSTREAM *stream = NIL;
2855 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2858 ast_debug(3, "vm_state user is:%s\n", vms->imapuser);
2859 if (vms->mailstream == NIL || !vms->mailstream) {
2860 ast_debug(1, "mailstream not set.\n");
2862 stream = vms->mailstream;
2864 /* debug = T; user wants protocol telemetry? */
2865 debug = NIL; /* NO protocol telemetry? */
2867 if (delimiter == '\0') { /* did not probe the server yet */
2869 #ifdef USE_SYSTEM_IMAP
2870 #include <imap/linkage.c>
2871 #elif defined(USE_SYSTEM_CCLIENT)
2872 #include <c-client/linkage.c>
2874 #include "linkage.c"
2876 /* Connect to INBOX first to get folders delimiter */
2877 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2878 ast_mutex_lock(&vms->lock);
2879 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2880 ast_mutex_unlock(&vms->lock);
2881 if (stream == NIL) {
2882 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2885 get_mailbox_delimiter(vms, stream);
2886 /* update delimiter in imapfolder */
2887 for (cp = vms->imapfolder; *cp; cp++)
2891 /* Now connect to the target folder */
2892 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2893 ast_debug(3, "Before mail_open, server: %s, box:%d\n", tmp, box);
2894 ast_mutex_lock(&vms->lock);
2895 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2896 ast_mutex_unlock(&vms->lock);
2897 if (vms->mailstream == NIL) {
2904 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2910 /* If Urgent, then look at INBOX */
2916 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2917 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2918 ast_copy_string(vms->imapserver, vmu->imapserver, sizeof(vms->imapserver));
2919 ast_copy_string(vms->imapport, vmu->imapport, sizeof(vms->imapport));
2920 ast_copy_string(vms->imapflags, vmu->imapflags, sizeof(vms->imapflags));
2921 vms->imapversion = vmu->imapversion;
2922 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2924 if (init_mailstream(vms, box) || !vms->mailstream) {
2925 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2929 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2933 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2934 check_quota(vms, (char *) mbox(vmu, box));
2937 ast_mutex_lock(&vms->lock);
2938 pgm = mail_newsearchpgm();
2940 /* Check IMAP folder for Asterisk messages only... */
2941 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2942 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2947 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2948 if (box == NEW_FOLDER && urgent == 1) {
2953 } else if (box == NEW_FOLDER && urgent == 0) {
2958 } else if (box == OLD_FOLDER) {
2963 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2965 vms->vmArrayIndex = 0;
2966 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2967 vms->lastmsg = vms->vmArrayIndex - 1;
2968 mail_free_searchpgm(&pgm);
2969 /* Since IMAP storage actually stores both old and new messages in the same IMAP folder,
2970 * ensure to allocate enough space to account for all of them. Warn if old messages
2971 * have not been checked first as that is required.
2973 if (box == 0 && !vms->dh_arraysize) {
2974 ast_log(LOG_WARNING, "The code expects the old messages to be checked first, fix the code.\n");
2976 if (vm_allocate_dh(vms, vmu, box == 0 ? vms->vmArrayIndex + vms->oldmessages : vms->lastmsg)) {
2977 ast_mutex_unlock(&vms->lock);
2981 ast_mutex_unlock(&vms->lock);
2985 static void write_file(char *filename, char *buffer, unsigned long len)
2989 output = fopen (filename, "w");
2990 if (fwrite(buffer, len, 1, output) != 1) {
2991 if (ferror(output)) {
2992 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2998 static void update_messages_by_imapuser(const char *user, unsigned long number)
3000 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
3002 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
3006 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
3007 vms->msgArray[vms->vmArrayIndex++] = number;
3010 void mm_searched(MAILSTREAM *stream, unsigned long number)
3012 char *mailbox = stream->mailbox, buf[1024] = "", *user;
3014 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
3017 update_messages_by_imapuser(user, number);
3020 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
3022 struct ast_variable *var;
3023 struct ast_vm_user *vmu;
3025 vmu = ast_calloc(1, sizeof *vmu);
3029 populate_defaults(vmu);
3030 ast_set_flag(vmu, VM_ALLOCED);
3032 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
3034 apply_options_full(vmu, var);
3035 ast_variables_destroy(var);
3043 /* Interfaces to C-client */
3045 void mm_exists(MAILSTREAM * stream, unsigned long number)
3047 /* mail_ping will callback here if new mail! */
3048 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
3049 if (number == 0) return;
3054 void mm_expunged(MAILSTREAM * stream, unsigned long number)
3056 /* mail_ping will callback here if expunged mail! */
3057 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
3058 if (number == 0) return;
3063 void mm_flags(MAILSTREAM * stream, unsigned long number)
3065 /* mail_ping will callback here if read mail! */
3066 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
3067 if (number == 0) return;
3072 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
3074 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
3075 mm_log (string, errflg);
3079 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
3081 if (delimiter == '\0') {
3085 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
3086 if (attributes & LATT_NOINFERIORS)
3087 ast_debug(5, "no inferiors\n");
3088 if (attributes & LATT_NOSELECT)
3089 ast_debug(5, "no select\n");
3090 if (attributes & LATT_MARKED)
3091 ast_debug(5, "marked\n");
3092 if (attributes & LATT_UNMARKED)
3093 ast_debug(5, "unmarked\n");
3097 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
3099 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
3100 if (attributes & LATT_NOINFERIORS)
3101 ast_debug(5, "no inferiors\n");
3102 if (attributes & LATT_NOSELECT)
3103 ast_debug(5, "no select\n");
3104 if (attributes & LATT_MARKED)
3105 ast_debug(5, "marked\n");
3106 if (attributes & LATT_UNMARKED)
3107 ast_debug(5, "unmarked\n");
3111 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
3113 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
3114 if (status->flags & SA_MESSAGES)
3115 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
3116 if (status->flags & SA_RECENT)
3117 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
3118 if (status->flags & SA_UNSEEN)
3119 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
3120 if (status->flags & SA_UIDVALIDITY)
3121 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
3122 if (status->flags & SA_UIDNEXT)
3123 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
3124 ast_log(AST_LOG_NOTICE, "\n");
3128 void mm_log(char *string, long errflg)
3130 switch ((short) errflg) {
3132 ast_debug(1, "IMAP Info: %s\n", string);
3136 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
3139 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
3145 void mm_dlog(char *string)
3147 ast_log(AST_LOG_NOTICE, "%s\n", string);
3151 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
3153 struct ast_vm_user *vmu;
3155 ast_debug(4, "Entering callback mm_login\n");
3157 ast_copy_string(user, mb->user, MAILTMPLEN);
3159 /* We should only do this when necessary */
3160 if (!ast_strlen_zero(authpassword)) {
3161 ast_copy_string(pwd, authpassword, MAILTMPLEN);
3163 AST_LIST_TRAVERSE(&users, vmu, list) {
3164 if (!strcasecmp(mb->user, vmu->imapuser)) {
3165 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
3170 if ((vmu = find_user_realtime_imapuser(mb->user))) {
3171 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
3179 void mm_critical(MAILSTREAM * stream)
3184 void mm_nocritical(MAILSTREAM * stream)
3189 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
3191 kill (getpid (), SIGSTOP);
3196 void mm_fatal(char *string)
3198 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
3201 /* C-client callback to handle quota */
3202 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
3204 struct vm_state *vms;
3205 char *mailbox = stream->mailbox, *user;
3206 char buf[1024] = "";
3207 unsigned long usage = 0, limit = 0;
3210 usage = pquota->usage;
3211 limit = pquota->limit;
3212 pquota = pquota->next;
3215 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || (!(vms = get_vm_state_by_imapuser(user, 2)) && !(vms = get_vm_state_by_imapuser(user, 0)))) {
3216 ast_log(AST_LOG_ERROR, "No state found.\n");
3220 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
3222 vms->quota_usage = usage;
3223 vms->quota_limit = limit;
3226 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
3228 char *start, *eol_pnt;
3231 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
3234 taglen = strlen(tag) + 1;
3238 if (!(start = strstr(header, tag)))
3241 /* Since we can be called multiple times we should clear our buffer */
3242 memset(buf, 0, len);
3244 ast_copy_string(buf, start+taglen, len);
3245 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
3250 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
3252 char *start, *eol_pnt, *quote;
3254 if (ast_strlen_zero(mailbox))
3257 if (!(start = strstr(mailbox, "/user=")))
3260 ast_copy_string(buf, start+6, len);
3262 if (!(quote = strchr(buf, '"'))) {
3263 if ((eol_pnt = strchr(buf, '/')) || (eol_pnt = strchr(buf, '}'))) {
3268 if ((eol_pnt = strchr(quote + 1, '"'))) {
3275 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
3277 struct vm_state *vms_p;
3279 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
3280 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
3283 ast_debug(5, "Adding new vmstate for %s\n", vmu->imapuser);
3284 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
3286 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
3287 ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
3288 ast_copy_string(vms_p->imapserver, vmu->imapserver, sizeof(vms_p->imapserver));
3289 ast_copy_string(vms_p->imapport, vmu->imapport, sizeof(vms_p->imapport));
3290 ast_copy_string(vms_p->imapflags, vmu->imapflags, sizeof(vms_p->imapflags));
3291 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
3292 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
3293 vms_p->mailstream = NIL; /* save for access from interactive entry point */
3294 vms_p->imapversion = vmu->imapversion;
3295 ast_debug(5, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
3297 /* set mailbox to INBOX! */
3298 ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
3299 init_vm_state(vms_p);
3300 vmstate_insert(vms_p);
3304 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
3306 struct vmstate *vlist = NULL;
3309 struct vm_state *vms;
3310 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
3311 vms = pthread_getspecific(ts_vmstate.key);
3315 AST_LIST_LOCK(&vmstates);
3316 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
3318 ast_debug(3, "error: vms is NULL for %s\n", user);
3321 if (vlist->vms->imapversion != imapversion) {
3324 if (!vlist->vms->imapuser) {
3325 ast_debug(3, "error: imapuser is NULL for %s\n", user);
3329 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
3330 AST_LIST_UNLOCK(&vmstates);
3334 AST_LIST_UNLOCK(&vmstates);
3336 ast_debug(3, "%s not found in vmstates\n", user);
3341 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
3344 struct vmstate *vlist = NULL;
3345 const char *local_context = S_OR(context, "default");
3348 struct vm_state *vms;
3349 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
3350 vms = pthread_getspecific(ts_vmstate.key);
3354 AST_LIST_LOCK(&vmstates);
3355 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
3357 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
3360 if (vlist->vms->imapversion != imapversion) {
3363 if (!vlist->vms->username || !vlist->vms->context) {
3364 ast_debug(3, "error: username is NULL for %s\n", mailbox);
3368 ast_debug(3, "comparing mailbox %s@%s (i=%d) to vmstate mailbox %s@%s (i=%d)\n", mailbox, local_context, interactive, vlist->vms->username, vlist->vms->context, vlist->vms->interactive);
3370 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
3371 ast_debug(3, "Found it!\n");
3372 AST_LIST_UNLOCK(&vmstates);
3376 AST_LIST_UNLOCK(&vmstates);
3378 ast_debug(3, "%s not found in vmstates\n", mailbox);
3383 static void vmstate_insert(struct vm_state *vms)
3386 struct vm_state *altvms;
3388 /* If interactive, it probably already exists, and we should
3389 use the one we already have since it is more up to date.
3390 We can compare the username to find the duplicate */
3391 if (vms->interactive == 1) {
3392 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
3394 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3395 vms->newmessages = altvms->newmessages;
3396 vms->oldmessages = altvms->oldmessages;
3397 vms->vmArrayIndex = altvms->vmArrayIndex;
3398 vms->lastmsg = altvms->lastmsg;
3399 vms->curmsg = altvms->curmsg;
3400 /* get a pointer to the persistent store */
3401 vms->persist_vms = altvms;
3402 /* Reuse the mailstream? */
3403 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
3404 vms->mailstream = altvms->mailstream;
3406 vms->mailstream = NIL;
3412 if (!(v = ast_calloc(1, sizeof(*v))))
3417 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3419 AST_LIST_LOCK(&vmstates);
3420 AST_LIST_INSERT_TAIL(&vmstates, v, list);
3421 AST_LIST_UNLOCK(&vmstates);
3424 static void vmstate_delete(struct vm_state *vms)
3426 struct vmstate *vc = NULL;
3427 struct vm_state *altvms = NULL;
3429 /* If interactive, we should copy pertinent info
3430 back to the persistent state (to make update immediate) */
3431 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
3432 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3433 altvms->newmessages = vms->newmessages;
3434 altvms->oldmessages = vms->oldmessages;
3435 altvms->updated = 1;
3436 vms->mailstream = mail_close(vms->mailstream);
3438 /* Interactive states are not stored within the persistent list */
3442 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3444 AST_LIST_LOCK(&vmstates);
3445 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
3446 if (vc->vms == vms) {
3447 AST_LIST_REMOVE_CURRENT(list);
3451 AST_LIST_TRAVERSE_SAFE_END
3452 AST_LIST_UNLOCK(&vmstates);
3455 ast_mutex_destroy(&vc->vms->lock);
3459 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3462 static void set_update(MAILSTREAM * stream)
3464 struct vm_state *vms;
3465 char *mailbox = stream->mailbox, *user;
3466 char buf[1024] = "";
3468 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
3469 if (user && option_debug > 2)
3470 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
3474 ast_debug(3, "User %s mailbox set for update.\n", user);
3476 vms->updated = 1; /* Set updated flag since mailbox changed */
3479 static void init_vm_state(struct vm_state *vms)
3482 vms->vmArrayIndex = 0;
3483 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
3484 vms->msgArray[x] = 0;
3486 ast_mutex_init(&vms->lock);
3489 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
3493 char *fn = is_intro ? vms->introfn : vms->fn;
3495 unsigned long newlen;
3498 if (!body || body == NIL)
3501 ast_mutex_lock(&vms->lock);
3502 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
3503 ast_mutex_unlock(&vms->lock);
3504 if (body_content != NIL) {
3505 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
3506 /* ast_debug(1, body_content); */
3507 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
3508 /* If the body of the file is empty, return an error */
3512 write_file(filename, (char *) body_decoded, newlen);
3514 ast_debug(5, "Body of message is NULL.\n");
3521 * \brief Get delimiter via mm_list callback
3522 * \param vms The voicemail state object
3525 * Determines the delimiter character that is used by the underlying IMAP based mail store.
3527 /* MUTEX should already be held */
3528 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream) {
3530 snprintf(tmp, sizeof(tmp), "{%s}", S_OR(vms->imapserver, imapserver));
3531 mail_list(stream, tmp, "*");
3535 * \brief Check Quota for user
3536 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3537 * \param mailbox the mailbox to check the quota for.
3539 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3541 static void check_quota(struct vm_state *vms, char *mailbox) {
3542 ast_mutex_lock(&vms->lock);
3543 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3544 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3545 if (vms && vms->mailstream != NULL) {
3546 imap_getquotaroot(vms->mailstream, mailbox);
3548 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3550 ast_mutex_unlock(&vms->lock);
3553 #endif /* IMAP_STORAGE */
3555 /*! \brief Lock file path
3556 * only return failure if ast_lock_path returns 'timeout',
3557 * not if the path does not exist or any