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 * \extref unixODBC (http://www.unixodbc.org/)
25 * \extref 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, read doc/imapstorage.txt
31 * \ingroup applications
32 * \note This module requires res_adsi to load. This needs to be optional
35 * \note 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.
41 <depend>res_smdi</depend>
45 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_voicemail.so apps/app_directory.o apps/app_directory.so">
46 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
47 <conflict>ODBC_STORAGE</conflict>
48 <conflict>IMAP_STORAGE</conflict>
49 <defaultenabled>yes</defaultenabled>
51 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
52 <depend>generic_odbc</depend>
54 <conflict>IMAP_STORAGE</conflict>
55 <conflict>FILE_STORAGE</conflict>
56 <defaultenabled>no</defaultenabled>
58 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
59 <depend>imap_tk</depend>
60 <conflict>ODBC_STORAGE</conflict>
61 <conflict>FILE_STORAGE</conflict>
63 <defaultenabled>no</defaultenabled>
74 #ifdef USE_SYSTEM_IMAP
75 #include <imap/c-client.h>
76 #include <imap/imap4r1.h>
77 #include <imap/linkage.h>
78 #elif defined (USE_SYSTEM_CCLIENT)
79 #include <c-client/c-client.h>
80 #include <c-client/imap4r1.h>
81 #include <c-client/linkage.h>
89 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
91 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
98 #include "asterisk/logger.h"
99 #include "asterisk/lock.h"
100 #include "asterisk/file.h"
101 #include "asterisk/channel.h"
102 #include "asterisk/pbx.h"
103 #include "asterisk/config.h"
104 #include "asterisk/say.h"
105 #include "asterisk/module.h"
106 #include "asterisk/adsi.h"
107 #include "asterisk/app.h"
108 #include "asterisk/manager.h"
109 #include "asterisk/dsp.h"
110 #include "asterisk/localtime.h"
111 #include "asterisk/cli.h"
112 #include "asterisk/utils.h"
113 #include "asterisk/stringfields.h"
114 #include "asterisk/smdi.h"
115 #include "asterisk/astobj2.h"
116 #include "asterisk/event.h"
117 #include "asterisk/taskprocessor.h"
120 #include "asterisk/res_odbc.h"
124 #include "asterisk/threadstorage.h"
128 <application name="VoiceMail" language="en_US">
130 Leave a Voicemail message.
133 <parameter name="mailboxs" argsep="&" required="true">
134 <argument name="mailbox1" argsep="@" required="true">
135 <argument name="mailbox" required="true" />
136 <argument name="context" />
138 <argument name="mailbox2" argsep="@" multiple="true">
139 <argument name="mailbox" required="true" />
140 <argument name="context" />
143 <parameter name="options">
146 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
149 <argument name="c" />
150 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
151 if played during the greeting. Context defaults to the current context.</para>
154 <argument name="#" required="true" />
155 <para>Use the specified amount of gain when recording the voicemail
156 message. The units are whole-number decibels (dB). Only works on supported
157 technologies, which is DAHDI only.</para>
160 <para>Skip the playback of instructions for leaving a message to the
161 calling party.</para>
164 <para>Play the <literal>unavailable</literal> greeting.</para>
167 <para>Mark message as <literal>URGENT</literal>.</para>
170 <para>Mark message as <literal>PRIORITY</literal>.</para>
176 <para>This application allows the calling party to leave a message for the specified
177 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
178 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
180 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
183 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
186 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
189 <para>This application will set the following channel variable upon completion:</para>
191 <variable name="VMSTATUS">
192 <para>This indicates the status of the execution of the VoiceMail application.</para>
193 <value name="SUCCESS" />
194 <value name="USEREXIT" />
195 <value name="FAILED" />
200 <application name="VoiceMailMain" language="en_US">
202 Check Voicemail messages.
205 <parameter name="mailbox" required="true" argsep="@">
206 <argument name="mailbox" />
207 <argument name="context" />
209 <parameter name="options">
212 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
213 the mailbox that is entered by the caller.</para>
216 <argument name="#" required="true" />
217 <para>Use the specified amount of gain when recording a voicemail message.
218 The units are whole-number decibels (dB).</para>
221 <para>Skip checking the passcode for the mailbox.</para>
224 <argument name="folder" required="true" />
225 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
226 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
228 <enum name="0"><para>INBOX</para></enum>
229 <enum name="1"><para>Old</para></enum>
230 <enum name="2"><para>Work</para></enum>
231 <enum name="3"><para>Family</para></enum>
232 <enum name="4"><para>Friends</para></enum>
233 <enum name="5"><para>Cust1</para></enum>
234 <enum name="6"><para>Cust2</para></enum>
235 <enum name="7"><para>Cust3</para></enum>
236 <enum name="8"><para>Cust4</para></enum>
237 <enum name="9"><para>Cust5</para></enum>
244 <para>This application allows the calling party to check voicemail messages. A specific
245 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
246 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
247 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
248 <literal>default</literal> context will be used.</para>
251 <application name="MailboxExists" language="en_US">
253 Check to see if Voicemail mailbox exists.
256 <parameter name="mailbox" required="true" argsep="@">
257 <argument name="mailbox" required="true" />
258 <argument name="context" />
260 <parameter name="options">
261 <para>None options.</para>
265 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
266 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
268 <para>This application will set the following channel variable upon completion:</para>
270 <variable name="VMBOXEXISTSSTATUS">
271 <para>This will contain the status of the execution of the MailboxExists application.
272 Possible values include:</para>
273 <value name="SUCCESS" />
274 <value name="FAILED" />
279 <application name="VMAuthenticate" language="en_US">
281 Authenticate with Voicemail passwords.
284 <parameter name="mailbox" required="true" argsep="@">
285 <argument name="mailbox" />
286 <argument name="context" />
288 <parameter name="options">
291 <para>Skip playing the initial prompts.</para>
297 <para>This application behaves the same way as the Authenticate application, but the passwords
298 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
299 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
300 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
304 <function name="MAILBOX_EXISTS" language="en_US">
306 Tell if a mailbox is configured.
309 <parameter name="mailbox" required="true" />
310 <parameter name="context" />
313 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
314 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
318 <manager name="VoicemailUsersList" language="en_US">
320 List All Voicemail User Information.
323 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
331 static char imapserver[48];
332 static char imapport[8];
333 static char imapflags[128];
334 static char imapfolder[64];
335 static char imapparentfolder[64] = "\0";
336 static char greetingfolder[64];
337 static char authuser[32];
338 static char authpassword[42];
339 static int imapversion = 1;
341 static int expungeonhangup = 1;
342 static int imapgreetings = 0;
343 static char delimiter = '\0';
348 AST_THREADSTORAGE(ts_vmstate);
350 /* Forward declarations for IMAP */
351 static int init_mailstream(struct vm_state *vms, int box);
352 static void write_file(char *filename, char *buffer, unsigned long len);
353 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
354 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
355 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
356 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
357 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
358 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
359 static void vmstate_insert(struct vm_state *vms);
360 static void vmstate_delete(struct vm_state *vms);
361 static void set_update(MAILSTREAM * stream);
362 static void init_vm_state(struct vm_state *vms);
363 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
364 static void get_mailbox_delimiter(MAILSTREAM *stream);
365 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
366 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
367 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
368 static void update_messages_by_imapuser(const char *user, unsigned long number);
369 static int vm_delete(char *file);
371 static int imap_remove_file (char *dir, int msgnum);
372 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
373 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
374 static void check_quota(struct vm_state *vms, char *mailbox);
375 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
377 struct vm_state *vms;
378 AST_LIST_ENTRY(vmstate) list;
381 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
385 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
387 #define COMMAND_TIMEOUT 5000
388 /* Don't modify these here; set your umask at runtime instead */
389 #define VOICEMAIL_DIR_MODE 0777
390 #define VOICEMAIL_FILE_MODE 0666
391 #define CHUNKSIZE 65536
393 #define VOICEMAIL_CONFIG "voicemail.conf"
394 #define ASTERISK_USERNAME "asterisk"
396 /* Define fast-forward, pause, restart, and reverse keys
397 while listening to a voicemail message - these are
398 strings, not characters */
399 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
400 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
401 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
402 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
403 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
404 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
406 /* Default mail command to mail voicemail. Change it with the
407 mailcmd= command in voicemail.conf */
408 #define SENDMAIL "/usr/sbin/sendmail -t"
410 #define INTRO "vm-intro"
413 #define MAXMSGLIMIT 9999
415 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
417 #define BASELINELEN 72
418 #define BASEMAXINLINE 256
421 #define MAX_DATETIME_FORMAT 512
422 #define MAX_NUM_CID_CONTEXTS 10
424 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
425 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
426 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
427 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
428 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
429 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
430 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
431 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
432 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
433 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
434 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
435 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
436 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
437 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
438 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
439 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
440 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
441 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
442 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
443 #define ERROR_LOCK_PATH -100
455 enum vm_option_flags {
456 OPT_SILENT = (1 << 0),
457 OPT_BUSY_GREETING = (1 << 1),
458 OPT_UNAVAIL_GREETING = (1 << 2),
459 OPT_RECORDGAIN = (1 << 3),
460 OPT_PREPEND_MAILBOX = (1 << 4),
461 OPT_AUTOPLAY = (1 << 6),
462 OPT_DTMFEXIT = (1 << 7),
463 OPT_MESSAGE_Urgent = (1 << 8),
464 OPT_MESSAGE_PRIORITY = (1 << 9)
467 enum vm_option_args {
468 OPT_ARG_RECORDGAIN = 0,
469 OPT_ARG_PLAYFOLDER = 1,
470 OPT_ARG_DTMFEXIT = 2,
471 /* This *must* be the last value in this enum! */
472 OPT_ARG_ARRAY_SIZE = 3,
475 enum vm_passwordlocation {
476 OPT_PWLOC_VOICEMAILCONF = 0,
477 OPT_PWLOC_SPOOLDIR = 1,
478 OPT_PWLOC_USERSCONF = 2,
481 AST_APP_OPTIONS(vm_app_options, {
482 AST_APP_OPTION('s', OPT_SILENT),
483 AST_APP_OPTION('b', OPT_BUSY_GREETING),
484 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
485 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
486 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
487 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
488 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
489 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
490 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
493 static int load_config(int reload);
495 /*! \page vmlang Voicemail Language Syntaxes Supported
497 \par Syntaxes supported, not really language codes.
504 \arg \b pt - Portuguese
505 \arg \b pt_BR - Portuguese (Brazil)
507 \arg \b no - Norwegian
509 \arg \b tw - Chinese (Taiwan)
510 \arg \b ua - Ukrainian
512 German requires the following additional soundfile:
513 \arg \b 1F einE (feminine)
515 Spanish requires the following additional soundfile:
516 \arg \b 1M un (masculine)
518 Dutch, Portuguese & Spanish require the following additional soundfiles:
519 \arg \b vm-INBOXs singular of 'new'
520 \arg \b vm-Olds singular of 'old/heard/read'
523 \arg \b vm-INBOX nieuwe (nl)
524 \arg \b vm-Old oude (nl)
527 \arg \b vm-new-a 'new', feminine singular accusative
528 \arg \b vm-new-e 'new', feminine plural accusative
529 \arg \b vm-new-ych 'new', feminine plural genitive
530 \arg \b vm-old-a 'old', feminine singular accusative
531 \arg \b vm-old-e 'old', feminine plural accusative
532 \arg \b vm-old-ych 'old', feminine plural genitive
533 \arg \b digits/1-a 'one', not always same as 'digits/1'
534 \arg \b digits/2-ie 'two', not always same as 'digits/2'
537 \arg \b vm-nytt singular of 'new'
538 \arg \b vm-nya plural of 'new'
539 \arg \b vm-gammalt singular of 'old'
540 \arg \b vm-gamla plural of 'old'
541 \arg \b digits/ett 'one', not always same as 'digits/1'
544 \arg \b vm-ny singular of 'new'
545 \arg \b vm-nye plural of 'new'
546 \arg \b vm-gammel singular of 'old'
547 \arg \b vm-gamle plural of 'old'
555 Italian requires the following additional soundfile:
559 \arg \b vm-nuovi new plural
560 \arg \b vm-vecchio old
561 \arg \b vm-vecchi old plural
563 Chinese (Taiwan) requires the following additional soundfile:
564 \arg \b vm-tong A class-word for call (tong1)
565 \arg \b vm-ri A class-word for day (ri4)
566 \arg \b vm-you You (ni3)
567 \arg \b vm-haveno Have no (mei2 you3)
568 \arg \b vm-have Have (you3)
569 \arg \b vm-listen To listen (yao4 ting1)
572 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
573 spelled among others when you have to change folder. For the above reasons, vm-INBOX
574 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
583 unsigned char iobuf[BASEMAXINLINE];
586 /*! Structure for linked list of users
587 * Use ast_vm_user_destroy() to free one of these structures. */
589 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
590 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
591 char password[80]; /*!< Secret pin code, numbers only */
592 char fullname[80]; /*!< Full name, for directory app */
593 char email[80]; /*!< E-mail address */
594 char *emailsubject; /*!< E-mail subject */
595 char *emailbody; /*!< E-mail body */
596 char pager[80]; /*!< E-mail address to pager (no attachment) */
597 char serveremail[80]; /*!< From: Mail address */
598 char mailcmd[160]; /*!< Configurable mail command */
599 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
600 char zonetag[80]; /*!< Time zone */
603 char uniqueid[80]; /*!< Unique integer identifier */
605 char attachfmt[20]; /*!< Attachment format */
606 unsigned int flags; /*!< VM_ flags */
608 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
609 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
610 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
611 int passwordlocation; /*!< Storage location of the password */
613 char imapuser[80]; /*!< IMAP server login */
614 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
615 char imapfolder[64]; /*!< IMAP voicemail folder */
616 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
617 int imapversion; /*!< If configuration changes, use the new values */
619 double volgain; /*!< Volume gain for voicemails sent via email */
620 AST_LIST_ENTRY(ast_vm_user) list;
623 /*! Voicemail time zones */
625 AST_LIST_ENTRY(vm_zone) list;
628 char msg_format[512];
631 #define VMSTATE_MAX_MSG_ARRAY 256
633 /*! Voicemail mailbox state */
638 char curdir[PATH_MAX];
639 char vmbox[PATH_MAX];
641 char intro[PATH_MAX];
653 int updated; /*!< decremented on each mail check until 1 -allows delay */
654 long msgArray[VMSTATE_MAX_MSG_ARRAY];
655 MAILSTREAM *mailstream;
657 char imapuser[80]; /*!< IMAP server login */
658 char imapfolder[64]; /*!< IMAP voicemail folder */
661 char introfn[PATH_MAX]; /*!< Name of prepended file */
662 unsigned int quota_limit;
663 unsigned int quota_usage;
664 struct vm_state *persist_vms;
669 static char odbc_database[80];
670 static char odbc_table[80];
671 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
672 #define DISPOSE(a,b) remove_file(a,b)
673 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
674 #define EXISTS(a,b,c,d) (message_exists(a,b))
675 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
676 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
677 #define DELETE(a,b,c,d) (delete_file(a,b))
680 #define DISPOSE(a,b) (imap_remove_file(a,b))
681 #define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j))
682 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
683 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
684 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
685 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
686 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
688 #define RETRIEVE(a,b,c,d)
690 #define STORE(a,b,c,d,e,f,g,h,i,j)
691 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
692 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
693 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
694 #define DELETE(a,b,c,d) (vm_delete(c))
698 static char VM_SPOOL_DIR[PATH_MAX];
700 static char ext_pass_cmd[128];
701 static char ext_pass_check_cmd[128];
705 #define PWDCHANGE_INTERNAL (1 << 1)
706 #define PWDCHANGE_EXTERNAL (1 << 2)
707 static int pwdchange = PWDCHANGE_INTERNAL;
710 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
713 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
715 # define tdesc "Comedian Mail (Voicemail System)"
719 static char userscontext[AST_MAX_EXTENSION] = "default";
721 static char *addesc = "Comedian Mail";
723 /* Leave a message */
724 static char *app = "VoiceMail";
726 /* Check mail, control, etc */
727 static char *app2 = "VoiceMailMain";
729 static char *app3 = "MailboxExists";
730 static char *app4 = "VMAuthenticate";
732 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
733 static AST_LIST_HEAD_STATIC(zones, vm_zone);
734 static char zonetag[80];
735 static int maxsilence;
737 static int maxdeletedmsg;
738 static int silencethreshold = 128;
739 static char serveremail[80];
740 static char mailcmd[160]; /* Configurable mail cmd */
741 static char externnotify[160];
742 static struct ast_smdi_interface *smdi_iface = NULL;
743 static char vmfmts[80];
744 static double volgain;
745 static int vmminsecs;
746 static int vmmaxsecs;
749 static int maxlogins;
750 static int minpassword;
751 static int passwordlocation;
753 /*! Poll mailboxes for changes since there is something external to
754 * app_voicemail that may change them. */
755 static unsigned int poll_mailboxes;
757 /*! Polling frequency */
758 static unsigned int poll_freq;
759 /*! By default, poll every 30 seconds */
760 #define DEFAULT_POLL_FREQ 30
762 AST_MUTEX_DEFINE_STATIC(poll_lock);
763 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
764 static pthread_t poll_thread = AST_PTHREADT_NULL;
765 static unsigned char poll_thread_run;
767 /*! Subscription to ... MWI event subscriptions */
768 static struct ast_event_sub *mwi_sub_sub;
769 /*! Subscription to ... MWI event un-subscriptions */
770 static struct ast_event_sub *mwi_unsub_sub;
773 * \brief An MWI subscription
775 * This is so we can keep track of which mailboxes are subscribed to.
776 * This way, we know which mailboxes to poll when the pollmailboxes
777 * option is being used.
780 AST_RWLIST_ENTRY(mwi_sub) entry;
788 struct mwi_sub_task {
794 static struct ast_taskprocessor *mwi_subscription_tps;
796 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
798 /* custom audio control prompts for voicemail playback */
799 static char listen_control_forward_key[12];
800 static char listen_control_reverse_key[12];
801 static char listen_control_pause_key[12];
802 static char listen_control_restart_key[12];
803 static char listen_control_stop_key[12];
805 /* custom password sounds */
806 static char vm_password[80] = "vm-password";
807 static char vm_newpassword[80] = "vm-newpassword";
808 static char vm_passchanged[80] = "vm-passchanged";
809 static char vm_reenterpassword[80] = "vm-reenterpassword";
810 static char vm_mismatch[80] = "vm-mismatch";
811 static char vm_invalid_password[80] = "vm-invalid-password";
812 static char vm_pls_try_again[80] = "vm-pls-try-again";
814 static struct ast_flags globalflags = {0};
816 static int saydurationminfo;
818 static char dialcontext[AST_MAX_CONTEXT] = "";
819 static char callcontext[AST_MAX_CONTEXT] = "";
820 static char exitcontext[AST_MAX_CONTEXT] = "";
822 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
825 static char *emailbody = NULL;
826 static char *emailsubject = NULL;
827 static char *pagerbody = NULL;
828 static char *pagersubject = NULL;
829 static char fromstring[100];
830 static char pagerfromstring[100];
831 static char charset[32] = "ISO-8859-1";
833 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
834 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
835 static int adsiver = 1;
836 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
837 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
839 /* Forward declarations - generic */
840 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
841 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);
842 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
843 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
844 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
845 signed char record_gain, struct vm_state *vms, char *flag);
846 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
847 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
848 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);
849 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);
850 static void apply_options(struct ast_vm_user *vmu, const char *options);
851 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);
852 static int is_valid_dtmf(const char *key);
853 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
854 static int write_password_to_file(const char *secretfn, const char *password);
856 struct ao2_container *inprocess_container;
864 static int inprocess_hash_fn(const void *obj, const int flags)
866 const struct inprocess *i = obj;
867 return atoi(i->mailbox);
870 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
872 struct inprocess *i = obj, *j = arg;
873 if (!strcmp(i->mailbox, j->mailbox)) {
876 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
879 static int inprocess_count(const char *context, const char *mailbox, int delta)
881 struct inprocess *i, *arg = alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
882 arg->context = arg->mailbox + strlen(mailbox) + 1;
883 strcpy(arg->mailbox, mailbox); /* SAFE */
884 strcpy(arg->context, context); /* SAFE */
885 ao2_lock(inprocess_container);
886 if ((i = ao2_find(inprocess_container, &arg, 0))) {
887 int ret = ast_atomic_fetchadd_int(&i->count, delta);
888 ao2_unlock(inprocess_container);
892 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
893 ao2_unlock(inprocess_container);
896 i->context = i->mailbox + strlen(mailbox) + 1;
897 strcpy(i->mailbox, mailbox); /* SAFE */
898 strcpy(i->context, context); /* SAFE */
900 ao2_link(inprocess_container, i);
901 ao2_unlock(inprocess_container);
906 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
907 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
911 * \brief Strips control and non 7-bit clean characters from input string.
913 * \note To map control and none 7-bit characters to a 7-bit clean characters
914 * please use ast_str_encode_mine().
916 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
919 for (; *input; input++) {
924 if (bufptr == buf + buflen - 1) {
934 * \brief Sets default voicemail system options to a voicemail user.
936 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
937 * - all the globalflags
938 * - the saydurationminfo
942 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
945 static void populate_defaults(struct ast_vm_user *vmu)
947 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
948 vmu->passwordlocation = passwordlocation;
949 if (saydurationminfo)
950 vmu->saydurationm = saydurationminfo;
951 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
952 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
953 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
954 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
956 vmu->maxsecs = vmmaxsecs;
958 vmu->maxmsg = maxmsg;
960 vmu->maxdeletedmsg = maxdeletedmsg;
961 vmu->volgain = volgain;
962 vmu->emailsubject = NULL;
963 vmu->emailbody = NULL;
965 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
970 * \brief Sets a a specific property value.
971 * \param vmu The voicemail user object to work with.
972 * \param var The name of the property to be set.
973 * \param value The value to be set to the property.
975 * The property name must be one of the understood properties. See the source for details.
977 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
980 if (!strcasecmp(var, "attach")) {
981 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
982 } else if (!strcasecmp(var, "attachfmt")) {
983 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
984 } else if (!strcasecmp(var, "serveremail")) {
985 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
986 } else if (!strcasecmp(var, "language")) {
987 ast_copy_string(vmu->language, value, sizeof(vmu->language));
988 } else if (!strcasecmp(var, "tz")) {
989 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
991 } else if (!strcasecmp(var, "imapuser")) {
992 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
993 vmu->imapversion = imapversion;
994 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
995 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
996 vmu->imapversion = imapversion;
997 } else if (!strcasecmp(var, "imapfolder")) {
998 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
999 } else if (!strcasecmp(var, "imapvmshareid")) {
1000 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1001 vmu->imapversion = imapversion;
1003 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1004 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1005 } else if (!strcasecmp(var, "saycid")){
1006 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1007 } else if (!strcasecmp(var, "sendvoicemail")){
1008 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1009 } else if (!strcasecmp(var, "review")){
1010 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1011 } else if (!strcasecmp(var, "tempgreetwarn")){
1012 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1013 } else if (!strcasecmp(var, "messagewrap")){
1014 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1015 } else if (!strcasecmp(var, "operator")) {
1016 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1017 } else if (!strcasecmp(var, "envelope")){
1018 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1019 } else if (!strcasecmp(var, "moveheard")){
1020 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1021 } else if (!strcasecmp(var, "sayduration")){
1022 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1023 } else if (!strcasecmp(var, "saydurationm")){
1024 if (sscanf(value, "%30d", &x) == 1) {
1025 vmu->saydurationm = x;
1027 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1029 } else if (!strcasecmp(var, "forcename")){
1030 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1031 } else if (!strcasecmp(var, "forcegreetings")){
1032 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1033 } else if (!strcasecmp(var, "callback")) {
1034 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1035 } else if (!strcasecmp(var, "dialout")) {
1036 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1037 } else if (!strcasecmp(var, "exitcontext")) {
1038 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1039 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1040 vmu->maxsecs = atoi(value);
1041 if (vmu->maxsecs <= 0) {
1042 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1043 vmu->maxsecs = vmmaxsecs;
1045 vmu->maxsecs = atoi(value);
1047 if (!strcasecmp(var, "maxmessage"))
1048 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1049 } else if (!strcasecmp(var, "maxmsg")) {
1050 vmu->maxmsg = atoi(value);
1051 /* Accept maxmsg=0 (Greetings only voicemail) */
1052 if (vmu->maxmsg < 0) {
1053 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1054 vmu->maxmsg = MAXMSG;
1055 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1056 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1057 vmu->maxmsg = MAXMSGLIMIT;
1059 } else if (!strcasecmp(var, "backupdeleted")) {
1060 if (sscanf(value, "%30d", &x) == 1)
1061 vmu->maxdeletedmsg = x;
1062 else if (ast_true(value))
1063 vmu->maxdeletedmsg = MAXMSG;
1065 vmu->maxdeletedmsg = 0;
1067 if (vmu->maxdeletedmsg < 0) {
1068 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1069 vmu->maxdeletedmsg = MAXMSG;
1070 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1071 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1072 vmu->maxdeletedmsg = MAXMSGLIMIT;
1074 } else if (!strcasecmp(var, "volgain")) {
1075 sscanf(value, "%30lf", &vmu->volgain);
1076 } else if (!strcasecmp(var, "passwordlocation")) {
1077 if (!strcasecmp(value, "spooldir")) {
1078 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1080 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1082 } else if (!strcasecmp(var, "options")) {
1083 apply_options(vmu, value);
1087 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1089 int fds[2], pid = 0;
1091 memset(buf, 0, len);
1094 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1097 pid = ast_safe_fork(0);
1103 snprintf(buf, len, "FAILURE: Fork failed");
1107 if (read(fds[0], buf, len) < 0) {
1108 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1113 AST_DECLARE_APP_ARGS(arg,
1116 char *mycmd = ast_strdupa(command);
1119 dup2(fds[1], STDOUT_FILENO);
1121 ast_close_fds_above_n(STDOUT_FILENO);
1123 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1125 execv(arg.v[0], arg.v);
1126 printf("FAILURE: %s", strerror(errno));
1134 * \brief Check that password meets minimum required length
1135 * \param vmu The voicemail user to change the password for.
1136 * \param password The password string to check
1138 * \return zero on ok, 1 on not ok.
1140 static int check_password(struct ast_vm_user *vmu, char *password)
1142 /* check minimum length */
1143 if (strlen(password) < minpassword)
1145 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1146 char cmd[255], buf[255];
1148 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1150 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1151 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1152 ast_debug(5, "Result: %s\n", buf);
1153 if (!strncasecmp(buf, "VALID", 5)) {
1154 ast_debug(3, "Passed password check: '%s'\n", buf);
1156 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1157 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1160 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1169 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1170 * \param vmu The voicemail user to change the password for.
1171 * \param password The new value to be set to the password for this user.
1173 * This only works if there is a realtime engine configured.
1174 * This is called from the (top level) vm_change_password.
1176 * \return zero on success, -1 on error.
1178 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1181 if (!strcmp(vmu->password, password)) {
1182 /* No change (but an update would return 0 rows updated, so we opt out here) */
1186 if (strlen(password) > 10) {
1187 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1189 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1190 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1197 * \brief Destructively Parse options and apply.
1199 static void apply_options(struct ast_vm_user *vmu, const char *options)
1204 stringp = ast_strdupa(options);
1205 while ((s = strsep(&stringp, "|"))) {
1207 if ((var = strsep(&value, "=")) && value) {
1208 apply_option(vmu, var, value);
1214 * \brief Loads the options specific to a voicemail user.
1216 * This is called when a vm_user structure is being set up, such as from load_options.
1218 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1220 for (; var; var = var->next) {
1221 if (!strcasecmp(var->name, "vmsecret")) {
1222 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1223 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1224 if (ast_strlen_zero(retval->password))
1225 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1226 } else if (!strcasecmp(var->name, "uniqueid")) {
1227 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1228 } else if (!strcasecmp(var->name, "pager")) {
1229 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1230 } else if (!strcasecmp(var->name, "email")) {
1231 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1232 } else if (!strcasecmp(var->name, "fullname")) {
1233 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1234 } else if (!strcasecmp(var->name, "context")) {
1235 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1236 } else if (!strcasecmp(var->name, "emailsubject")) {
1237 retval->emailsubject = ast_strdup(var->value);
1238 } else if (!strcasecmp(var->name, "emailbody")) {
1239 retval->emailbody = ast_strdup(var->value);
1241 } else if (!strcasecmp(var->name, "imapuser")) {
1242 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1243 retval->imapversion = imapversion;
1244 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1245 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1246 retval->imapversion = imapversion;
1247 } else if (!strcasecmp(var->name, "imapfolder")) {
1248 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1249 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1250 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1251 retval->imapversion = imapversion;
1254 apply_option(retval, var->name, var->value);
1259 * \brief Determines if a DTMF key entered is valid.
1260 * \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.
1262 * Tests the character entered against the set of valid DTMF characters.
1263 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1265 static int is_valid_dtmf(const char *key)
1268 char *local_key = ast_strdupa(key);
1270 for (i = 0; i < strlen(key); ++i) {
1271 if (!strchr(VALID_DTMF, *local_key)) {
1272 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1281 * \brief Finds a voicemail user from the realtime engine.
1286 * 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.
1288 * \return The ast_vm_user structure for the user that was found.
1290 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1292 struct ast_variable *var;
1293 struct ast_vm_user *retval;
1295 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1297 ast_set_flag(retval, VM_ALLOCED);
1299 memset(retval, 0, sizeof(*retval));
1301 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1302 populate_defaults(retval);
1303 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1304 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1306 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1308 apply_options_full(retval, var);
1309 ast_variables_destroy(var);
1320 * \brief Finds a voicemail user from the users file or the realtime engine.
1325 * \return The ast_vm_user structure for the user that was found.
1327 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1329 /* This function could be made to generate one from a database, too */
1330 struct ast_vm_user *vmu = NULL, *cur;
1331 AST_LIST_LOCK(&users);
1333 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1334 context = "default";
1336 AST_LIST_TRAVERSE(&users, cur, list) {
1338 if (cur->imapversion != imapversion) {
1342 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1344 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1348 /* Make a copy, so that on a reload, we have no race */
1349 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1350 memcpy(vmu, cur, sizeof(*vmu));
1351 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1352 AST_LIST_NEXT(vmu, list) = NULL;
1355 vmu = find_user_realtime(ivm, context, mailbox);
1356 AST_LIST_UNLOCK(&users);
1361 * \brief Resets a user password to a specified password.
1366 * This does the actual change password work, called by the vm_change_password() function.
1368 * \return zero on success, -1 on error.
1370 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1372 /* This function could be made to generate one from a database, too */
1373 struct ast_vm_user *cur;
1375 AST_LIST_LOCK(&users);
1376 AST_LIST_TRAVERSE(&users, cur, list) {
1377 if ((!context || !strcasecmp(context, cur->context)) &&
1378 (!strcasecmp(mailbox, cur->mailbox)))
1382 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1385 AST_LIST_UNLOCK(&users);
1390 * \brief The handler for the change password option.
1391 * \param vmu The voicemail user to work with.
1392 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1393 * 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.
1394 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1396 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1398 struct ast_config *cfg = NULL;
1399 struct ast_variable *var = NULL;
1400 struct ast_category *cat = NULL;
1401 char *category = NULL, *value = NULL, *new = NULL;
1402 const char *tmp = NULL;
1403 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1404 char secretfn[PATH_MAX] = "";
1407 if (!change_password_realtime(vmu, newpassword))
1410 /* check if we should store the secret in the spool directory next to the messages */
1411 switch (vmu->passwordlocation) {
1412 case OPT_PWLOC_SPOOLDIR:
1413 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1414 if (write_password_to_file(secretfn, newpassword) == 0) {
1415 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1416 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1417 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1420 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1423 case OPT_PWLOC_VOICEMAILCONF:
1424 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1425 while ((category = ast_category_browse(cfg, category))) {
1426 if (!strcasecmp(category, vmu->context)) {
1427 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1428 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1431 value = strstr(tmp, ",");
1433 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1436 new = alloca((strlen(value) + strlen(newpassword) + 1));
1437 sprintf(new, "%s%s", newpassword, value);
1438 if (!(cat = ast_category_get(cfg, category))) {
1439 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1442 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1446 /* save the results */
1448 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1449 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1450 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1455 case OPT_PWLOC_USERSCONF:
1456 /* check users.conf and update the password stored for the mailbox */
1457 /* if no vmsecret entry exists create one. */
1458 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1459 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1460 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1461 ast_debug(4, "users.conf: %s\n", category);
1462 if (!strcasecmp(category, vmu->mailbox)) {
1463 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1464 ast_debug(3, "looks like we need to make vmsecret!\n");
1465 var = ast_variable_new("vmsecret", newpassword, "");
1469 new = alloca(strlen(newpassword) + 1);
1470 sprintf(new, "%s", newpassword);
1471 if (!(cat = ast_category_get(cfg, category))) {
1472 ast_debug(4, "failed to get category!\n");
1477 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1479 ast_variable_append(cat, var);
1485 /* save the results and clean things up */
1487 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1488 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1489 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1495 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1498 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1499 if (!ast_safe_system(buf)) {
1500 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1501 /* Reset the password in memory, too */
1502 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1507 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1508 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1509 * \param len The length of the path string that was written out.
1511 * The path is constructed as
1512 * VM_SPOOL_DIRcontext/ext/folder
1514 * \return zero on success, -1 on error.
1516 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1518 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1522 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1523 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1524 * \param len The length of the path string that was written out.
1526 * The path is constructed as
1527 * VM_SPOOL_DIRcontext/ext/folder
1529 * \return zero on success, -1 on error.
1531 static int make_file(char *dest, const int len, const char *dir, const int num)
1533 return snprintf(dest, len, "%s/msg%04d", dir, num);
1536 /* same as mkstemp, but return a FILE * */
1537 static FILE *vm_mkftemp(char *template)
1540 int pfd = mkstemp(template);
1541 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1543 p = fdopen(pfd, "w+");
1552 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1553 * \param dest String. base directory.
1554 * \param len Length of dest.
1555 * \param context String. Ignored if is null or empty string.
1556 * \param ext String. Ignored if is null or empty string.
1557 * \param folder String. Ignored if is null or empty string.
1558 * \return -1 on failure, 0 on success.
1560 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1562 mode_t mode = VOICEMAIL_DIR_MODE;
1565 make_dir(dest, len, context, ext, folder);
1566 if ((res = ast_mkdir(dest, mode))) {
1567 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1573 static const char * const mailbox_folders[] = {
1592 static const char *mbox(struct ast_vm_user *vmu, int id)
1595 if (vmu && id == 0) {
1596 return vmu->imapfolder;
1599 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1602 static int get_folder_by_name(const char *name)
1606 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1607 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1615 static void free_user(struct ast_vm_user *vmu)
1617 if (ast_test_flag(vmu, VM_ALLOCED)) {
1618 if (vmu->emailbody != NULL) {
1619 ast_free(vmu->emailbody);
1620 vmu->emailbody = NULL;
1622 if (vmu->emailsubject != NULL) {
1623 ast_free(vmu->emailsubject);
1624 vmu->emailsubject = NULL;
1630 /* All IMAP-specific functions should go in this block. This
1631 * keeps them from being spread out all over the code */
1633 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1636 struct vm_state *vms;
1637 unsigned long messageNum;
1639 /* If greetings aren't stored in IMAP, just delete the file */
1640 if (msgnum < 0 && !imapgreetings) {
1641 ast_filedelete(file, NULL);
1645 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1646 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);
1650 /* find real message number based on msgnum */
1651 /* this may be an index into vms->msgArray based on the msgnum. */
1652 messageNum = vms->msgArray[msgnum];
1653 if (messageNum == 0) {
1654 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1657 if (option_debug > 2)
1658 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1659 /* delete message */
1660 snprintf (arg, sizeof(arg), "%lu", messageNum);
1661 ast_mutex_lock(&vms->lock);
1662 mail_setflag (vms->mailstream, arg, "\\DELETED");
1663 mail_expunge(vms->mailstream);
1664 ast_mutex_unlock(&vms->lock);
1667 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1669 struct vm_state *vms_p;
1670 char *file, *filename;
1675 /* This function is only used for retrieval of IMAP greetings
1676 * regular messages are not retrieved this way, nor are greetings
1677 * if they are stored locally*/
1678 if (msgnum > -1 || !imapgreetings) {
1681 file = strrchr(ast_strdupa(dir), '/');
1685 ast_debug (1, "Failed to procure file name from directory passed.\n");
1690 /* check if someone is accessing this box right now... */
1691 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
1692 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1693 /* Unlike when retrieving a message, it is reasonable not to be able to find a
1694 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1695 * that's all we need to do.
1697 if (!(vms_p = create_vm_state_from_user(vmu))) {
1698 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1703 /* Greetings will never have a prepended message */
1704 *vms_p->introfn = '\0';
1706 ast_mutex_lock(&vms_p->lock);
1707 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1708 if (!vms_p->mailstream) {
1709 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1710 ast_mutex_unlock(&vms_p->lock);
1714 /*XXX Yuck, this could probably be done a lot better */
1715 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1716 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1717 /* We have the body, now we extract the file name of the first attachment. */
1718 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1719 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1721 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1722 ast_mutex_unlock(&vms_p->lock);
1725 filename = strsep(&attachment, ".");
1726 if (!strcmp(filename, file)) {
1727 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1728 vms_p->msgArray[vms_p->curmsg] = i + 1;
1729 save_body(body, vms_p, "2", attachment, 0);
1730 ast_mutex_unlock(&vms_p->lock);
1734 ast_mutex_unlock(&vms_p->lock);
1739 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1742 char *header_content;
1743 char *attachedfilefmt;
1745 struct vm_state *vms;
1746 char text_file[PATH_MAX];
1747 FILE *text_file_ptr;
1749 struct ast_vm_user *vmu;
1751 if (!(vmu = find_user(NULL, context, mailbox))) {
1752 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1757 if (imapgreetings) {
1758 res = imap_retrieve_greeting(dir, msgnum, vmu);
1766 /* Before anything can happen, we need a vm_state so that we can
1767 * actually access the imap server through the vms->mailstream
1769 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1770 /* This should not happen. If it does, then I guess we'd
1771 * need to create the vm_state, extract which mailbox to
1772 * open, and then set up the msgArray so that the correct
1773 * IMAP message could be accessed. If I have seen correctly
1774 * though, the vms should be obtainable from the vmstates list
1775 * and should have its msgArray properly set up.
1777 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1782 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1783 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1785 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1786 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1791 if (option_debug > 2)
1792 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1793 if (vms->msgArray[msgnum] == 0) {
1794 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1799 /* This will only work for new messages... */
1800 ast_mutex_lock(&vms->lock);
1801 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1802 ast_mutex_unlock(&vms->lock);
1803 /* empty string means no valid header */
1804 if (ast_strlen_zero(header_content)) {
1805 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1810 ast_mutex_lock(&vms->lock);
1811 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1812 ast_mutex_unlock(&vms->lock);
1814 /* We have the body, now we extract the file name of the first attachment. */
1815 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1816 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1818 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1823 /* Find the format of the attached file */
1825 strsep(&attachedfilefmt, ".");
1826 if (!attachedfilefmt) {
1827 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1832 save_body(body, vms, "2", attachedfilefmt, 0);
1833 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1834 *vms->introfn = '\0';
1837 /* Get info from headers!! */
1838 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1840 if (!(text_file_ptr = fopen(text_file, "w"))) {
1841 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1844 fprintf(text_file_ptr, "%s\n", "[message]");
1846 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1847 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1848 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1849 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1850 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1851 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1852 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1853 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1854 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1855 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1856 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1857 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1858 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1859 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1860 fclose(text_file_ptr);
1867 static int folder_int(const char *folder)
1869 /*assume a NULL folder means INBOX*/
1872 if (!strcasecmp(folder, imapfolder))
1874 else if (!strcasecmp(folder, "Old"))
1876 else if (!strcasecmp(folder, "Work"))
1878 else if (!strcasecmp(folder, "Family"))
1880 else if (!strcasecmp(folder, "Friends"))
1882 else if (!strcasecmp(folder, "Cust1"))
1884 else if (!strcasecmp(folder, "Cust2"))
1886 else if (!strcasecmp(folder, "Cust3"))
1888 else if (!strcasecmp(folder, "Cust4"))
1890 else if (!strcasecmp(folder, "Cust5"))
1892 else /*assume they meant INBOX if folder is not found otherwise*/
1897 * \brief Gets the number of messages that exist in a mailbox folder.
1902 * This method is used when IMAP backend is used.
1903 * \return The number of messages in this mailbox folder (zero or more).
1905 static int messagecount(const char *context, const char *mailbox, const char *folder)
1910 struct ast_vm_user *vmu, vmus;
1911 struct vm_state *vms_p;
1913 int fold = folder_int(folder);
1916 /* If URGENT, then look at INBOX */
1922 if (ast_strlen_zero(mailbox))
1925 /* We have to get the user before we can open the stream! */
1926 vmu = find_user(&vmus, context, mailbox);
1928 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1931 /* No IMAP account available */
1932 if (vmu->imapuser[0] == '\0') {
1933 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1938 /* No IMAP account available */
1939 if (vmu->imapuser[0] == '\0') {
1940 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1945 /* check if someone is accessing this box right now... */
1946 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1948 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1951 ast_debug(3, "Returning before search - user is logged in\n");
1952 if (fold == 0) { /* INBOX */
1953 return vms_p->newmessages;
1955 if (fold == 1) { /* Old messages */
1956 return vms_p->oldmessages;
1958 if (fold == 11) {/*Urgent messages*/
1959 return vms_p->urgentmessages;
1963 /* add one if not there... */
1964 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
1966 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1970 vms_p = create_vm_state_from_user(vmu);
1972 ret = init_mailstream(vms_p, fold);
1973 if (!vms_p->mailstream) {
1974 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1978 ast_mutex_lock(&vms_p->lock);
1979 pgm = mail_newsearchpgm ();
1980 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
1981 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
1987 /* In the special case where fold is 1 (old messages) we have to do things a bit
1988 * differently. Old messages are stored in the INBOX but are marked as "seen"
1994 /* look for urgent messages */
2002 vms_p->vmArrayIndex = 0;
2003 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2004 if (fold == 0 && urgent == 0)
2005 vms_p->newmessages = vms_p->vmArrayIndex;
2007 vms_p->oldmessages = vms_p->vmArrayIndex;
2008 if (fold == 0 && urgent == 1)
2009 vms_p->urgentmessages = vms_p->vmArrayIndex;
2010 /*Freeing the searchpgm also frees the searchhdr*/
2011 mail_free_searchpgm(&pgm);
2012 ast_mutex_unlock(&vms_p->lock);
2014 return vms_p->vmArrayIndex;
2016 ast_mutex_lock(&vms_p->lock);
2017 mail_ping(vms_p->mailstream);
2018 ast_mutex_unlock(&vms_p->lock);
2023 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
2025 char *myserveremail = serveremail;
2027 char introfn[PATH_MAX];
2031 char tmp[80] = "/tmp/astmail-XXXXXX";
2036 int ret; /* for better error checking */
2037 char *imap_flags = NIL;
2039 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2040 if (msgnum < 0 && !imapgreetings) {
2044 /* Set urgent flag for IMAP message */
2045 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2046 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2047 imap_flags = "\\FLAGGED";
2050 /* Attach only the first format */
2051 fmt = ast_strdupa(fmt);
2053 strsep(&stringp, "|");
2055 if (!ast_strlen_zero(vmu->serveremail))
2056 myserveremail = vmu->serveremail;
2059 make_file(fn, sizeof(fn), dir, msgnum);
2061 ast_copy_string (fn, dir, sizeof(fn));
2063 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2064 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2068 if (ast_strlen_zero(vmu->email)) {
2069 /* We need the vmu->email to be set when we call make_email_file, but
2070 * if we keep it set, a duplicate e-mail will be created. So at the end
2071 * of this function, we will revert back to an empty string if tempcopy
2074 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2078 if (!strcmp(fmt, "wav49"))
2080 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2082 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2084 if (!(p = vm_mkftemp(tmp))) {
2085 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2087 *(vmu->email) = '\0';
2091 if (msgnum < 0 && imapgreetings) {
2092 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2093 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2096 imap_delete_old_greeting(fn, vms);
2099 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX", S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
2100 /* read mail file to memory */
2103 if (!(buf = ast_malloc(len + 1))) {
2104 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2107 *(vmu->email) = '\0';
2110 if (fread(buf, len, 1, p) < len) {
2112 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2116 ((char *) buf)[len] = '\0';
2117 INIT(&str, mail_string, buf, len);
2118 ret = init_mailstream(vms, NEW_FOLDER);
2120 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2121 ast_mutex_lock(&vms->lock);
2122 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2123 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2124 ast_mutex_unlock(&vms->lock);
2129 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2135 ast_debug(3, "%s stored\n", fn);
2138 *(vmu->email) = '\0';
2145 * \brief Gets the number of messages that exist in the inbox folder.
2146 * \param mailbox_context
2147 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2148 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2149 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2151 * This method is used when IMAP backend is used.
2152 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2154 * \return zero on success, -1 on error.
2157 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2159 char tmp[PATH_MAX] = "";
2171 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2172 /* If no mailbox, return immediately */
2173 if (ast_strlen_zero(mailbox_context))
2176 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2177 context = strchr(tmp, '@');
2178 if (strchr(mailbox_context, ',')) {
2179 int tmpnew, tmpold, tmpurgent;
2180 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2182 while ((cur = strsep(&mb, ", "))) {
2183 if (!ast_strlen_zero(cur)) {
2184 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2192 *urgentmsgs += tmpurgent;
2203 context = "default";
2204 mailboxnc = (char *) mailbox_context;
2208 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2210 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2213 if ((*newmsgs = messagecount(context, mailboxnc, vmu->imapfolder)) < 0)
2217 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2221 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
2227 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2229 return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
2233 * \brief Determines if the given folder has messages.
2234 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2235 * \param folder the folder to look in
2237 * This function is used when the mailbox is stored in an IMAP back end.
2238 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2239 * \return 1 if the folder has one or more messages. zero otherwise.
2242 static int has_voicemail(const char *mailbox, const char *folder)
2244 char tmp[256], *tmp2, *box, *context;
2245 ast_copy_string(tmp, mailbox, sizeof(tmp));
2247 if (strchr(tmp2, ',')) {
2248 while ((box = strsep(&tmp2, ","))) {
2249 if (!ast_strlen_zero(box)) {
2250 if (has_voicemail(box, folder))
2255 if ((context = strchr(tmp, '@')))
2258 context = "default";
2259 return messagecount(context, tmp, folder) ? 1 : 0;
2263 * \brief Copies a message from one mailbox to another.
2273 * This works with IMAP storage based mailboxes.
2275 * \return zero on success, -1 on error.
2277 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)
2279 struct vm_state *sendvms = NULL, *destvms = NULL;
2280 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2281 if (msgnum >= recip->maxmsg) {
2282 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2285 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2286 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2289 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2290 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2293 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2294 ast_mutex_lock(&sendvms->lock);
2295 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2296 ast_mutex_unlock(&sendvms->lock);
2299 ast_mutex_unlock(&sendvms->lock);
2300 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2304 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2306 char tmp[256], *t = tmp;
2307 size_t left = sizeof(tmp);
2309 if (box == OLD_FOLDER) {
2310 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2312 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2315 if (box == NEW_FOLDER) {
2316 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2318 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2321 /* Build up server information */
2322 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2324 /* Add authentication user if present */
2325 if (!ast_strlen_zero(authuser))
2326 ast_build_string(&t, &left, "/authuser=%s", authuser);
2328 /* Add flags if present */
2329 if (!ast_strlen_zero(imapflags))
2330 ast_build_string(&t, &left, "/%s", imapflags);
2332 /* End with username */
2334 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2336 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2338 if (box == NEW_FOLDER || box == OLD_FOLDER)
2339 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2340 else if (box == GREETINGS_FOLDER)
2341 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2342 else { /* Other folders such as Friends, Family, etc... */
2343 if (!ast_strlen_zero(imapparentfolder)) {
2344 /* imapparentfolder would typically be set to INBOX */
2345 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2347 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2352 static int init_mailstream(struct vm_state *vms, int box)
2354 MAILSTREAM *stream = NIL;
2359 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2362 if (option_debug > 2)
2363 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2364 if (vms->mailstream == NIL || !vms->mailstream) {
2366 ast_log(LOG_DEBUG, "mailstream not set.\n");
2368 stream = vms->mailstream;
2370 /* debug = T; user wants protocol telemetry? */
2371 debug = NIL; /* NO protocol telemetry? */
2373 if (delimiter == '\0') { /* did not probe the server yet */
2375 #ifdef USE_SYSTEM_IMAP
2376 #include <imap/linkage.c>
2377 #elif defined(USE_SYSTEM_CCLIENT)
2378 #include <c-client/linkage.c>
2380 #include "linkage.c"
2382 /* Connect to INBOX first to get folders delimiter */
2383 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2384 ast_mutex_lock(&vms->lock);
2385 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2386 ast_mutex_unlock(&vms->lock);
2387 if (stream == NIL) {
2388 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2391 get_mailbox_delimiter(stream);
2392 /* update delimiter in imapfolder */
2393 for (cp = vms->imapfolder; *cp; cp++)
2397 /* Now connect to the target folder */
2398 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2399 if (option_debug > 2)
2400 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2401 ast_mutex_lock(&vms->lock);
2402 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2403 ast_mutex_unlock(&vms->lock);
2404 if (vms->mailstream == NIL) {
2411 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2415 int ret, urgent = 0;
2417 /* If Urgent, then look at INBOX */
2423 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2424 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2425 vms->imapversion = vmu->imapversion;
2426 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2428 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2429 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2433 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2437 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2438 check_quota(vms, (char *) mbox(vmu, box));
2441 ast_mutex_lock(&vms->lock);
2442 pgm = mail_newsearchpgm();
2444 /* Check IMAP folder for Asterisk messages only... */
2445 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2446 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2451 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2452 if (box == NEW_FOLDER && urgent == 1) {
2457 } else if (box == NEW_FOLDER && urgent == 0) {
2462 } else if (box == OLD_FOLDER) {
2467 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2469 vms->vmArrayIndex = 0;
2470 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2471 vms->lastmsg = vms->vmArrayIndex - 1;
2472 mail_free_searchpgm(&pgm);
2474 ast_mutex_unlock(&vms->lock);
2478 static void write_file(char *filename, char *buffer, unsigned long len)
2482 output = fopen (filename, "w");
2483 if (fwrite(buffer, len, 1, output) != 1) {
2484 if (ferror(output)) {
2485 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2491 static void update_messages_by_imapuser(const char *user, unsigned long number)
2493 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2495 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2499 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2500 vms->msgArray[vms->vmArrayIndex++] = number;
2503 void mm_searched(MAILSTREAM *stream, unsigned long number)
2505 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2507 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2510 update_messages_by_imapuser(user, number);
2513 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2515 struct ast_variable *var;
2516 struct ast_vm_user *vmu;
2518 vmu = ast_calloc(1, sizeof *vmu);
2521 ast_set_flag(vmu, VM_ALLOCED);
2522 populate_defaults(vmu);
2524 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2526 apply_options_full(vmu, var);
2527 ast_variables_destroy(var);
2535 /* Interfaces to C-client */
2537 void mm_exists(MAILSTREAM * stream, unsigned long number)
2539 /* mail_ping will callback here if new mail! */
2540 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2541 if (number == 0) return;
2546 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2548 /* mail_ping will callback here if expunged mail! */
2549 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2550 if (number == 0) return;
2555 void mm_flags(MAILSTREAM * stream, unsigned long number)
2557 /* mail_ping will callback here if read mail! */
2558 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2559 if (number == 0) return;
2564 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2566 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2567 mm_log (string, errflg);
2571 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2573 if (delimiter == '\0') {
2577 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2578 if (attributes & LATT_NOINFERIORS)
2579 ast_debug(5, "no inferiors\n");
2580 if (attributes & LATT_NOSELECT)
2581 ast_debug(5, "no select\n");
2582 if (attributes & LATT_MARKED)
2583 ast_debug(5, "marked\n");
2584 if (attributes & LATT_UNMARKED)
2585 ast_debug(5, "unmarked\n");
2589 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2591 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2592 if (attributes & LATT_NOINFERIORS)
2593 ast_debug(5, "no inferiors\n");
2594 if (attributes & LATT_NOSELECT)
2595 ast_debug(5, "no select\n");
2596 if (attributes & LATT_MARKED)
2597 ast_debug(5, "marked\n");
2598 if (attributes & LATT_UNMARKED)
2599 ast_debug(5, "unmarked\n");
2603 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2605 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2606 if (status->flags & SA_MESSAGES)
2607 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2608 if (status->flags & SA_RECENT)
2609 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2610 if (status->flags & SA_UNSEEN)
2611 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2612 if (status->flags & SA_UIDVALIDITY)
2613 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2614 if (status->flags & SA_UIDNEXT)
2615 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2616 ast_log(AST_LOG_NOTICE, "\n");
2620 void mm_log(char *string, long errflg)
2622 switch ((short) errflg) {
2624 ast_debug(1, "IMAP Info: %s\n", string);
2628 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2631 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2637 void mm_dlog(char *string)
2639 ast_log(AST_LOG_NOTICE, "%s\n", string);
2643 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2645 struct ast_vm_user *vmu;
2647 ast_debug(4, "Entering callback mm_login\n");
2649 ast_copy_string(user, mb->user, MAILTMPLEN);
2651 /* We should only do this when necessary */
2652 if (!ast_strlen_zero(authpassword)) {
2653 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2655 AST_LIST_TRAVERSE(&users, vmu, list) {
2656 if (!strcasecmp(mb->user, vmu->imapuser)) {
2657 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2662 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2663 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2671 void mm_critical(MAILSTREAM * stream)
2676 void mm_nocritical(MAILSTREAM * stream)
2681 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2683 kill (getpid (), SIGSTOP);
2688 void mm_fatal(char *string)
2690 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2693 /* C-client callback to handle quota */
2694 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2696 struct vm_state *vms;
2697 char *mailbox = stream->mailbox, *user;
2698 char buf[1024] = "";
2699 unsigned long usage = 0, limit = 0;
2702 usage = pquota->usage;
2703 limit = pquota->limit;
2704 pquota = pquota->next;
2707 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)))) {
2708 ast_log(AST_LOG_ERROR, "No state found.\n");
2712 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2714 vms->quota_usage = usage;
2715 vms->quota_limit = limit;
2718 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2720 char *start, *eol_pnt;
2723 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2726 taglen = strlen(tag) + 1;
2730 if (!(start = strstr(header, tag)))
2733 /* Since we can be called multiple times we should clear our buffer */
2734 memset(buf, 0, len);
2736 ast_copy_string(buf, start+taglen, len);
2737 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2742 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2744 char *start, *quote, *eol_pnt;
2746 if (ast_strlen_zero(mailbox))
2749 if (!(start = strstr(mailbox, "/user=")))
2752 ast_copy_string(buf, start+6, len);
2754 if (!(quote = strchr(buf, '\"'))) {
2755 if (!(eol_pnt = strchr(buf, '/')))
2756 eol_pnt = strchr(buf,'}');
2760 eol_pnt = strchr(buf+1,'\"');
2766 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2768 struct vm_state *vms_p;
2770 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2771 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2774 if (option_debug > 4)
2775 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2776 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2778 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2779 ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
2780 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2781 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2782 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2783 vms_p->imapversion = vmu->imapversion;
2784 if (option_debug > 4)
2785 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2787 /* set mailbox to INBOX! */
2788 ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
2789 init_vm_state(vms_p);
2790 vmstate_insert(vms_p);
2794 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2796 struct vmstate *vlist = NULL;
2799 struct vm_state *vms;
2800 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2801 vms = pthread_getspecific(ts_vmstate.key);
2805 AST_LIST_LOCK(&vmstates);
2806 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2808 ast_debug(3, "error: vms is NULL for %s\n", user);
2811 if (vlist->vms->imapversion != imapversion) {
2814 if (!vlist->vms->imapuser) {
2815 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2819 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2820 AST_LIST_UNLOCK(&vmstates);
2824 AST_LIST_UNLOCK(&vmstates);
2826 ast_debug(3, "%s not found in vmstates\n", user);
2831 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2834 struct vmstate *vlist = NULL;
2835 const char *local_context = S_OR(context, "default");
2838 struct vm_state *vms;
2839 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2840 vms = pthread_getspecific(ts_vmstate.key);
2844 AST_LIST_LOCK(&vmstates);
2845 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2847 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2850 if (vlist->vms->imapversion != imapversion) {
2853 if (!vlist->vms->username || !vlist->vms->context) {
2854 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2858 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);
2860 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2861 ast_debug(3, "Found it!\n");
2862 AST_LIST_UNLOCK(&vmstates);
2866 AST_LIST_UNLOCK(&vmstates);
2868 ast_debug(3, "%s not found in vmstates\n", mailbox);
2873 static void vmstate_insert(struct vm_state *vms)
2876 struct vm_state *altvms;
2878 /* If interactive, it probably already exists, and we should
2879 use the one we already have since it is more up to date.
2880 We can compare the username to find the duplicate */
2881 if (vms->interactive == 1) {
2882 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2884 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2885 vms->newmessages = altvms->newmessages;
2886 vms->oldmessages = altvms->oldmessages;
2887 vms->vmArrayIndex = altvms->vmArrayIndex;
2888 vms->lastmsg = altvms->lastmsg;
2889 vms->curmsg = altvms->curmsg;
2890 /* get a pointer to the persistent store */
2891 vms->persist_vms = altvms;
2892 /* Reuse the mailstream? */
2893 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2894 vms->mailstream = altvms->mailstream;
2896 vms->mailstream = NIL;
2902 if (!(v = ast_calloc(1, sizeof(*v))))
2907 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2909 AST_LIST_LOCK(&vmstates);
2910 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2911 AST_LIST_UNLOCK(&vmstates);
2914 static void vmstate_delete(struct vm_state *vms)
2916 struct vmstate *vc = NULL;
2917 struct vm_state *altvms = NULL;
2919 /* If interactive, we should copy pertinent info
2920 back to the persistent state (to make update immediate) */
2921 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2922 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2923 altvms->newmessages = vms->newmessages;
2924 altvms->oldmessages = vms->oldmessages;
2925 altvms->updated = 1;
2926 vms->mailstream = mail_close(vms->mailstream);
2928 /* Interactive states are not stored within the persistent list */
2932 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2934 AST_LIST_LOCK(&vmstates);
2935 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2936 if (vc->vms == vms) {
2937 AST_LIST_REMOVE_CURRENT(list);
2941 AST_LIST_TRAVERSE_SAFE_END
2942 AST_LIST_UNLOCK(&vmstates);
2945 ast_mutex_destroy(&vc->vms->lock);
2949 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2952 static void set_update(MAILSTREAM * stream)
2954 struct vm_state *vms;
2955 char *mailbox = stream->mailbox, *user;
2956 char buf[1024] = "";
2958 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2959 if (user && option_debug > 2)
2960 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2964 ast_debug(3, "User %s mailbox set for update.\n", user);
2966 vms->updated = 1; /* Set updated flag since mailbox changed */
2969 static void init_vm_state(struct vm_state *vms)
2972 vms->vmArrayIndex = 0;
2973 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2974 vms->msgArray[x] = 0;
2976 ast_mutex_init(&vms->lock);
2979 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
2983 char *fn = is_intro ? vms->introfn : vms->fn;
2985 unsigned long newlen;
2988 if (!body || body == NIL)
2991 ast_mutex_lock(&vms->lock);
2992 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2993 ast_mutex_unlock(&vms->lock);
2994 if (body_content != NIL) {
2995 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2996 /* ast_debug(1,body_content); */
2997 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
2998 /* If the body of the file is empty, return an error */
3002 write_file(filename, (char *) body_decoded, newlen);
3004 ast_debug(5, "Body of message is NULL.\n");
3011 * \brief Get delimiter via mm_list callback
3014 * Determines the delimiter character that is used by the underlying IMAP based mail store.
3016 /* MUTEX should already be held */
3017 static void get_mailbox_delimiter(MAILSTREAM *stream) {
3019 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
3020 mail_list(stream, tmp, "*");
3024 * \brief Check Quota for user
3025 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3026 * \param mailbox the mailbox to check the quota for.
3028 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3030 static void check_quota(struct vm_state *vms, char *mailbox) {
3031 ast_mutex_lock(&vms->lock);
3032 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3033 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3034 if (vms && vms->mailstream != NULL) {
3035 imap_getquotaroot(vms->mailstream, mailbox);
3037 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3039 ast_mutex_unlock(&vms->lock);
3042 #endif /* IMAP_STORAGE */
3044 /*! \brief Lock file path
3045 only return failure if ast_lock_path returns 'timeout',
3046 not if the path does not exist or any other reason
3048 static int vm_lock_path(const char *path)
3050 switch (ast_lock_path(path)) {
3051 case AST_LOCK_TIMEOUT:
3060 struct generic_prepare_struct {
3066 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
3068 struct generic_prepare_struct *gps = data;
3072 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3073 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3074 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3077 res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
3078 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3079 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
3080 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3083 for (i = 0; i < gps->argc; i++)
3084 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
3090 * \brief Retrieves a file from an ODBC data store.
3091 * \param dir the path to the file to be retreived.
3092 * \param msgnum the message number, such as within a mailbox folder.
3094 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
3095 * The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
3097 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
3098 * The output is the message information file with the name msgnum and the extension .txt
3099 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
3101 * \return 0 on success, -1 on error.
3103 static int retrieve_file(char *dir, int msgnum)
3109 void *fdm = MAP_FAILED;
3110 SQLSMALLINT colcount = 0;
3117 SQLSMALLINT datatype;
3118 SQLSMALLINT decimaldigits;
3119 SQLSMALLINT nullable;
3125 char full_fn[PATH_MAX];
3127 char *argv[] = { dir, msgnums };
3128 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3130 struct odbc_obj *obj;
3131 obj = ast_odbc_request_obj(odbc_database, 0);
3133 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3134 c = strchr(fmt, '|');
3137 if (!strcasecmp(fmt, "wav49"))
3139 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3141 make_file(fn, sizeof(fn), dir, msgnum);
3143 ast_copy_string(fn, dir, sizeof(fn));
3145 /* Create the information file */
3146 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3148 if (!(f = fopen(full_fn, "w+"))) {
3149 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
3153 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3154 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3155 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3157 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3158 ast_odbc_release_obj(obj);
3161 res = SQLFetch(stmt);
3162 if (res == SQL_NO_DATA) {
3163 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3164 ast_odbc_release_obj(obj);
3166 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3167 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3168 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3169 ast_odbc_release_obj(obj);
3172 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3174 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3175 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3176 ast_odbc_release_obj(obj);
3179 res = SQLNumResultCols(stmt, &colcount);
3180 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3181 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3182 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3183 ast_odbc_release_obj(obj);
3187 fprintf(f, "[message]\n");
3188 for (x = 0; x < colcount; x++) {
3190 collen = sizeof(coltitle);
3191 res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
3192 &datatype, &colsize, &decimaldigits, &nullable);
3193 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3194 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3195 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3196 ast_odbc_release_obj(obj);
3199 if (!strcasecmp(coltitle, "recording")) {
3201 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3205 lseek(fd, fdlen - 1, SEEK_SET);
3206 if (write(fd, tmp, 1) != 1) {
3211 /* Read out in small chunks */
3212 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3213 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3214 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3215 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3216 ast_odbc_release_obj(obj);
3219 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3220 munmap(fdm, CHUNKSIZE);
3221 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3222 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3224 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3225 ast_odbc_release_obj(obj);
3230 if (truncate(full_fn, fdlen) < 0) {
3231 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3235 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3236 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3237 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3238 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3239 ast_odbc_release_obj(obj);
3242 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3243 fprintf(f, "%s=%s\n", coltitle, rowdata);
3246 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3247 ast_odbc_release_obj(obj);
3249 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3259 * \brief Determines the highest message number in use for a given user and mailbox folder.
3261 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3263 * This method is used when mailboxes are stored in an ODBC back end.
3264 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3266 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3268 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3275 char *argv[] = { dir };
3276 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3278 struct odbc_obj *obj;
3279 obj = ast_odbc_request_obj(odbc_database, 0);
3281 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3282 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3284 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3285 ast_odbc_release_obj(obj);
3288 res = SQLFetch(stmt);
3289 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3290 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3291 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3292 ast_odbc_release_obj(obj);
3295 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3296 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3297 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3298 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3299 ast_odbc_release_obj(obj);
3302 if (sscanf(rowdata, "%30d", &x) != 1)
3303 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3304 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3305 ast_odbc_release_obj(obj);
3307 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3313 * \brief Determines if the specified message exists.
3314 * \param dir the folder the mailbox folder to look for messages.
3315 * \param msgnum the message index to query for.
3317 * This method is used when mailboxes are stored in an ODBC back end.
3319 * \return greater than zero if the message exists, zero when the message does not exist or on error.
3321 static int message_exists(char *dir, int msgnum)
3329 char *argv[] = { dir, msgnums };
3330 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3332 struct odbc_obj *obj;
3333 obj = ast_odbc_request_obj(odbc_database, 0);
3335 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3336 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3337 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3339 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3340 ast_odbc_release_obj(obj);
3343 res = SQLFetch(stmt);
3344 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3345 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3346 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3347 ast_odbc_release_obj(obj);
3350 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3351 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3352 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3353 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3354 ast_odbc_release_obj(obj);
3357 if (sscanf(rowdata, "%30d", &x) != 1)
3358 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3359 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3360 ast_odbc_release_obj(obj);
3362 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3368 * \brief returns the one-based count for messages.
3370 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3372 * This method is used when mailboxes are stored in an ODBC back end.
3373 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3374 * one-based messages.
3375 * This method just calls last_message_index and returns +1 of its value.
3377 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3379 static int count_messages(struct ast_vm_user *vmu, char *dir)
3381 return last_message_index(vmu, dir) + 1;
3385 * \brief Deletes a message from the mailbox folder.
3386 * \param sdir The mailbox folder to work in.
3387 * \param smsg The message index to be deleted.
3389 * This method is used when mailboxes are stored in an ODBC back end.
3390 * The specified message is directly deleted from the database 'voicemessages' table.
3392 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3394 static void delete_file(char *sdir, int smsg)
3399 char *argv[] = { sdir, msgnums };
3400 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3402 struct odbc_obj *obj;
3403 obj = ast_odbc_request_obj(odbc_database, 0);
3405 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3406 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3407 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3409 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3411 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3412 ast_odbc_release_obj(obj);
3414 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3419 * \brief Copies a voicemail from one mailbox to another.
3420 * \param sdir the folder for which to look for the message to be copied.
3421 * \param smsg the index of the message to be copied.
3422 * \param ddir the destination folder to copy the message into.
3423 * \param dmsg the index to be used for the copied message.
3424 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3425 * \param dmailboxcontext The context for the destination user.
3427 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3429 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3435 struct odbc_obj *obj;
3436 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3437 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3439 delete_file(ddir, dmsg);
3440 obj = ast_odbc_request_obj(odbc_database, 0);
3442 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3443 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3444 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
3445 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3447 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3449 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3450 ast_odbc_release_obj(obj);
3452 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3456 struct insert_data {
3463 const char *context;
3464 const char *macrocontext;
3465 const char *callerid;
3466 const char *origtime;
3467 const char *duration;
3469 char *mailboxcontext;
3470 const char *category;
3474 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3476 struct insert_data *data = vdata;
3480 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3481 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3482 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3483 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3487 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
3488 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
3489 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
3490 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
3491 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
3492 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
3493 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
3494 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
3495 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
3496 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
3497 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
3498 if (!ast_strlen_zero(data->category)) {
3499 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
3501 res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
3502 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3503 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3504 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3512 * \brief Stores a voicemail into the database.
3513 * \param dir the folder the mailbox folder to store the message.
3514 * \param mailboxuser the user owning the mailbox folder.
3515 * \param mailboxcontext
3516 * \param msgnum the message index for the message to be stored.
3518 * This method is used when mailboxes are stored in an ODBC back end.
3519 * The message sound file and information file is looked up on the file system.
3520 * A SQL query is invoked to store the message into the (MySQL) database.
3522 * \return the zero on success -1 on error.
3524 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3528 void *fdm = MAP_FAILED;
3534 char full_fn[PATH_MAX];
3537 struct ast_config *cfg = NULL;
3538 struct odbc_obj *obj;
3539 struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
3540 .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "" };
3541 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3543 delete_file(dir, msgnum);
3544 if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3545 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3550 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3551 c = strchr(fmt, '|');
3554 if (!strcasecmp(fmt, "wav49"))
3556 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3558 make_file(fn, sizeof(fn), dir, msgnum);
3560 ast_copy_string(fn, dir, sizeof(fn));
3561 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3562 cfg = ast_config_load(full_fn, config_flags);
3563 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3564 fd = open(full_fn, O_RDWR);
3566 ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3570 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
3571 if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3574 if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3575 idata.macrocontext = "";
3577 if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {