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" touch_on_change="apps/app_voicemail.c apps/app_directory.c">
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 */
97 #if defined(__FreeBSD__)
101 #include "asterisk/logger.h"
102 #include "asterisk/lock.h"
103 #include "asterisk/file.h"
104 #include "asterisk/channel.h"
105 #include "asterisk/pbx.h"
106 #include "asterisk/config.h"
107 #include "asterisk/say.h"
108 #include "asterisk/module.h"
109 #include "asterisk/adsi.h"
110 #include "asterisk/app.h"
111 #include "asterisk/manager.h"
112 #include "asterisk/dsp.h"
113 #include "asterisk/localtime.h"
114 #include "asterisk/cli.h"
115 #include "asterisk/utils.h"
116 #include "asterisk/stringfields.h"
117 #include "asterisk/smdi.h"
118 #include "asterisk/astobj2.h"
119 #include "asterisk/event.h"
120 #include "asterisk/taskprocessor.h"
121 #include "asterisk/test.h"
124 #include "asterisk/res_odbc.h"
128 #include "asterisk/threadstorage.h"
132 <application name="VoiceMail" language="en_US">
134 Leave a Voicemail message.
137 <parameter name="mailboxs" argsep="&" required="true">
138 <argument name="mailbox1" argsep="@" required="true">
139 <argument name="mailbox" required="true" />
140 <argument name="context" />
142 <argument name="mailbox2" argsep="@" multiple="true">
143 <argument name="mailbox" required="true" />
144 <argument name="context" />
147 <parameter name="options">
150 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
153 <argument name="c" />
154 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
155 if played during the greeting. Context defaults to the current context.</para>
158 <argument name="#" required="true" />
159 <para>Use the specified amount of gain when recording the voicemail
160 message. The units are whole-number decibels (dB). Only works on supported
161 technologies, which is DAHDI only.</para>
164 <para>Skip the playback of instructions for leaving a message to the
165 calling party.</para>
168 <para>Play the <literal>unavailable</literal> greeting.</para>
171 <para>Mark message as <literal>URGENT</literal>.</para>
174 <para>Mark message as <literal>PRIORITY</literal>.</para>
180 <para>This application allows the calling party to leave a message for the specified
181 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
182 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
184 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
187 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
190 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
193 <para>This application will set the following channel variable upon completion:</para>
195 <variable name="VMSTATUS">
196 <para>This indicates the status of the execution of the VoiceMail application.</para>
197 <value name="SUCCESS" />
198 <value name="USEREXIT" />
199 <value name="FAILED" />
204 <application name="VoiceMailMain" language="en_US">
206 Check Voicemail messages.
209 <parameter name="mailbox" required="true" argsep="@">
210 <argument name="mailbox" />
211 <argument name="context" />
213 <parameter name="options">
216 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
217 the mailbox that is entered by the caller.</para>
220 <argument name="#" required="true" />
221 <para>Use the specified amount of gain when recording a voicemail message.
222 The units are whole-number decibels (dB).</para>
225 <para>Skip checking the passcode for the mailbox.</para>
228 <argument name="folder" required="true" />
229 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
230 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
232 <enum name="0"><para>INBOX</para></enum>
233 <enum name="1"><para>Old</para></enum>
234 <enum name="2"><para>Work</para></enum>
235 <enum name="3"><para>Family</para></enum>
236 <enum name="4"><para>Friends</para></enum>
237 <enum name="5"><para>Cust1</para></enum>
238 <enum name="6"><para>Cust2</para></enum>
239 <enum name="7"><para>Cust3</para></enum>
240 <enum name="8"><para>Cust4</para></enum>
241 <enum name="9"><para>Cust5</para></enum>
248 <para>This application allows the calling party to check voicemail messages. A specific
249 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
250 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
251 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
252 <literal>default</literal> context will be used.</para>
255 <application name="MailboxExists" language="en_US">
257 Check to see if Voicemail mailbox exists.
260 <parameter name="mailbox" required="true" argsep="@">
261 <argument name="mailbox" required="true" />
262 <argument name="context" />
264 <parameter name="options">
265 <para>None options.</para>
269 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
270 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
272 <para>This application will set the following channel variable upon completion:</para>
274 <variable name="VMBOXEXISTSSTATUS">
275 <para>This will contain the status of the execution of the MailboxExists application.
276 Possible values include:</para>
277 <value name="SUCCESS" />
278 <value name="FAILED" />
283 <application name="VMAuthenticate" language="en_US">
285 Authenticate with Voicemail passwords.
288 <parameter name="mailbox" required="true" argsep="@">
289 <argument name="mailbox" />
290 <argument name="context" />
292 <parameter name="options">
295 <para>Skip playing the initial prompts.</para>
301 <para>This application behaves the same way as the Authenticate application, but the passwords
302 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
303 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
304 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
308 <application name="VMSayName" language="en_US">
310 Play the name of a voicemail user
313 <parameter name="mailbox" required="true" argsep="@">
314 <argument name="mailbox" />
315 <argument name="context" />
319 <para>This application will say the recorded name of the voicemail user specified as the
320 argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
323 <function name="MAILBOX_EXISTS" language="en_US">
325 Tell if a mailbox is configured.
328 <parameter name="mailbox" required="true" />
329 <parameter name="context" />
332 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
333 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
337 <manager name="VoicemailUsersList" language="en_US">
339 List All Voicemail User Information.
342 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
350 static char imapserver[48];
351 static char imapport[8];
352 static char imapflags[128];
353 static char imapfolder[64];
354 static char imapparentfolder[64] = "\0";
355 static char greetingfolder[64];
356 static char authuser[32];
357 static char authpassword[42];
358 static int imapversion = 1;
360 static int expungeonhangup = 1;
361 static int imapgreetings = 0;
362 static char delimiter = '\0';
367 AST_THREADSTORAGE(ts_vmstate);
369 /* Forward declarations for IMAP */
370 static int init_mailstream(struct vm_state *vms, int box);
371 static void write_file(char *filename, char *buffer, unsigned long len);
372 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
373 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
374 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
375 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
376 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
377 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
378 static void vmstate_insert(struct vm_state *vms);
379 static void vmstate_delete(struct vm_state *vms);
380 static void set_update(MAILSTREAM * stream);
381 static void init_vm_state(struct vm_state *vms);
382 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
383 static void get_mailbox_delimiter(MAILSTREAM *stream);
384 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
385 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
386 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
387 static void update_messages_by_imapuser(const char *user, unsigned long number);
388 static int vm_delete(char *file);
390 static int imap_remove_file (char *dir, int msgnum);
391 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
392 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
393 static void check_quota(struct vm_state *vms, char *mailbox);
394 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
396 struct vm_state *vms;
397 AST_LIST_ENTRY(vmstate) list;
400 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
404 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
406 #define COMMAND_TIMEOUT 5000
407 /* Don't modify these here; set your umask at runtime instead */
408 #define VOICEMAIL_DIR_MODE 0777
409 #define VOICEMAIL_FILE_MODE 0666
410 #define CHUNKSIZE 65536
412 #define VOICEMAIL_CONFIG "voicemail.conf"
413 #define ASTERISK_USERNAME "asterisk"
415 /* Define fast-forward, pause, restart, and reverse keys
416 while listening to a voicemail message - these are
417 strings, not characters */
418 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
419 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
420 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
421 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
422 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
423 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
425 /* Default mail command to mail voicemail. Change it with the
426 mailcmd= command in voicemail.conf */
427 #define SENDMAIL "/usr/sbin/sendmail -t"
429 #define INTRO "vm-intro"
432 #define MAXMSGLIMIT 9999
434 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
436 #define BASELINELEN 72
437 #define BASEMAXINLINE 256
440 #define MAX_DATETIME_FORMAT 512
441 #define MAX_NUM_CID_CONTEXTS 10
443 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
444 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
445 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
446 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
447 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
448 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
449 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
450 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
451 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
452 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
453 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
454 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
455 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
456 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
457 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
458 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
459 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
460 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
461 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
462 #define ERROR_LOCK_PATH -100
474 enum vm_option_flags {
475 OPT_SILENT = (1 << 0),
476 OPT_BUSY_GREETING = (1 << 1),
477 OPT_UNAVAIL_GREETING = (1 << 2),
478 OPT_RECORDGAIN = (1 << 3),
479 OPT_PREPEND_MAILBOX = (1 << 4),
480 OPT_AUTOPLAY = (1 << 6),
481 OPT_DTMFEXIT = (1 << 7),
482 OPT_MESSAGE_Urgent = (1 << 8),
483 OPT_MESSAGE_PRIORITY = (1 << 9)
486 enum vm_option_args {
487 OPT_ARG_RECORDGAIN = 0,
488 OPT_ARG_PLAYFOLDER = 1,
489 OPT_ARG_DTMFEXIT = 2,
490 /* This *must* be the last value in this enum! */
491 OPT_ARG_ARRAY_SIZE = 3,
494 enum vm_passwordlocation {
495 OPT_PWLOC_VOICEMAILCONF = 0,
496 OPT_PWLOC_SPOOLDIR = 1,
497 OPT_PWLOC_USERSCONF = 2,
500 AST_APP_OPTIONS(vm_app_options, {
501 AST_APP_OPTION('s', OPT_SILENT),
502 AST_APP_OPTION('b', OPT_BUSY_GREETING),
503 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
504 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
505 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
506 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
507 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
508 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
509 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
512 static int load_config(int reload);
514 /*! \page vmlang Voicemail Language Syntaxes Supported
516 \par Syntaxes supported, not really language codes.
523 \arg \b pt - Portuguese
524 \arg \b pt_BR - Portuguese (Brazil)
526 \arg \b no - Norwegian
528 \arg \b tw - Chinese (Taiwan)
529 \arg \b ua - Ukrainian
531 German requires the following additional soundfile:
532 \arg \b 1F einE (feminine)
534 Spanish requires the following additional soundfile:
535 \arg \b 1M un (masculine)
537 Dutch, Portuguese & Spanish require the following additional soundfiles:
538 \arg \b vm-INBOXs singular of 'new'
539 \arg \b vm-Olds singular of 'old/heard/read'
542 \arg \b vm-INBOX nieuwe (nl)
543 \arg \b vm-Old oude (nl)
546 \arg \b vm-new-a 'new', feminine singular accusative
547 \arg \b vm-new-e 'new', feminine plural accusative
548 \arg \b vm-new-ych 'new', feminine plural genitive
549 \arg \b vm-old-a 'old', feminine singular accusative
550 \arg \b vm-old-e 'old', feminine plural accusative
551 \arg \b vm-old-ych 'old', feminine plural genitive
552 \arg \b digits/1-a 'one', not always same as 'digits/1'
553 \arg \b digits/2-ie 'two', not always same as 'digits/2'
556 \arg \b vm-nytt singular of 'new'
557 \arg \b vm-nya plural of 'new'
558 \arg \b vm-gammalt singular of 'old'
559 \arg \b vm-gamla plural of 'old'
560 \arg \b digits/ett 'one', not always same as 'digits/1'
563 \arg \b vm-ny singular of 'new'
564 \arg \b vm-nye plural of 'new'
565 \arg \b vm-gammel singular of 'old'
566 \arg \b vm-gamle plural of 'old'
574 Italian requires the following additional soundfile:
578 \arg \b vm-nuovi new plural
579 \arg \b vm-vecchio old
580 \arg \b vm-vecchi old plural
582 Chinese (Taiwan) requires the following additional soundfile:
583 \arg \b vm-tong A class-word for call (tong1)
584 \arg \b vm-ri A class-word for day (ri4)
585 \arg \b vm-you You (ni3)
586 \arg \b vm-haveno Have no (mei2 you3)
587 \arg \b vm-have Have (you3)
588 \arg \b vm-listen To listen (yao4 ting1)
591 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
592 spelled among others when you have to change folder. For the above reasons, vm-INBOX
593 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
602 unsigned char iobuf[BASEMAXINLINE];
605 /*! Structure for linked list of users
606 * Use ast_vm_user_destroy() to free one of these structures. */
608 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
609 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
610 char password[80]; /*!< Secret pin code, numbers only */
611 char fullname[80]; /*!< Full name, for directory app */
612 char email[80]; /*!< E-mail address */
613 char *emailsubject; /*!< E-mail subject */
614 char *emailbody; /*!< E-mail body */
615 char pager[80]; /*!< E-mail address to pager (no attachment) */
616 char serveremail[80]; /*!< From: Mail address */
617 char mailcmd[160]; /*!< Configurable mail command */
618 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
619 char zonetag[80]; /*!< Time zone */
622 char uniqueid[80]; /*!< Unique integer identifier */
624 char attachfmt[20]; /*!< Attachment format */
625 unsigned int flags; /*!< VM_ flags */
627 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
628 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
629 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
630 int passwordlocation; /*!< Storage location of the password */
632 char imapuser[80]; /*!< IMAP server login */
633 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
634 char imapfolder[64]; /*!< IMAP voicemail folder */
635 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
636 int imapversion; /*!< If configuration changes, use the new values */
638 double volgain; /*!< Volume gain for voicemails sent via email */
639 AST_LIST_ENTRY(ast_vm_user) list;
642 /*! Voicemail time zones */
644 AST_LIST_ENTRY(vm_zone) list;
647 char msg_format[512];
650 #define VMSTATE_MAX_MSG_ARRAY 256
652 /*! Voicemail mailbox state */
657 char curdir[PATH_MAX];
658 char vmbox[PATH_MAX];
660 char intro[PATH_MAX];
672 int updated; /*!< decremented on each mail check until 1 -allows delay */
673 long msgArray[VMSTATE_MAX_MSG_ARRAY];
674 MAILSTREAM *mailstream;
676 char imapuser[80]; /*!< IMAP server login */
677 char imapfolder[64]; /*!< IMAP voicemail folder */
680 char introfn[PATH_MAX]; /*!< Name of prepended file */
681 unsigned int quota_limit;
682 unsigned int quota_usage;
683 struct vm_state *persist_vms;
688 static char odbc_database[80];
689 static char odbc_table[80];
690 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
691 #define DISPOSE(a,b) remove_file(a,b)
692 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
693 #define EXISTS(a,b,c,d) (message_exists(a,b))
694 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
695 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
696 #define DELETE(a,b,c,d) (delete_file(a,b))
699 #define DISPOSE(a,b) (imap_remove_file(a,b))
700 #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))
701 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
702 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
703 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
704 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
705 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
707 #define RETRIEVE(a,b,c,d)
709 #define STORE(a,b,c,d,e,f,g,h,i,j)
710 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
711 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
712 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
713 #define DELETE(a,b,c,d) (vm_delete(c))
717 static char VM_SPOOL_DIR[PATH_MAX];
719 static char ext_pass_cmd[128];
720 static char ext_pass_check_cmd[128];
724 #define PWDCHANGE_INTERNAL (1 << 1)
725 #define PWDCHANGE_EXTERNAL (1 << 2)
726 static int pwdchange = PWDCHANGE_INTERNAL;
729 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
732 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
734 # define tdesc "Comedian Mail (Voicemail System)"
738 static char userscontext[AST_MAX_EXTENSION] = "default";
740 static char *addesc = "Comedian Mail";
742 /* Leave a message */
743 static char *app = "VoiceMail";
745 /* Check mail, control, etc */
746 static char *app2 = "VoiceMailMain";
748 static char *app3 = "MailboxExists";
749 static char *app4 = "VMAuthenticate";
751 static char *sayname_app = "VMSayName";
753 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
754 static AST_LIST_HEAD_STATIC(zones, vm_zone);
755 static char zonetag[80];
756 static int maxsilence;
758 static int maxdeletedmsg;
759 static int silencethreshold = 128;
760 static char serveremail[80];
761 static char mailcmd[160]; /* Configurable mail cmd */
762 static char externnotify[160];
763 static struct ast_smdi_interface *smdi_iface = NULL;
764 static char vmfmts[80];
765 static double volgain;
766 static int vmminsecs;
767 static int vmmaxsecs;
770 static int maxlogins;
771 static int minpassword;
772 static int passwordlocation;
774 /*! Poll mailboxes for changes since there is something external to
775 * app_voicemail that may change them. */
776 static unsigned int poll_mailboxes;
778 /*! Polling frequency */
779 static unsigned int poll_freq;
780 /*! By default, poll every 30 seconds */
781 #define DEFAULT_POLL_FREQ 30
783 AST_MUTEX_DEFINE_STATIC(poll_lock);
784 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
785 static pthread_t poll_thread = AST_PTHREADT_NULL;
786 static unsigned char poll_thread_run;
788 /*! Subscription to ... MWI event subscriptions */
789 static struct ast_event_sub *mwi_sub_sub;
790 /*! Subscription to ... MWI event un-subscriptions */
791 static struct ast_event_sub *mwi_unsub_sub;
794 * \brief An MWI subscription
796 * This is so we can keep track of which mailboxes are subscribed to.
797 * This way, we know which mailboxes to poll when the pollmailboxes
798 * option is being used.
801 AST_RWLIST_ENTRY(mwi_sub) entry;
809 struct mwi_sub_task {
815 static struct ast_taskprocessor *mwi_subscription_tps;
817 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
819 /* custom audio control prompts for voicemail playback */
820 static char listen_control_forward_key[12];
821 static char listen_control_reverse_key[12];
822 static char listen_control_pause_key[12];
823 static char listen_control_restart_key[12];
824 static char listen_control_stop_key[12];
826 /* custom password sounds */
827 static char vm_password[80] = "vm-password";
828 static char vm_newpassword[80] = "vm-newpassword";
829 static char vm_passchanged[80] = "vm-passchanged";
830 static char vm_reenterpassword[80] = "vm-reenterpassword";
831 static char vm_mismatch[80] = "vm-mismatch";
832 static char vm_invalid_password[80] = "vm-invalid-password";
833 static char vm_pls_try_again[80] = "vm-pls-try-again";
835 static struct ast_flags globalflags = {0};
837 static int saydurationminfo;
839 static char dialcontext[AST_MAX_CONTEXT] = "";
840 static char callcontext[AST_MAX_CONTEXT] = "";
841 static char exitcontext[AST_MAX_CONTEXT] = "";
843 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
846 static char *emailbody = NULL;
847 static char *emailsubject = NULL;
848 static char *pagerbody = NULL;
849 static char *pagersubject = NULL;
850 static char fromstring[100];
851 static char pagerfromstring[100];
852 static char charset[32] = "ISO-8859-1";
854 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
855 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
856 static int adsiver = 1;
857 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
858 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
860 /* Forward declarations - generic */
861 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
862 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);
863 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
864 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
865 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
866 signed char record_gain, struct vm_state *vms, char *flag);
867 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
868 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
869 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);
870 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);
871 static void apply_options(struct ast_vm_user *vmu, const char *options);
872 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);
873 static int is_valid_dtmf(const char *key);
874 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
875 static int write_password_to_file(const char *secretfn, const char *password);
877 struct ao2_container *inprocess_container;
885 static int inprocess_hash_fn(const void *obj, const int flags)
887 const struct inprocess *i = obj;
888 return atoi(i->mailbox);
891 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
893 struct inprocess *i = obj, *j = arg;
894 if (!strcmp(i->mailbox, j->mailbox)) {
897 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
900 static int inprocess_count(const char *context, const char *mailbox, int delta)
902 struct inprocess *i, *arg = alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
903 arg->context = arg->mailbox + strlen(mailbox) + 1;
904 strcpy(arg->mailbox, mailbox); /* SAFE */
905 strcpy(arg->context, context); /* SAFE */
906 ao2_lock(inprocess_container);
907 if ((i = ao2_find(inprocess_container, arg, 0))) {
908 int ret = ast_atomic_fetchadd_int(&i->count, delta);
909 ao2_unlock(inprocess_container);
913 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
914 ao2_unlock(inprocess_container);
917 i->context = i->mailbox + strlen(mailbox) + 1;
918 strcpy(i->mailbox, mailbox); /* SAFE */
919 strcpy(i->context, context); /* SAFE */
921 ao2_link(inprocess_container, i);
922 ao2_unlock(inprocess_container);
927 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
928 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
932 * \brief Strips control and non 7-bit clean characters from input string.
934 * \note To map control and none 7-bit characters to a 7-bit clean characters
935 * please use ast_str_encode_mine().
937 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
940 for (; *input; input++) {
945 if (bufptr == buf + buflen - 1) {
955 * \brief Sets default voicemail system options to a voicemail user.
957 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
958 * - all the globalflags
959 * - the saydurationminfo
963 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
966 static void populate_defaults(struct ast_vm_user *vmu)
968 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
969 vmu->passwordlocation = passwordlocation;
970 if (saydurationminfo)
971 vmu->saydurationm = saydurationminfo;
972 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
973 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
974 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
975 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
977 vmu->maxsecs = vmmaxsecs;
979 vmu->maxmsg = maxmsg;
981 vmu->maxdeletedmsg = maxdeletedmsg;
982 vmu->volgain = volgain;
983 vmu->emailsubject = NULL;
984 vmu->emailbody = NULL;
986 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
991 * \brief Sets a a specific property value.
992 * \param vmu The voicemail user object to work with.
993 * \param var The name of the property to be set.
994 * \param value The value to be set to the property.
996 * The property name must be one of the understood properties. See the source for details.
998 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1001 if (!strcasecmp(var, "attach")) {
1002 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1003 } else if (!strcasecmp(var, "attachfmt")) {
1004 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1005 } else if (!strcasecmp(var, "serveremail")) {
1006 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1007 } else if (!strcasecmp(var, "language")) {
1008 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1009 } else if (!strcasecmp(var, "tz")) {
1010 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1012 } else if (!strcasecmp(var, "imapuser")) {
1013 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1014 vmu->imapversion = imapversion;
1015 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1016 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1017 vmu->imapversion = imapversion;
1018 } else if (!strcasecmp(var, "imapfolder")) {
1019 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1020 } else if (!strcasecmp(var, "imapvmshareid")) {
1021 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1022 vmu->imapversion = imapversion;
1024 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1025 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1026 } else if (!strcasecmp(var, "saycid")){
1027 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1028 } else if (!strcasecmp(var, "sendvoicemail")){
1029 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1030 } else if (!strcasecmp(var, "review")){
1031 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1032 } else if (!strcasecmp(var, "tempgreetwarn")){
1033 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1034 } else if (!strcasecmp(var, "messagewrap")){
1035 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1036 } else if (!strcasecmp(var, "operator")) {
1037 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1038 } else if (!strcasecmp(var, "envelope")){
1039 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1040 } else if (!strcasecmp(var, "moveheard")){
1041 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1042 } else if (!strcasecmp(var, "sayduration")){
1043 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1044 } else if (!strcasecmp(var, "saydurationm")){
1045 if (sscanf(value, "%30d", &x) == 1) {
1046 vmu->saydurationm = x;
1048 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1050 } else if (!strcasecmp(var, "forcename")){
1051 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1052 } else if (!strcasecmp(var, "forcegreetings")){
1053 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1054 } else if (!strcasecmp(var, "callback")) {
1055 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1056 } else if (!strcasecmp(var, "dialout")) {
1057 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1058 } else if (!strcasecmp(var, "exitcontext")) {
1059 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1060 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1061 vmu->maxsecs = atoi(value);
1062 if (vmu->maxsecs <= 0) {
1063 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1064 vmu->maxsecs = vmmaxsecs;
1066 vmu->maxsecs = atoi(value);
1068 if (!strcasecmp(var, "maxmessage"))
1069 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1070 } else if (!strcasecmp(var, "maxmsg")) {
1071 vmu->maxmsg = atoi(value);
1072 /* Accept maxmsg=0 (Greetings only voicemail) */
1073 if (vmu->maxmsg < 0) {
1074 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1075 vmu->maxmsg = MAXMSG;
1076 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1077 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1078 vmu->maxmsg = MAXMSGLIMIT;
1080 } else if (!strcasecmp(var, "backupdeleted")) {
1081 if (sscanf(value, "%30d", &x) == 1)
1082 vmu->maxdeletedmsg = x;
1083 else if (ast_true(value))
1084 vmu->maxdeletedmsg = MAXMSG;
1086 vmu->maxdeletedmsg = 0;
1088 if (vmu->maxdeletedmsg < 0) {
1089 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1090 vmu->maxdeletedmsg = MAXMSG;
1091 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1092 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1093 vmu->maxdeletedmsg = MAXMSGLIMIT;
1095 } else if (!strcasecmp(var, "volgain")) {
1096 sscanf(value, "%30lf", &vmu->volgain);
1097 } else if (!strcasecmp(var, "passwordlocation")) {
1098 if (!strcasecmp(value, "spooldir")) {
1099 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1101 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1103 } else if (!strcasecmp(var, "options")) {
1104 apply_options(vmu, value);
1108 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1110 int fds[2], pid = 0;
1112 memset(buf, 0, len);
1115 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1118 pid = ast_safe_fork(0);
1124 snprintf(buf, len, "FAILURE: Fork failed");
1128 if (read(fds[0], buf, len) < 0) {
1129 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1134 AST_DECLARE_APP_ARGS(arg,
1137 char *mycmd = ast_strdupa(command);
1140 dup2(fds[1], STDOUT_FILENO);
1142 ast_close_fds_above_n(STDOUT_FILENO);
1144 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1146 execv(arg.v[0], arg.v);
1147 printf("FAILURE: %s", strerror(errno));
1155 * \brief Check that password meets minimum required length
1156 * \param vmu The voicemail user to change the password for.
1157 * \param password The password string to check
1159 * \return zero on ok, 1 on not ok.
1161 static int check_password(struct ast_vm_user *vmu, char *password)
1163 /* check minimum length */
1164 if (strlen(password) < minpassword)
1166 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1167 char cmd[255], buf[255];
1169 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1171 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1172 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1173 ast_debug(5, "Result: %s\n", buf);
1174 if (!strncasecmp(buf, "VALID", 5)) {
1175 ast_debug(3, "Passed password check: '%s'\n", buf);
1177 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1178 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1181 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1190 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1191 * \param vmu The voicemail user to change the password for.
1192 * \param password The new value to be set to the password for this user.
1194 * This only works if there is a realtime engine configured.
1195 * This is called from the (top level) vm_change_password.
1197 * \return zero on success, -1 on error.
1199 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1202 if (!strcmp(vmu->password, password)) {
1203 /* No change (but an update would return 0 rows updated, so we opt out here) */
1207 if (strlen(password) > 10) {
1208 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1210 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1211 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1218 * \brief Destructively Parse options and apply.
1220 static void apply_options(struct ast_vm_user *vmu, const char *options)
1225 stringp = ast_strdupa(options);
1226 while ((s = strsep(&stringp, "|"))) {
1228 if ((var = strsep(&value, "=")) && value) {
1229 apply_option(vmu, var, value);
1235 * \brief Loads the options specific to a voicemail user.
1237 * This is called when a vm_user structure is being set up, such as from load_options.
1239 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1241 for (; var; var = var->next) {
1242 if (!strcasecmp(var->name, "vmsecret")) {
1243 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1244 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1245 if (ast_strlen_zero(retval->password))
1246 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1247 } else if (!strcasecmp(var->name, "uniqueid")) {
1248 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1249 } else if (!strcasecmp(var->name, "pager")) {
1250 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1251 } else if (!strcasecmp(var->name, "email")) {
1252 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1253 } else if (!strcasecmp(var->name, "fullname")) {
1254 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1255 } else if (!strcasecmp(var->name, "context")) {
1256 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1257 } else if (!strcasecmp(var->name, "emailsubject")) {
1258 retval->emailsubject = ast_strdup(var->value);
1259 } else if (!strcasecmp(var->name, "emailbody")) {
1260 retval->emailbody = ast_strdup(var->value);
1262 } else if (!strcasecmp(var->name, "imapuser")) {
1263 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1264 retval->imapversion = imapversion;
1265 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1266 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1267 retval->imapversion = imapversion;
1268 } else if (!strcasecmp(var->name, "imapfolder")) {
1269 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1270 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1271 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1272 retval->imapversion = imapversion;
1275 apply_option(retval, var->name, var->value);
1280 * \brief Determines if a DTMF key entered is valid.
1281 * \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.
1283 * Tests the character entered against the set of valid DTMF characters.
1284 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1286 static int is_valid_dtmf(const char *key)
1289 char *local_key = ast_strdupa(key);
1291 for (i = 0; i < strlen(key); ++i) {
1292 if (!strchr(VALID_DTMF, *local_key)) {
1293 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1302 * \brief Finds a voicemail user from the realtime engine.
1307 * 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.
1309 * \return The ast_vm_user structure for the user that was found.
1311 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1313 struct ast_variable *var;
1314 struct ast_vm_user *retval;
1316 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1318 ast_set_flag(retval, VM_ALLOCED);
1320 memset(retval, 0, sizeof(*retval));
1322 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1323 populate_defaults(retval);
1324 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1325 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1327 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1329 apply_options_full(retval, var);
1330 ast_variables_destroy(var);
1341 * \brief Finds a voicemail user from the users file or the realtime engine.
1346 * \return The ast_vm_user structure for the user that was found.
1348 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1350 /* This function could be made to generate one from a database, too */
1351 struct ast_vm_user *vmu = NULL, *cur;
1352 AST_LIST_LOCK(&users);
1354 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1355 context = "default";
1357 AST_LIST_TRAVERSE(&users, cur, list) {
1359 if (cur->imapversion != imapversion) {
1363 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1365 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1369 /* Make a copy, so that on a reload, we have no race */
1370 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1371 memcpy(vmu, cur, sizeof(*vmu));
1372 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1373 AST_LIST_NEXT(vmu, list) = NULL;
1376 vmu = find_user_realtime(ivm, context, mailbox);
1377 AST_LIST_UNLOCK(&users);
1382 * \brief Resets a user password to a specified password.
1387 * This does the actual change password work, called by the vm_change_password() function.
1389 * \return zero on success, -1 on error.
1391 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1393 /* This function could be made to generate one from a database, too */
1394 struct ast_vm_user *cur;
1396 AST_LIST_LOCK(&users);
1397 AST_LIST_TRAVERSE(&users, cur, list) {
1398 if ((!context || !strcasecmp(context, cur->context)) &&
1399 (!strcasecmp(mailbox, cur->mailbox)))
1403 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1406 AST_LIST_UNLOCK(&users);
1411 * \brief The handler for the change password option.
1412 * \param vmu The voicemail user to work with.
1413 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1414 * 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.
1415 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1417 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1419 struct ast_config *cfg = NULL;
1420 struct ast_variable *var = NULL;
1421 struct ast_category *cat = NULL;
1422 char *category = NULL, *value = NULL, *new = NULL;
1423 const char *tmp = NULL;
1424 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1425 char secretfn[PATH_MAX] = "";
1428 if (!change_password_realtime(vmu, newpassword))
1431 /* check if we should store the secret in the spool directory next to the messages */
1432 switch (vmu->passwordlocation) {
1433 case OPT_PWLOC_SPOOLDIR:
1434 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1435 if (write_password_to_file(secretfn, newpassword) == 0) {
1436 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1437 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1438 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1441 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1444 case OPT_PWLOC_VOICEMAILCONF:
1445 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1446 while ((category = ast_category_browse(cfg, category))) {
1447 if (!strcasecmp(category, vmu->context)) {
1448 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1449 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1452 value = strstr(tmp, ",");
1454 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1457 new = alloca((strlen(value) + strlen(newpassword) + 1));
1458 sprintf(new, "%s%s", newpassword, value);
1459 if (!(cat = ast_category_get(cfg, category))) {
1460 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1463 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1467 /* save the results */
1469 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1470 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1471 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1476 case OPT_PWLOC_USERSCONF:
1477 /* check users.conf and update the password stored for the mailbox */
1478 /* if no vmsecret entry exists create one. */
1479 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1480 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1481 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1482 ast_debug(4, "users.conf: %s\n", category);
1483 if (!strcasecmp(category, vmu->mailbox)) {
1484 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1485 ast_debug(3, "looks like we need to make vmsecret!\n");
1486 var = ast_variable_new("vmsecret", newpassword, "");
1490 new = alloca(strlen(newpassword) + 1);
1491 sprintf(new, "%s", newpassword);
1492 if (!(cat = ast_category_get(cfg, category))) {
1493 ast_debug(4, "failed to get category!\n");
1498 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1500 ast_variable_append(cat, var);
1506 /* save the results and clean things up */
1508 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1509 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1510 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1516 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1519 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1520 if (!ast_safe_system(buf)) {
1521 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1522 /* Reset the password in memory, too */
1523 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1528 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1529 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1530 * \param len The length of the path string that was written out.
1532 * The path is constructed as
1533 * VM_SPOOL_DIRcontext/ext/folder
1535 * \return zero on success, -1 on error.
1537 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1539 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1543 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1544 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1545 * \param len The length of the path string that was written out.
1547 * The path is constructed as
1548 * VM_SPOOL_DIRcontext/ext/folder
1550 * \return zero on success, -1 on error.
1552 static int make_file(char *dest, const int len, const char *dir, const int num)
1554 return snprintf(dest, len, "%s/msg%04d", dir, num);
1557 /* same as mkstemp, but return a FILE * */
1558 static FILE *vm_mkftemp(char *template)
1561 int pfd = mkstemp(template);
1562 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1564 p = fdopen(pfd, "w+");
1573 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1574 * \param dest String. base directory.
1575 * \param len Length of dest.
1576 * \param context String. Ignored if is null or empty string.
1577 * \param ext String. Ignored if is null or empty string.
1578 * \param folder String. Ignored if is null or empty string.
1579 * \return -1 on failure, 0 on success.
1581 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1583 mode_t mode = VOICEMAIL_DIR_MODE;
1586 make_dir(dest, len, context, ext, folder);
1587 if ((res = ast_mkdir(dest, mode))) {
1588 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1594 static const char * const mailbox_folders[] = {
1613 static const char *mbox(struct ast_vm_user *vmu, int id)
1616 if (vmu && id == 0) {
1617 return vmu->imapfolder;
1620 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1623 static int get_folder_by_name(const char *name)
1627 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1628 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1636 static void free_user(struct ast_vm_user *vmu)
1638 if (ast_test_flag(vmu, VM_ALLOCED)) {
1639 if (vmu->emailbody != NULL) {
1640 ast_free(vmu->emailbody);
1641 vmu->emailbody = NULL;
1643 if (vmu->emailsubject != NULL) {
1644 ast_free(vmu->emailsubject);
1645 vmu->emailsubject = NULL;
1651 /* All IMAP-specific functions should go in this block. This
1652 * keeps them from being spread out all over the code */
1654 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1657 struct vm_state *vms;
1658 unsigned long messageNum;
1660 /* If greetings aren't stored in IMAP, just delete the file */
1661 if (msgnum < 0 && !imapgreetings) {
1662 ast_filedelete(file, NULL);
1666 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1667 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);
1671 /* find real message number based on msgnum */
1672 /* this may be an index into vms->msgArray based on the msgnum. */
1673 messageNum = vms->msgArray[msgnum];
1674 if (messageNum == 0) {
1675 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1678 if (option_debug > 2)
1679 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1680 /* delete message */
1681 snprintf (arg, sizeof(arg), "%lu", messageNum);
1682 ast_mutex_lock(&vms->lock);
1683 mail_setflag (vms->mailstream, arg, "\\DELETED");
1684 mail_expunge(vms->mailstream);
1685 ast_mutex_unlock(&vms->lock);
1688 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1690 struct vm_state *vms_p;
1691 char *file, *filename;
1696 /* This function is only used for retrieval of IMAP greetings
1697 * regular messages are not retrieved this way, nor are greetings
1698 * if they are stored locally*/
1699 if (msgnum > -1 || !imapgreetings) {
1702 file = strrchr(ast_strdupa(dir), '/');
1706 ast_debug (1, "Failed to procure file name from directory passed.\n");
1711 /* check if someone is accessing this box right now... */
1712 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
1713 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1714 /* Unlike when retrieving a message, it is reasonable not to be able to find a
1715 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1716 * that's all we need to do.
1718 if (!(vms_p = create_vm_state_from_user(vmu))) {
1719 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1724 /* Greetings will never have a prepended message */
1725 *vms_p->introfn = '\0';
1727 ast_mutex_lock(&vms_p->lock);
1728 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1729 if (!vms_p->mailstream) {
1730 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1731 ast_mutex_unlock(&vms_p->lock);
1735 /*XXX Yuck, this could probably be done a lot better */
1736 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1737 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1738 /* We have the body, now we extract the file name of the first attachment. */
1739 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1740 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1742 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1743 ast_mutex_unlock(&vms_p->lock);
1746 filename = strsep(&attachment, ".");
1747 if (!strcmp(filename, file)) {
1748 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1749 vms_p->msgArray[vms_p->curmsg] = i + 1;
1750 save_body(body, vms_p, "2", attachment, 0);
1751 ast_mutex_unlock(&vms_p->lock);
1755 ast_mutex_unlock(&vms_p->lock);
1760 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1763 char *header_content;
1764 char *attachedfilefmt;
1766 struct vm_state *vms;
1767 char text_file[PATH_MAX];
1768 FILE *text_file_ptr;
1770 struct ast_vm_user *vmu;
1772 if (!(vmu = find_user(NULL, context, mailbox))) {
1773 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1778 if (imapgreetings) {
1779 res = imap_retrieve_greeting(dir, msgnum, vmu);
1787 /* Before anything can happen, we need a vm_state so that we can
1788 * actually access the imap server through the vms->mailstream
1790 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1791 /* This should not happen. If it does, then I guess we'd
1792 * need to create the vm_state, extract which mailbox to
1793 * open, and then set up the msgArray so that the correct
1794 * IMAP message could be accessed. If I have seen correctly
1795 * though, the vms should be obtainable from the vmstates list
1796 * and should have its msgArray properly set up.
1798 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1803 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1804 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1806 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1807 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1812 if (option_debug > 2)
1813 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1814 if (vms->msgArray[msgnum] == 0) {
1815 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1820 /* This will only work for new messages... */
1821 ast_mutex_lock(&vms->lock);
1822 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1823 ast_mutex_unlock(&vms->lock);
1824 /* empty string means no valid header */
1825 if (ast_strlen_zero(header_content)) {
1826 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1831 ast_mutex_lock(&vms->lock);
1832 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1833 ast_mutex_unlock(&vms->lock);
1835 /* We have the body, now we extract the file name of the first attachment. */
1836 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1837 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1839 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1844 /* Find the format of the attached file */
1846 strsep(&attachedfilefmt, ".");
1847 if (!attachedfilefmt) {
1848 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1853 save_body(body, vms, "2", attachedfilefmt, 0);
1854 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1855 *vms->introfn = '\0';
1858 /* Get info from headers!! */
1859 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1861 if (!(text_file_ptr = fopen(text_file, "w"))) {
1862 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1865 fprintf(text_file_ptr, "%s\n", "[message]");
1867 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1868 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1869 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1870 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1871 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1872 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1873 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1874 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1875 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1876 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1877 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1878 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1879 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1880 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1881 fclose(text_file_ptr);
1888 static int folder_int(const char *folder)
1890 /*assume a NULL folder means INBOX*/
1893 if (!strcasecmp(folder, imapfolder))
1895 else if (!strcasecmp(folder, "Old"))
1897 else if (!strcasecmp(folder, "Work"))
1899 else if (!strcasecmp(folder, "Family"))
1901 else if (!strcasecmp(folder, "Friends"))
1903 else if (!strcasecmp(folder, "Cust1"))
1905 else if (!strcasecmp(folder, "Cust2"))
1907 else if (!strcasecmp(folder, "Cust3"))
1909 else if (!strcasecmp(folder, "Cust4"))
1911 else if (!strcasecmp(folder, "Cust5"))
1913 else /*assume they meant INBOX if folder is not found otherwise*/
1917 static int __messagecount(const char *context, const char *mailbox, const char *folder)
1922 struct ast_vm_user *vmu, vmus;
1923 struct vm_state *vms_p;
1925 int fold = folder_int(folder);
1928 /* If URGENT, then look at INBOX */
1934 if (ast_strlen_zero(mailbox))
1937 /* We have to get the user before we can open the stream! */
1938 vmu = find_user(&vmus, context, mailbox);
1940 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1943 /* No IMAP account available */
1944 if (vmu->imapuser[0] == '\0') {
1945 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1950 /* No IMAP account available */
1951 if (vmu->imapuser[0] == '\0') {
1952 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1957 /* check if someone is accessing this box right now... */
1958 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1960 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1963 ast_debug(3, "Returning before search - user is logged in\n");
1964 if (fold == 0) { /* INBOX */
1965 return vms_p->newmessages;
1967 if (fold == 1) { /* Old messages */
1968 return vms_p->oldmessages;
1970 if (fold == 11) {/*Urgent messages*/
1971 return vms_p->urgentmessages;
1975 /* add one if not there... */
1976 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
1978 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1982 vms_p = create_vm_state_from_user(vmu);
1984 ret = init_mailstream(vms_p, fold);
1985 if (!vms_p->mailstream) {
1986 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1990 ast_mutex_lock(&vms_p->lock);
1991 pgm = mail_newsearchpgm ();
1992 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
1993 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
1999 /* In the special case where fold is 1 (old messages) we have to do things a bit
2000 * differently. Old messages are stored in the INBOX but are marked as "seen"
2006 /* look for urgent messages */
2014 vms_p->vmArrayIndex = 0;
2015 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2016 if (fold == 0 && urgent == 0)
2017 vms_p->newmessages = vms_p->vmArrayIndex;
2019 vms_p->oldmessages = vms_p->vmArrayIndex;
2020 if (fold == 0 && urgent == 1)
2021 vms_p->urgentmessages = vms_p->vmArrayIndex;
2022 /*Freeing the searchpgm also frees the searchhdr*/
2023 mail_free_searchpgm(&pgm);
2024 ast_mutex_unlock(&vms_p->lock);
2026 return vms_p->vmArrayIndex;
2028 ast_mutex_lock(&vms_p->lock);
2029 mail_ping(vms_p->mailstream);
2030 ast_mutex_unlock(&vms_p->lock);
2036 * \brief Gets the number of messages that exist in a mailbox folder.
2041 * This method is used when IMAP backend is used.
2042 * \return The number of messages in this mailbox folder (zero or more).
2044 static int messagecount(const char *context, const char *mailbox, const char *folder)
2046 if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2047 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2049 return __messagecount(context, mailbox, folder);
2053 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
2055 char *myserveremail = serveremail;
2057 char introfn[PATH_MAX];
2061 char tmp[80] = "/tmp/astmail-XXXXXX";
2066 int ret; /* for better error checking */
2067 char *imap_flags = NIL;
2069 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2070 if (msgnum < 0 && !imapgreetings) {
2074 /* Set urgent flag for IMAP message */
2075 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2076 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2077 imap_flags = "\\FLAGGED";
2080 /* Attach only the first format */
2081 fmt = ast_strdupa(fmt);
2083 strsep(&stringp, "|");
2085 if (!ast_strlen_zero(vmu->serveremail))
2086 myserveremail = vmu->serveremail;
2089 make_file(fn, sizeof(fn), dir, msgnum);
2091 ast_copy_string (fn, dir, sizeof(fn));
2093 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2094 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2098 if (ast_strlen_zero(vmu->email)) {
2099 /* We need the vmu->email to be set when we call make_email_file, but
2100 * if we keep it set, a duplicate e-mail will be created. So at the end
2101 * of this function, we will revert back to an empty string if tempcopy
2104 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2108 if (!strcmp(fmt, "wav49"))
2110 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2112 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2114 if (!(p = vm_mkftemp(tmp))) {
2115 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2117 *(vmu->email) = '\0';
2121 if (msgnum < 0 && imapgreetings) {
2122 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2123 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2126 imap_delete_old_greeting(fn, vms);
2129 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);
2130 /* read mail file to memory */
2133 if (!(buf = ast_malloc(len + 1))) {
2134 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2137 *(vmu->email) = '\0';
2140 if (fread(buf, len, 1, p) < len) {
2142 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2146 ((char *) buf)[len] = '\0';
2147 INIT(&str, mail_string, buf, len);
2148 ret = init_mailstream(vms, NEW_FOLDER);
2150 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2151 ast_mutex_lock(&vms->lock);
2152 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2153 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2154 ast_mutex_unlock(&vms->lock);
2159 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2165 ast_debug(3, "%s stored\n", fn);
2168 *(vmu->email) = '\0';
2175 * \brief Gets the number of messages that exist in the inbox folder.
2176 * \param mailbox_context
2177 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2178 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2179 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2181 * This method is used when IMAP backend is used.
2182 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2184 * \return zero on success, -1 on error.
2187 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2189 char tmp[PATH_MAX] = "";
2201 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2202 /* If no mailbox, return immediately */
2203 if (ast_strlen_zero(mailbox_context))
2206 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2207 context = strchr(tmp, '@');
2208 if (strchr(mailbox_context, ',')) {
2209 int tmpnew, tmpold, tmpurgent;
2210 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2212 while ((cur = strsep(&mb, ", "))) {
2213 if (!ast_strlen_zero(cur)) {
2214 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2222 *urgentmsgs += tmpurgent;
2233 context = "default";
2234 mailboxnc = (char *) mailbox_context;
2238 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2240 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2243 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2248 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2253 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2261 * \brief Determines if the given folder has messages.
2262 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2263 * \param folder the folder to look in
2265 * This function is used when the mailbox is stored in an IMAP back end.
2266 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2267 * \return 1 if the folder has one or more messages. zero otherwise.
2270 static int has_voicemail(const char *mailbox, const char *folder)
2272 char tmp[256], *tmp2, *box, *context;
2273 ast_copy_string(tmp, mailbox, sizeof(tmp));
2275 if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2276 while ((box = strsep(&tmp2, ",&"))) {
2277 if (!ast_strlen_zero(box)) {
2278 if (has_voicemail(box, folder)) {
2284 if ((context = strchr(tmp, '@'))) {
2287 context = "default";
2289 return __messagecount(context, tmp, folder) ? 1 : 0;
2293 * \brief Copies a message from one mailbox to another.
2303 * This works with IMAP storage based mailboxes.
2305 * \return zero on success, -1 on error.
2307 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)
2309 struct vm_state *sendvms = NULL, *destvms = NULL;
2310 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2311 if (msgnum >= recip->maxmsg) {
2312 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2315 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2316 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2319 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2320 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2323 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2324 ast_mutex_lock(&sendvms->lock);
2325 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2326 ast_mutex_unlock(&sendvms->lock);
2329 ast_mutex_unlock(&sendvms->lock);
2330 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2334 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2336 char tmp[256], *t = tmp;
2337 size_t left = sizeof(tmp);
2339 if (box == OLD_FOLDER) {
2340 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2342 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2345 if (box == NEW_FOLDER) {
2346 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2348 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2351 /* Build up server information */
2352 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2354 /* Add authentication user if present */
2355 if (!ast_strlen_zero(authuser))
2356 ast_build_string(&t, &left, "/authuser=%s", authuser);
2358 /* Add flags if present */
2359 if (!ast_strlen_zero(imapflags))
2360 ast_build_string(&t, &left, "/%s", imapflags);
2362 /* End with username */
2364 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2366 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2368 if (box == NEW_FOLDER || box == OLD_FOLDER)
2369 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2370 else if (box == GREETINGS_FOLDER)
2371 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2372 else { /* Other folders such as Friends, Family, etc... */
2373 if (!ast_strlen_zero(imapparentfolder)) {
2374 /* imapparentfolder would typically be set to INBOX */
2375 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2377 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2382 static int init_mailstream(struct vm_state *vms, int box)
2384 MAILSTREAM *stream = NIL;
2389 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2392 if (option_debug > 2)
2393 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2394 if (vms->mailstream == NIL || !vms->mailstream) {
2396 ast_log(LOG_DEBUG, "mailstream not set.\n");
2398 stream = vms->mailstream;
2400 /* debug = T; user wants protocol telemetry? */
2401 debug = NIL; /* NO protocol telemetry? */
2403 if (delimiter == '\0') { /* did not probe the server yet */
2405 #ifdef USE_SYSTEM_IMAP
2406 #include <imap/linkage.c>
2407 #elif defined(USE_SYSTEM_CCLIENT)
2408 #include <c-client/linkage.c>
2410 #include "linkage.c"
2412 /* Connect to INBOX first to get folders delimiter */
2413 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2414 ast_mutex_lock(&vms->lock);
2415 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2416 ast_mutex_unlock(&vms->lock);
2417 if (stream == NIL) {
2418 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2421 get_mailbox_delimiter(stream);
2422 /* update delimiter in imapfolder */
2423 for (cp = vms->imapfolder; *cp; cp++)
2427 /* Now connect to the target folder */
2428 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2429 if (option_debug > 2)
2430 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2431 ast_mutex_lock(&vms->lock);
2432 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2433 ast_mutex_unlock(&vms->lock);
2434 if (vms->mailstream == NIL) {
2441 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2445 int ret, urgent = 0;
2447 /* If Urgent, then look at INBOX */
2453 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2454 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2455 vms->imapversion = vmu->imapversion;
2456 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2458 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2459 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2463 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2467 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2468 check_quota(vms, (char *) mbox(vmu, box));
2471 ast_mutex_lock(&vms->lock);
2472 pgm = mail_newsearchpgm();
2474 /* Check IMAP folder for Asterisk messages only... */
2475 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2476 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2481 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2482 if (box == NEW_FOLDER && urgent == 1) {
2487 } else if (box == NEW_FOLDER && urgent == 0) {
2492 } else if (box == OLD_FOLDER) {
2497 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2499 vms->vmArrayIndex = 0;
2500 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2501 vms->lastmsg = vms->vmArrayIndex - 1;
2502 mail_free_searchpgm(&pgm);
2504 ast_mutex_unlock(&vms->lock);
2508 static void write_file(char *filename, char *buffer, unsigned long len)
2512 output = fopen (filename, "w");
2513 if (fwrite(buffer, len, 1, output) != 1) {
2514 if (ferror(output)) {
2515 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2521 static void update_messages_by_imapuser(const char *user, unsigned long number)
2523 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2525 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2529 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2530 vms->msgArray[vms->vmArrayIndex++] = number;
2533 void mm_searched(MAILSTREAM *stream, unsigned long number)
2535 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2537 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2540 update_messages_by_imapuser(user, number);
2543 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2545 struct ast_variable *var;
2546 struct ast_vm_user *vmu;
2548 vmu = ast_calloc(1, sizeof *vmu);
2551 ast_set_flag(vmu, VM_ALLOCED);
2552 populate_defaults(vmu);
2554 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2556 apply_options_full(vmu, var);
2557 ast_variables_destroy(var);
2565 /* Interfaces to C-client */
2567 void mm_exists(MAILSTREAM * stream, unsigned long number)
2569 /* mail_ping will callback here if new mail! */
2570 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2571 if (number == 0) return;
2576 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2578 /* mail_ping will callback here if expunged mail! */
2579 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2580 if (number == 0) return;
2585 void mm_flags(MAILSTREAM * stream, unsigned long number)
2587 /* mail_ping will callback here if read mail! */
2588 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2589 if (number == 0) return;
2594 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2596 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2597 mm_log (string, errflg);
2601 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2603 if (delimiter == '\0') {
2607 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2608 if (attributes & LATT_NOINFERIORS)
2609 ast_debug(5, "no inferiors\n");
2610 if (attributes & LATT_NOSELECT)
2611 ast_debug(5, "no select\n");
2612 if (attributes & LATT_MARKED)
2613 ast_debug(5, "marked\n");
2614 if (attributes & LATT_UNMARKED)
2615 ast_debug(5, "unmarked\n");
2619 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2621 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2622 if (attributes & LATT_NOINFERIORS)
2623 ast_debug(5, "no inferiors\n");
2624 if (attributes & LATT_NOSELECT)
2625 ast_debug(5, "no select\n");
2626 if (attributes & LATT_MARKED)
2627 ast_debug(5, "marked\n");
2628 if (attributes & LATT_UNMARKED)
2629 ast_debug(5, "unmarked\n");
2633 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2635 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2636 if (status->flags & SA_MESSAGES)
2637 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2638 if (status->flags & SA_RECENT)
2639 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2640 if (status->flags & SA_UNSEEN)
2641 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2642 if (status->flags & SA_UIDVALIDITY)
2643 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2644 if (status->flags & SA_UIDNEXT)
2645 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2646 ast_log(AST_LOG_NOTICE, "\n");
2650 void mm_log(char *string, long errflg)
2652 switch ((short) errflg) {
2654 ast_debug(1, "IMAP Info: %s\n", string);
2658 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2661 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2667 void mm_dlog(char *string)
2669 ast_log(AST_LOG_NOTICE, "%s\n", string);
2673 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2675 struct ast_vm_user *vmu;
2677 ast_debug(4, "Entering callback mm_login\n");
2679 ast_copy_string(user, mb->user, MAILTMPLEN);
2681 /* We should only do this when necessary */
2682 if (!ast_strlen_zero(authpassword)) {
2683 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2685 AST_LIST_TRAVERSE(&users, vmu, list) {
2686 if (!strcasecmp(mb->user, vmu->imapuser)) {
2687 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2692 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2693 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2701 void mm_critical(MAILSTREAM * stream)
2706 void mm_nocritical(MAILSTREAM * stream)
2711 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2713 kill (getpid (), SIGSTOP);
2718 void mm_fatal(char *string)
2720 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2723 /* C-client callback to handle quota */
2724 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2726 struct vm_state *vms;
2727 char *mailbox = stream->mailbox, *user;
2728 char buf[1024] = "";
2729 unsigned long usage = 0, limit = 0;
2732 usage = pquota->usage;
2733 limit = pquota->limit;
2734 pquota = pquota->next;
2737 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)))) {
2738 ast_log(AST_LOG_ERROR, "No state found.\n");
2742 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2744 vms->quota_usage = usage;
2745 vms->quota_limit = limit;
2748 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2750 char *start, *eol_pnt;
2753 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2756 taglen = strlen(tag) + 1;
2760 if (!(start = strstr(header, tag)))
2763 /* Since we can be called multiple times we should clear our buffer */
2764 memset(buf, 0, len);
2766 ast_copy_string(buf, start+taglen, len);
2767 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2772 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2774 char *start, *quote, *eol_pnt;
2776 if (ast_strlen_zero(mailbox))
2779 if (!(start = strstr(mailbox, "/user=")))
2782 ast_copy_string(buf, start+6, len);
2784 if (!(quote = strchr(buf, '\"'))) {
2785 if (!(eol_pnt = strchr(buf, '/')))
2786 eol_pnt = strchr(buf,'}');
2790 eol_pnt = strchr(buf+1,'\"');
2796 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2798 struct vm_state *vms_p;
2800 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2801 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2804 if (option_debug > 4)
2805 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2806 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2808 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2809 ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
2810 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2811 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2812 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2813 vms_p->imapversion = vmu->imapversion;
2814 if (option_debug > 4)
2815 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2817 /* set mailbox to INBOX! */
2818 ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
2819 init_vm_state(vms_p);
2820 vmstate_insert(vms_p);
2824 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2826 struct vmstate *vlist = NULL;
2829 struct vm_state *vms;
2830 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2831 vms = pthread_getspecific(ts_vmstate.key);
2835 AST_LIST_LOCK(&vmstates);
2836 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2838 ast_debug(3, "error: vms is NULL for %s\n", user);
2841 if (vlist->vms->imapversion != imapversion) {
2844 if (!vlist->vms->imapuser) {
2845 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2849 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2850 AST_LIST_UNLOCK(&vmstates);
2854 AST_LIST_UNLOCK(&vmstates);
2856 ast_debug(3, "%s not found in vmstates\n", user);
2861 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2864 struct vmstate *vlist = NULL;
2865 const char *local_context = S_OR(context, "default");
2868 struct vm_state *vms;
2869 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2870 vms = pthread_getspecific(ts_vmstate.key);
2874 AST_LIST_LOCK(&vmstates);
2875 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2877 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2880 if (vlist->vms->imapversion != imapversion) {
2883 if (!vlist->vms->username || !vlist->vms->context) {
2884 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2888 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);
2890 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2891 ast_debug(3, "Found it!\n");
2892 AST_LIST_UNLOCK(&vmstates);
2896 AST_LIST_UNLOCK(&vmstates);
2898 ast_debug(3, "%s not found in vmstates\n", mailbox);
2903 static void vmstate_insert(struct vm_state *vms)
2906 struct vm_state *altvms;
2908 /* If interactive, it probably already exists, and we should
2909 use the one we already have since it is more up to date.
2910 We can compare the username to find the duplicate */
2911 if (vms->interactive == 1) {
2912 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2914 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2915 vms->newmessages = altvms->newmessages;
2916 vms->oldmessages = altvms->oldmessages;
2917 vms->vmArrayIndex = altvms->vmArrayIndex;
2918 vms->lastmsg = altvms->lastmsg;
2919 vms->curmsg = altvms->curmsg;
2920 /* get a pointer to the persistent store */
2921 vms->persist_vms = altvms;
2922 /* Reuse the mailstream? */
2923 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2924 vms->mailstream = altvms->mailstream;
2926 vms->mailstream = NIL;
2932 if (!(v = ast_calloc(1, sizeof(*v))))
2937 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2939 AST_LIST_LOCK(&vmstates);
2940 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2941 AST_LIST_UNLOCK(&vmstates);
2944 static void vmstate_delete(struct vm_state *vms)
2946 struct vmstate *vc = NULL;
2947 struct vm_state *altvms = NULL;
2949 /* If interactive, we should copy pertinent info
2950 back to the persistent state (to make update immediate) */
2951 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2952 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2953 altvms->newmessages = vms->newmessages;
2954 altvms->oldmessages = vms->oldmessages;
2955 altvms->updated = 1;
2956 vms->mailstream = mail_close(vms->mailstream);
2958 /* Interactive states are not stored within the persistent list */
2962 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2964 AST_LIST_LOCK(&vmstates);
2965 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2966 if (vc->vms == vms) {
2967 AST_LIST_REMOVE_CURRENT(list);
2971 AST_LIST_TRAVERSE_SAFE_END
2972 AST_LIST_UNLOCK(&vmstates);
2975 ast_mutex_destroy(&vc->vms->lock);
2979 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2982 static void set_update(MAILSTREAM * stream)
2984 struct vm_state *vms;
2985 char *mailbox = stream->mailbox, *user;
2986 char buf[1024] = "";
2988 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2989 if (user && option_debug > 2)
2990 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2994 ast_debug(3, "User %s mailbox set for update.\n", user);
2996 vms->updated = 1; /* Set updated flag since mailbox changed */
2999 static void init_vm_state(struct vm_state *vms)
3002 vms->vmArrayIndex = 0;
3003 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
3004 vms->msgArray[x] = 0;
3006 ast_mutex_init(&vms->lock);
3009 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
3013 char *fn = is_intro ? vms->introfn : vms->fn;
3015 unsigned long newlen;
3018 if (!body || body == NIL)
3021 ast_mutex_lock(&vms->lock);
3022 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
3023 ast_mutex_unlock(&vms->lock);
3024 if (body_content != NIL) {
3025 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
3026 /* ast_debug(1,body_content); */
3027 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
3028 /* If the body of the file is empty, return an error */
3032 write_file(filename, (char *) body_decoded, newlen);
3034 ast_debug(5, "Body of message is NULL.\n");
3041 * \brief Get delimiter via mm_list callback
3044 * Determines the delimiter character that is used by the underlying IMAP based mail store.
3046 /* MUTEX should already be held */
3047 static void get_mailbox_delimiter(MAILSTREAM *stream) {
3049 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
3050 mail_list(stream, tmp, "*");
3054 * \brief Check Quota for user
3055 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3056 * \param mailbox the mailbox to check the quota for.
3058 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3060 static void check_quota(struct vm_state *vms, char *mailbox) {
3061 ast_mutex_lock(&vms->lock);
3062 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3063 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3064 if (vms && vms->mailstream != NULL) {
3065 imap_getquotaroot(vms->mailstream, mailbox);
3067 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3069 ast_mutex_unlock(&vms->lock);
3072 #endif /* IMAP_STORAGE */
3074 /*! \brief Lock file path
3075 only return failure if ast_lock_path returns 'timeout',
3076 not if the path does not exist or any other reason
3078 static int vm_lock_path(const char *path)
3080 switch (ast_lock_path(path)) {
3081 case AST_LOCK_TIMEOUT:
3090 struct generic_prepare_struct {
3096 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
3098 struct generic_prepare_struct *gps = data;
3102 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3103 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3104 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3107 res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
3108 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3109 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
3110 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3113 for (i = 0; i < gps->argc; i++)
3114 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
3120 * \brief Retrieves a file from an ODBC data store.
3121 * \param dir the path to the file to be retreived.
3122 * \param msgnum the message number, such as within a mailbox folder.
3124 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
3125 * 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.
3127 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
3128 * The output is the message information file with the name msgnum and the extension .txt
3129 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
3131 * \return 0 on success, -1 on error.
3133 static int retrieve_file(char *dir, int msgnum)
3139 void *fdm = MAP_FAILED;
3140 SQLSMALLINT colcount = 0;
3147 SQLSMALLINT datatype;
3148 SQLSMALLINT decimaldigits;
3149 SQLSMALLINT nullable;
3155 char full_fn[PATH_MAX];
3157 char *argv[] = { dir, msgnums };
3158 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3160 struct odbc_obj *obj;
3161 obj = ast_odbc_request_obj(odbc_database, 0);
3163 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3164 c = strchr(fmt, '|');
3167 if (!strcasecmp(fmt, "wav49"))
3169 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3171 make_file(fn, sizeof(fn), dir, msgnum);
3173 ast_copy_string(fn, dir, sizeof(fn));
3175 /* Create the information file */
3176 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3178 if (!(f = fopen(full_fn, "w+"))) {
3179 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
3183 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3184 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3185 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3187 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3188 ast_odbc_release_obj(obj);
3191 res = SQLFetch(stmt);
3192 if (res == SQL_NO_DATA) {
3193 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3194 ast_odbc_release_obj(obj);
3196 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3197 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3198 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3199 ast_odbc_release_obj(obj);
3202 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3204 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3205 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3206 ast_odbc_release_obj(obj);
3209 res = SQLNumResultCols(stmt, &colcount);
3210 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3211 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3212 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3213 ast_odbc_release_obj(obj);
3217 fprintf(f, "[message]\n");
3218 for (x = 0; x < colcount; x++) {
3220 collen = sizeof(coltitle);
3221 res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
3222 &datatype, &colsize, &decimaldigits, &nullable);
3223 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3224 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3225 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3226 ast_odbc_release_obj(obj);
3229 if (!strcasecmp(coltitle, "recording")) {
3231 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3235 lseek(fd, fdlen - 1, SEEK_SET);
3236 if (write(fd, tmp, 1) != 1) {
3241 /* Read out in small chunks */
3242 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3243 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3244 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3245 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3246 ast_odbc_release_obj(obj);
3249 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3250 munmap(fdm, CHUNKSIZE);
3251 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3252 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3254 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3255 ast_odbc_release_obj(obj);
3260 if (truncate(full_fn, fdlen) < 0) {
3261 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3265 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3266 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3267 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3268 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3269 ast_odbc_release_obj(obj);
3272 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3273 fprintf(f, "%s=%s\n", coltitle, rowdata);
3276 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3277 ast_odbc_release_obj(obj);
3279 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3289 * \brief Determines the highest message number in use for a given user and mailbox folder.
3291 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3293 * This method is used when mailboxes are stored in an ODBC back end.
3294 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3296 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3298 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3305 char *argv[] = { dir };
3306 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3308 struct odbc_obj *obj;
3309 obj = ast_odbc_request_obj(odbc_database, 0);
3311 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3312 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3314 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3315 ast_odbc_release_obj(obj);
3318 res = SQLFetch(stmt);
3319 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3320 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3321 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3322 ast_odbc_release_obj(obj);
3325 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3326 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3327 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3328 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3329 ast_odbc_release_obj(obj);
3332 if (sscanf(rowdata, "%30d", &x) != 1)
3333 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3334 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3335 ast_odbc_release_obj(obj);
3337 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3343 * \brief Determines if the specified message exists.
3344 * \param dir the folder the mailbox folder to look for messages.
3345 * \param msgnum the message index to query for.
3347 * This method is used when mailboxes are stored in an ODBC back end.
3349 * \return greater than zero if the message exists, zero when the message does not exist or on error.
3351 static int message_exists(char *dir, int msgnum)
3359 char *argv[] = { dir, msgnums };
3360 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3362 struct odbc_obj *obj;
3363 obj = ast_odbc_request_obj(odbc_database, 0);
3365 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3366 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3367 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3369 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3370 ast_odbc_release_obj(obj);
3373 res = SQLFetch(stmt);
3374 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3375 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3376 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3377 ast_odbc_release_obj(obj);
3380 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3381 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3382 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3383 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3384 ast_odbc_release_obj(obj);
3387 if (sscanf(rowdata, "%30d", &x) != 1)
3388 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3389 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3390 ast_odbc_release_obj(obj);
3392 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3398 * \brief returns the one-based count for messages.
3400 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3402 * This method is used when mailboxes are stored in an ODBC back end.
3403 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3404 * one-based messages.
3405 * This method just calls last_message_index and returns +1 of its value.
3407 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3409 static int count_messages(struct ast_vm_user *vmu, char *dir)
3411 return last_message_index(vmu, dir) + 1;
3415 * \brief Deletes a message from the mailbox folder.
3416 * \param sdir The mailbox folder to work in.
3417 * \param smsg The message index to be deleted.
3419 * This method is used when mailboxes are stored in an ODBC back end.
3420 * The specified message is directly deleted from the database 'voicemessages' table.
3422 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3424 static void delete_file(const char *sdir, int smsg)
3429 char *argv[] = { NULL, msgnums };
3430 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3431 struct odbc_obj *obj;
3433 argv[0] = ast_strdupa(sdir);
3435 obj = ast_odbc_request_obj(odbc_database, 0);
3437 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3438 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3439 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3441 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3443 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3444 ast_odbc_release_obj(obj);
3446 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3451 * \brief Copies a voicemail from one mailbox to another.
3452 * \param sdir the folder for which to look for the message to be copied.
3453 * \param smsg the index of the message to be copied.
3454 * \param ddir the destination folder to copy the message into.
3455 * \param dmsg the index to be used for the copied message.
3456 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3457 * \param dmailboxcontext The context for the destination user.
3459 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3461 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3467 struct odbc_obj *obj;
3468 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3469 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3471 delete_file(ddir, dmsg);
3472 obj = ast_odbc_request_obj(odbc_database, 0);
3474 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3475 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3476 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);
3477 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3479 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3481 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3482 ast_odbc_release_obj(obj);
3484 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3488 struct insert_data {
3491 const char *msgnums;
3495 const char *context;
3496 const char *macrocontext;
3497 const char *callerid;
3498 const char *origtime;
3499 const char *duration;
3500 const char *mailboxuser;
3501 const char *mailboxcontext;
3502 const char *category;
3506 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3508 struct insert_data *data = vdata;
3512 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3513 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3514 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3515 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3519 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
3520 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
3521 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
3522 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
3523 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
3524 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
3525 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
3526 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
3527 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
3528 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
3529 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
3530 if (!ast_strlen_zero(data->category)) {
3531 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
3533 res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
3534 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3535 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3536 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3544 * \brief Stores a voicemail into the database.
3545 * \param dir the folder the mailbox folder to store the message.
3546 * \param mailboxuser the user owning the mailbox folder.
3547 * \param mailboxcontext
3548 * \param msgnum the message index for the message to be stored.
3550 * This method is used when mailboxes are stored in an ODBC back end.
3551 * The message sound file and information file is looked up on the file system.
3552 * A SQL query is invoked to store the message into the (MySQL) database.
3554 * \return the zero on success -1 on error.
3556 static int store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum)
3560 void *fdm = MAP_FAILED;
3566 char full_fn[PATH_MAX];