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.
46 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" touch_on_change="apps/app_voicemail.c apps/app_directory.c">
47 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
48 <conflict>ODBC_STORAGE</conflict>
49 <conflict>IMAP_STORAGE</conflict>
50 <defaultenabled>yes</defaultenabled>
52 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
53 <depend>generic_odbc</depend>
55 <conflict>IMAP_STORAGE</conflict>
56 <conflict>FILE_STORAGE</conflict>
57 <defaultenabled>no</defaultenabled>
59 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
60 <depend>imap_tk</depend>
61 <conflict>ODBC_STORAGE</conflict>
62 <conflict>FILE_STORAGE</conflict>
64 <defaultenabled>no</defaultenabled>
75 #ifdef USE_SYSTEM_IMAP
76 #include <imap/c-client.h>
77 #include <imap/imap4r1.h>
78 #include <imap/linkage.h>
79 #elif defined (USE_SYSTEM_CCLIENT)
80 #include <c-client/c-client.h>
81 #include <c-client/imap4r1.h>
82 #include <c-client/linkage.h>
90 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
92 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
98 #if defined(__FreeBSD__) || defined(__OpenBSD__)
102 #include "asterisk/logger.h"
103 #include "asterisk/lock.h"
104 #include "asterisk/file.h"
105 #include "asterisk/channel.h"
106 #include "asterisk/pbx.h"
107 #include "asterisk/config.h"
108 #include "asterisk/say.h"
109 #include "asterisk/module.h"
110 #include "asterisk/adsi.h"
111 #include "asterisk/app.h"
112 #include "asterisk/manager.h"
113 #include "asterisk/dsp.h"
114 #include "asterisk/localtime.h"
115 #include "asterisk/cli.h"
116 #include "asterisk/utils.h"
117 #include "asterisk/stringfields.h"
118 #include "asterisk/smdi.h"
119 #include "asterisk/astobj2.h"
120 #include "asterisk/event.h"
121 #include "asterisk/taskprocessor.h"
122 #include "asterisk/test.h"
125 #include "asterisk/res_odbc.h"
129 #include "asterisk/threadstorage.h"
133 <application name="VoiceMail" language="en_US">
135 Leave a Voicemail message.
138 <parameter name="mailboxs" argsep="&" required="true">
139 <argument name="mailbox1" argsep="@" required="true">
140 <argument name="mailbox" required="true" />
141 <argument name="context" />
143 <argument name="mailbox2" argsep="@" multiple="true">
144 <argument name="mailbox" required="true" />
145 <argument name="context" />
148 <parameter name="options">
151 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
154 <argument name="c" />
155 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
156 if played during the greeting. Context defaults to the current context.</para>
159 <argument name="#" required="true" />
160 <para>Use the specified amount of gain when recording the voicemail
161 message. The units are whole-number decibels (dB). Only works on supported
162 technologies, which is DAHDI only.</para>
165 <para>Skip the playback of instructions for leaving a message to the
166 calling party.</para>
169 <para>Play the <literal>unavailable</literal> greeting.</para>
172 <para>Mark message as <literal>URGENT</literal>.</para>
175 <para>Mark message as <literal>PRIORITY</literal>.</para>
181 <para>This application allows the calling party to leave a message for the specified
182 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
183 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
185 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
188 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
191 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
194 <para>This application will set the following channel variable upon completion:</para>
196 <variable name="VMSTATUS">
197 <para>This indicates the status of the execution of the VoiceMail application.</para>
198 <value name="SUCCESS" />
199 <value name="USEREXIT" />
200 <value name="FAILED" />
205 <application name="VoiceMailMain" language="en_US">
207 Check Voicemail messages.
210 <parameter name="mailbox" required="true" argsep="@">
211 <argument name="mailbox" />
212 <argument name="context" />
214 <parameter name="options">
217 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
218 the mailbox that is entered by the caller.</para>
221 <argument name="#" required="true" />
222 <para>Use the specified amount of gain when recording a voicemail message.
223 The units are whole-number decibels (dB).</para>
226 <para>Skip checking the passcode for the mailbox.</para>
229 <argument name="folder" required="true" />
230 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
231 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
233 <enum name="0"><para>INBOX</para></enum>
234 <enum name="1"><para>Old</para></enum>
235 <enum name="2"><para>Work</para></enum>
236 <enum name="3"><para>Family</para></enum>
237 <enum name="4"><para>Friends</para></enum>
238 <enum name="5"><para>Cust1</para></enum>
239 <enum name="6"><para>Cust2</para></enum>
240 <enum name="7"><para>Cust3</para></enum>
241 <enum name="8"><para>Cust4</para></enum>
242 <enum name="9"><para>Cust5</para></enum>
249 <para>This application allows the calling party to check voicemail messages. A specific
250 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
251 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
252 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
253 <literal>default</literal> context will be used.</para>
254 <para>The VoiceMailMain application will exit if the following DTMF digit is entered as Mailbox
255 or Password, and the extension exists:</para>
258 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
263 <application name="MailboxExists" language="en_US">
265 Check to see if Voicemail mailbox exists.
268 <parameter name="mailbox" required="true" argsep="@">
269 <argument name="mailbox" required="true" />
270 <argument name="context" />
272 <parameter name="options">
273 <para>None options.</para>
277 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
278 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
280 <para>This application will set the following channel variable upon completion:</para>
282 <variable name="VMBOXEXISTSSTATUS">
283 <para>This will contain the status of the execution of the MailboxExists application.
284 Possible values include:</para>
285 <value name="SUCCESS" />
286 <value name="FAILED" />
291 <application name="VMAuthenticate" language="en_US">
293 Authenticate with Voicemail passwords.
296 <parameter name="mailbox" required="true" argsep="@">
297 <argument name="mailbox" />
298 <argument name="context" />
300 <parameter name="options">
303 <para>Skip playing the initial prompts.</para>
309 <para>This application behaves the same way as the Authenticate application, but the passwords
310 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
311 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
312 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
314 <para>The VMAuthenticate application will exit if the following DTMF digit is entered as Mailbox
315 or Password, and the extension exists:</para>
318 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
323 <application name="VMSayName" language="en_US">
325 Play the name of a voicemail user
328 <parameter name="mailbox" required="true" argsep="@">
329 <argument name="mailbox" />
330 <argument name="context" />
334 <para>This application will say the recorded name of the voicemail user specified as the
335 argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
338 <function name="MAILBOX_EXISTS" language="en_US">
340 Tell if a mailbox is configured.
343 <parameter name="mailbox" required="true" />
344 <parameter name="context" />
347 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
348 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
352 <manager name="VoicemailUsersList" language="en_US">
354 List All Voicemail User Information.
357 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
365 static char imapserver[48];
366 static char imapport[8];
367 static char imapflags[128];
368 static char imapfolder[64];
369 static char imapparentfolder[64] = "\0";
370 static char greetingfolder[64];
371 static char authuser[32];
372 static char authpassword[42];
373 static int imapversion = 1;
375 static int expungeonhangup = 1;
376 static int imapgreetings = 0;
377 static char delimiter = '\0';
382 AST_THREADSTORAGE(ts_vmstate);
384 /* Forward declarations for IMAP */
385 static int init_mailstream(struct vm_state *vms, int box);
386 static void write_file(char *filename, char *buffer, unsigned long len);
387 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
388 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
389 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
390 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
391 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
392 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
393 static void vmstate_insert(struct vm_state *vms);
394 static void vmstate_delete(struct vm_state *vms);
395 static void set_update(MAILSTREAM * stream);
396 static void init_vm_state(struct vm_state *vms);
397 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
398 static void get_mailbox_delimiter(MAILSTREAM *stream);
399 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
400 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
401 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);
402 static void update_messages_by_imapuser(const char *user, unsigned long number);
403 static int vm_delete(char *file);
405 static int imap_remove_file (char *dir, int msgnum);
406 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
407 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
408 static void check_quota(struct vm_state *vms, char *mailbox);
409 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
411 struct vm_state *vms;
412 AST_LIST_ENTRY(vmstate) list;
415 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
419 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
421 #define COMMAND_TIMEOUT 5000
422 /* Don't modify these here; set your umask at runtime instead */
423 #define VOICEMAIL_DIR_MODE 0777
424 #define VOICEMAIL_FILE_MODE 0666
425 #define CHUNKSIZE 65536
427 #define VOICEMAIL_CONFIG "voicemail.conf"
428 #define ASTERISK_USERNAME "asterisk"
430 /* Define fast-forward, pause, restart, and reverse keys
431 while listening to a voicemail message - these are
432 strings, not characters */
433 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
434 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
435 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
436 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
437 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
438 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
440 /* Default mail command to mail voicemail. Change it with the
441 mailcmd= command in voicemail.conf */
442 #define SENDMAIL "/usr/sbin/sendmail -t"
444 #define INTRO "vm-intro"
447 #define MAXMSGLIMIT 9999
449 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
451 #define BASELINELEN 72
452 #define BASEMAXINLINE 256
459 #define MAX_DATETIME_FORMAT 512
460 #define MAX_NUM_CID_CONTEXTS 10
462 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
463 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
464 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
465 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
466 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
467 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
468 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
469 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
470 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
471 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
472 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
473 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
474 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
475 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
476 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
477 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
478 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
479 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
480 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
481 #define ERROR_LOCK_PATH -100
482 #define OPERATOR_EXIT 300
494 enum vm_option_flags {
495 OPT_SILENT = (1 << 0),
496 OPT_BUSY_GREETING = (1 << 1),
497 OPT_UNAVAIL_GREETING = (1 << 2),
498 OPT_RECORDGAIN = (1 << 3),
499 OPT_PREPEND_MAILBOX = (1 << 4),
500 OPT_AUTOPLAY = (1 << 6),
501 OPT_DTMFEXIT = (1 << 7),
502 OPT_MESSAGE_Urgent = (1 << 8),
503 OPT_MESSAGE_PRIORITY = (1 << 9)
506 enum vm_option_args {
507 OPT_ARG_RECORDGAIN = 0,
508 OPT_ARG_PLAYFOLDER = 1,
509 OPT_ARG_DTMFEXIT = 2,
510 /* This *must* be the last value in this enum! */
511 OPT_ARG_ARRAY_SIZE = 3,
514 enum vm_passwordlocation {
515 OPT_PWLOC_VOICEMAILCONF = 0,
516 OPT_PWLOC_SPOOLDIR = 1,
517 OPT_PWLOC_USERSCONF = 2,
520 AST_APP_OPTIONS(vm_app_options, {
521 AST_APP_OPTION('s', OPT_SILENT),
522 AST_APP_OPTION('b', OPT_BUSY_GREETING),
523 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
524 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
525 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
526 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
527 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
528 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
529 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
532 static int load_config(int reload);
534 /*! \page vmlang Voicemail Language Syntaxes Supported
536 \par Syntaxes supported, not really language codes.
543 \arg \b pt - Portuguese
544 \arg \b pt_BR - Portuguese (Brazil)
546 \arg \b no - Norwegian
548 \arg \b tw - Chinese (Taiwan)
549 \arg \b ua - Ukrainian
551 German requires the following additional soundfile:
552 \arg \b 1F einE (feminine)
554 Spanish requires the following additional soundfile:
555 \arg \b 1M un (masculine)
557 Dutch, Portuguese & Spanish require the following additional soundfiles:
558 \arg \b vm-INBOXs singular of 'new'
559 \arg \b vm-Olds singular of 'old/heard/read'
562 \arg \b vm-INBOX nieuwe (nl)
563 \arg \b vm-Old oude (nl)
566 \arg \b vm-new-a 'new', feminine singular accusative
567 \arg \b vm-new-e 'new', feminine plural accusative
568 \arg \b vm-new-ych 'new', feminine plural genitive
569 \arg \b vm-old-a 'old', feminine singular accusative
570 \arg \b vm-old-e 'old', feminine plural accusative
571 \arg \b vm-old-ych 'old', feminine plural genitive
572 \arg \b digits/1-a 'one', not always same as 'digits/1'
573 \arg \b digits/2-ie 'two', not always same as 'digits/2'
576 \arg \b vm-nytt singular of 'new'
577 \arg \b vm-nya plural of 'new'
578 \arg \b vm-gammalt singular of 'old'
579 \arg \b vm-gamla plural of 'old'
580 \arg \b digits/ett 'one', not always same as 'digits/1'
583 \arg \b vm-ny singular of 'new'
584 \arg \b vm-nye plural of 'new'
585 \arg \b vm-gammel singular of 'old'
586 \arg \b vm-gamle plural of 'old'
594 Italian requires the following additional soundfile:
598 \arg \b vm-nuovi new plural
599 \arg \b vm-vecchio old
600 \arg \b vm-vecchi old plural
602 Chinese (Taiwan) requires the following additional soundfile:
603 \arg \b vm-tong A class-word for call (tong1)
604 \arg \b vm-ri A class-word for day (ri4)
605 \arg \b vm-you You (ni3)
606 \arg \b vm-haveno Have no (mei2 you3)
607 \arg \b vm-have Have (you3)
608 \arg \b vm-listen To listen (yao4 ting1)
611 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
612 spelled among others when you have to change folder. For the above reasons, vm-INBOX
613 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
622 unsigned char iobuf[BASEMAXINLINE];
625 /*! Structure for linked list of users
626 * Use ast_vm_user_destroy() to free one of these structures. */
628 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
629 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
630 char password[80]; /*!< Secret pin code, numbers only */
631 char fullname[80]; /*!< Full name, for directory app */
632 char email[80]; /*!< E-mail address */
633 char *emailsubject; /*!< E-mail subject */
634 char *emailbody; /*!< E-mail body */
635 char pager[80]; /*!< E-mail address to pager (no attachment) */
636 char serveremail[80]; /*!< From: Mail address */
637 char mailcmd[160]; /*!< Configurable mail command */
638 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
639 char zonetag[80]; /*!< Time zone */
640 char locale[20]; /*!< The locale (for presentation of date/time) */
643 char uniqueid[80]; /*!< Unique integer identifier */
645 char attachfmt[20]; /*!< Attachment format */
646 unsigned int flags; /*!< VM_ flags */
648 int minsecs; /*!< Minimum number of seconds per message for this mailbox */
649 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
650 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
651 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
652 int passwordlocation; /*!< Storage location of the password */
654 char imapuser[80]; /*!< IMAP server login */
655 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
656 char imapfolder[64]; /*!< IMAP voicemail folder */
657 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
658 int imapversion; /*!< If configuration changes, use the new values */
660 double volgain; /*!< Volume gain for voicemails sent via email */
661 AST_LIST_ENTRY(ast_vm_user) list;
664 /*! Voicemail time zones */
666 AST_LIST_ENTRY(vm_zone) list;
669 char msg_format[512];
672 #define VMSTATE_MAX_MSG_ARRAY 256
674 /*! Voicemail mailbox state */
679 char curdir[PATH_MAX];
680 char vmbox[PATH_MAX];
682 char intro[PATH_MAX];
694 int updated; /*!< decremented on each mail check until 1 -allows delay */
695 long msgArray[VMSTATE_MAX_MSG_ARRAY];
696 MAILSTREAM *mailstream;
698 char imapuser[80]; /*!< IMAP server login */
699 char imapfolder[64]; /*!< IMAP voicemail folder */
702 char introfn[PATH_MAX]; /*!< Name of prepended file */
703 unsigned int quota_limit;
704 unsigned int quota_usage;
705 struct vm_state *persist_vms;
710 static char odbc_database[80];
711 static char odbc_table[80];
712 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
713 #define DISPOSE(a,b) remove_file(a,b)
714 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
715 #define EXISTS(a,b,c,d) (message_exists(a,b))
716 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
717 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
718 #define DELETE(a,b,c,d) (delete_file(a,b))
721 #define DISPOSE(a,b) (imap_remove_file(a,b))
722 #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))
723 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
724 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
725 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
726 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
727 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
729 #define RETRIEVE(a,b,c,d)
731 #define STORE(a,b,c,d,e,f,g,h,i,j)
732 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
733 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
734 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
735 #define DELETE(a,b,c,d) (vm_delete(c))
739 static char VM_SPOOL_DIR[PATH_MAX];
741 static char ext_pass_cmd[128];
742 static char ext_pass_check_cmd[128];
746 #define PWDCHANGE_INTERNAL (1 << 1)
747 #define PWDCHANGE_EXTERNAL (1 << 2)
748 static int pwdchange = PWDCHANGE_INTERNAL;
751 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
754 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
756 # define tdesc "Comedian Mail (Voicemail System)"
760 static char userscontext[AST_MAX_EXTENSION] = "default";
762 static char *addesc = "Comedian Mail";
764 /* Leave a message */
765 static char *app = "VoiceMail";
767 /* Check mail, control, etc */
768 static char *app2 = "VoiceMailMain";
770 static char *app3 = "MailboxExists";
771 static char *app4 = "VMAuthenticate";
773 static char *sayname_app = "VMSayName";
775 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
776 static AST_LIST_HEAD_STATIC(zones, vm_zone);
777 static char zonetag[80];
778 static char locale[20];
779 static int maxsilence;
781 static int maxdeletedmsg;
782 static int silencethreshold = 128;
783 static char serveremail[80];
784 static char mailcmd[160]; /* Configurable mail cmd */
785 static char externnotify[160];
786 static struct ast_smdi_interface *smdi_iface = NULL;
787 static char vmfmts[80];
788 static double volgain;
789 static int vmminsecs;
790 static int vmmaxsecs;
793 static int maxlogins;
794 static int minpassword;
795 static int passwordlocation;
797 /*! Poll mailboxes for changes since there is something external to
798 * app_voicemail that may change them. */
799 static unsigned int poll_mailboxes;
801 /*! Polling frequency */
802 static unsigned int poll_freq;
803 /*! By default, poll every 30 seconds */
804 #define DEFAULT_POLL_FREQ 30
806 AST_MUTEX_DEFINE_STATIC(poll_lock);
807 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
808 static pthread_t poll_thread = AST_PTHREADT_NULL;
809 static unsigned char poll_thread_run;
811 /*! Subscription to ... MWI event subscriptions */
812 static struct ast_event_sub *mwi_sub_sub;
813 /*! Subscription to ... MWI event un-subscriptions */
814 static struct ast_event_sub *mwi_unsub_sub;
817 * \brief An MWI subscription
819 * This is so we can keep track of which mailboxes are subscribed to.
820 * This way, we know which mailboxes to poll when the pollmailboxes
821 * option is being used.
824 AST_RWLIST_ENTRY(mwi_sub) entry;
832 struct mwi_sub_task {
838 static struct ast_taskprocessor *mwi_subscription_tps;
840 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
842 /* custom audio control prompts for voicemail playback */
843 static char listen_control_forward_key[12];
844 static char listen_control_reverse_key[12];
845 static char listen_control_pause_key[12];
846 static char listen_control_restart_key[12];
847 static char listen_control_stop_key[12];
849 /* custom password sounds */
850 static char vm_password[80] = "vm-password";
851 static char vm_newpassword[80] = "vm-newpassword";
852 static char vm_passchanged[80] = "vm-passchanged";
853 static char vm_reenterpassword[80] = "vm-reenterpassword";
854 static char vm_mismatch[80] = "vm-mismatch";
855 static char vm_invalid_password[80] = "vm-invalid-password";
856 static char vm_pls_try_again[80] = "vm-pls-try-again";
858 static struct ast_flags globalflags = {0};
860 static int saydurationminfo;
862 static char dialcontext[AST_MAX_CONTEXT] = "";
863 static char callcontext[AST_MAX_CONTEXT] = "";
864 static char exitcontext[AST_MAX_CONTEXT] = "";
866 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
869 static char *emailbody = NULL;
870 static char *emailsubject = NULL;
871 static char *pagerbody = NULL;
872 static char *pagersubject = NULL;
873 static char fromstring[100];
874 static char pagerfromstring[100];
875 static char charset[32] = "ISO-8859-1";
877 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
878 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
879 static int adsiver = 1;
880 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
881 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
883 /* Forward declarations - generic */
884 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
885 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);
886 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
887 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
888 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
889 signed char record_gain, struct vm_state *vms, char *flag);
890 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
891 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
892 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);
893 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);
894 static void apply_options(struct ast_vm_user *vmu, const char *options);
895 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);
896 static int is_valid_dtmf(const char *key);
897 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
898 static int write_password_to_file(const char *secretfn, const char *password);
900 struct ao2_container *inprocess_container;
908 static int inprocess_hash_fn(const void *obj, const int flags)
910 const struct inprocess *i = obj;
911 return atoi(i->mailbox);
914 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
916 struct inprocess *i = obj, *j = arg;
917 if (!strcmp(i->mailbox, j->mailbox)) {
920 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
923 static int inprocess_count(const char *context, const char *mailbox, int delta)
925 struct inprocess *i, *arg = alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
926 arg->context = arg->mailbox + strlen(mailbox) + 1;
927 strcpy(arg->mailbox, mailbox); /* SAFE */
928 strcpy(arg->context, context); /* SAFE */
929 ao2_lock(inprocess_container);
930 if ((i = ao2_find(inprocess_container, arg, 0))) {
931 int ret = ast_atomic_fetchadd_int(&i->count, delta);
932 ao2_unlock(inprocess_container);
936 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
937 ao2_unlock(inprocess_container);
940 i->context = i->mailbox + strlen(mailbox) + 1;
941 strcpy(i->mailbox, mailbox); /* SAFE */
942 strcpy(i->context, context); /* SAFE */
944 ao2_link(inprocess_container, i);
945 ao2_unlock(inprocess_container);
950 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
951 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
955 * \brief Strips control and non 7-bit clean characters from input string.
957 * \note To map control and none 7-bit characters to a 7-bit clean characters
958 * please use ast_str_encode_mine().
960 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
963 for (; *input; input++) {
968 if (bufptr == buf + buflen - 1) {
978 * \brief Sets default voicemail system options to a voicemail user.
980 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
981 * - all the globalflags
982 * - the saydurationminfo
986 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
989 static void populate_defaults(struct ast_vm_user *vmu)
991 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
992 vmu->passwordlocation = passwordlocation;
993 if (saydurationminfo) {
994 vmu->saydurationm = saydurationminfo;
996 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
997 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
998 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
999 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
1000 ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
1002 vmu->minsecs = vmminsecs;
1005 vmu->maxsecs = vmmaxsecs;
1008 vmu->maxmsg = maxmsg;
1010 if (maxdeletedmsg) {
1011 vmu->maxdeletedmsg = maxdeletedmsg;
1013 vmu->volgain = volgain;
1014 vmu->emailsubject = NULL;
1015 vmu->emailbody = NULL;
1017 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
1022 * \brief Sets a a specific property value.
1023 * \param vmu The voicemail user object to work with.
1024 * \param var The name of the property to be set.
1025 * \param value The value to be set to the property.
1027 * The property name must be one of the understood properties. See the source for details.
1029 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1032 if (!strcasecmp(var, "attach")) {
1033 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1034 } else if (!strcasecmp(var, "attachfmt")) {
1035 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1036 } else if (!strcasecmp(var, "serveremail")) {
1037 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1038 } else if (!strcasecmp(var, "language")) {
1039 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1040 } else if (!strcasecmp(var, "tz")) {
1041 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1042 } else if (!strcasecmp(var, "locale")) {
1043 ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
1045 } else if (!strcasecmp(var, "imapuser")) {
1046 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1047 vmu->imapversion = imapversion;
1048 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1049 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1050 vmu->imapversion = imapversion;
1051 } else if (!strcasecmp(var, "imapfolder")) {
1052 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1053 } else if (!strcasecmp(var, "imapvmshareid")) {
1054 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1055 vmu->imapversion = imapversion;
1057 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1058 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1059 } else if (!strcasecmp(var, "saycid")){
1060 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1061 } else if (!strcasecmp(var, "sendvoicemail")){
1062 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1063 } else if (!strcasecmp(var, "review")){
1064 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1065 } else if (!strcasecmp(var, "tempgreetwarn")){
1066 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1067 } else if (!strcasecmp(var, "messagewrap")){
1068 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1069 } else if (!strcasecmp(var, "operator")) {
1070 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1071 } else if (!strcasecmp(var, "envelope")){
1072 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1073 } else if (!strcasecmp(var, "moveheard")){
1074 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1075 } else if (!strcasecmp(var, "sayduration")){
1076 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1077 } else if (!strcasecmp(var, "saydurationm")){
1078 if (sscanf(value, "%30d", &x) == 1) {
1079 vmu->saydurationm = x;
1081 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1083 } else if (!strcasecmp(var, "forcename")){
1084 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1085 } else if (!strcasecmp(var, "forcegreetings")){
1086 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1087 } else if (!strcasecmp(var, "callback")) {
1088 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1089 } else if (!strcasecmp(var, "dialout")) {
1090 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1091 } else if (!strcasecmp(var, "exitcontext")) {
1092 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1093 } else if (!strcasecmp(var, "minsecs")) {
1094 if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1097 ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1098 vmu->minsecs = vmminsecs;
1100 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1101 vmu->maxsecs = atoi(value);
1102 if (vmu->maxsecs <= 0) {
1103 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1104 vmu->maxsecs = vmmaxsecs;
1106 vmu->maxsecs = atoi(value);
1108 if (!strcasecmp(var, "maxmessage"))
1109 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1110 } else if (!strcasecmp(var, "maxmsg")) {
1111 vmu->maxmsg = atoi(value);
1112 /* Accept maxmsg=0 (Greetings only voicemail) */
1113 if (vmu->maxmsg < 0) {
1114 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1115 vmu->maxmsg = MAXMSG;
1116 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1117 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1118 vmu->maxmsg = MAXMSGLIMIT;
1120 } else if (!strcasecmp(var, "nextaftercmd")) {
1121 ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
1122 } else if (!strcasecmp(var, "backupdeleted")) {
1123 if (sscanf(value, "%30d", &x) == 1)
1124 vmu->maxdeletedmsg = x;
1125 else if (ast_true(value))
1126 vmu->maxdeletedmsg = MAXMSG;
1128 vmu->maxdeletedmsg = 0;
1130 if (vmu->maxdeletedmsg < 0) {
1131 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1132 vmu->maxdeletedmsg = MAXMSG;
1133 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1134 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1135 vmu->maxdeletedmsg = MAXMSGLIMIT;
1137 } else if (!strcasecmp(var, "volgain")) {
1138 sscanf(value, "%30lf", &vmu->volgain);
1139 } else if (!strcasecmp(var, "passwordlocation")) {
1140 if (!strcasecmp(value, "spooldir")) {
1141 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1143 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1145 } else if (!strcasecmp(var, "options")) {
1146 apply_options(vmu, value);
1150 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1152 int fds[2], pid = 0;
1154 memset(buf, 0, len);
1157 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1160 pid = ast_safe_fork(0);
1166 snprintf(buf, len, "FAILURE: Fork failed");
1170 if (read(fds[0], buf, len) < 0) {
1171 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1176 AST_DECLARE_APP_ARGS(arg,
1179 char *mycmd = ast_strdupa(command);
1182 dup2(fds[1], STDOUT_FILENO);
1184 ast_close_fds_above_n(STDOUT_FILENO);
1186 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1188 execv(arg.v[0], arg.v);
1189 printf("FAILURE: %s", strerror(errno));
1197 * \brief Check that password meets minimum required length
1198 * \param vmu The voicemail user to change the password for.
1199 * \param password The password string to check
1201 * \return zero on ok, 1 on not ok.
1203 static int check_password(struct ast_vm_user *vmu, char *password)
1205 /* check minimum length */
1206 if (strlen(password) < minpassword)
1208 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1209 char cmd[255], buf[255];
1211 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1213 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1214 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1215 ast_debug(5, "Result: %s\n", buf);
1216 if (!strncasecmp(buf, "VALID", 5)) {
1217 ast_debug(3, "Passed password check: '%s'\n", buf);
1219 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1220 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1223 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1232 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1233 * \param vmu The voicemail user to change the password for.
1234 * \param password The new value to be set to the password for this user.
1236 * This only works if there is a realtime engine configured.
1237 * This is called from the (top level) vm_change_password.
1239 * \return zero on success, -1 on error.
1241 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1244 if (!strcmp(vmu->password, password)) {
1245 /* No change (but an update would return 0 rows updated, so we opt out here) */
1249 if (strlen(password) > 10) {
1250 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1252 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1253 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1260 * \brief Destructively Parse options and apply.
1262 static void apply_options(struct ast_vm_user *vmu, const char *options)
1267 stringp = ast_strdupa(options);
1268 while ((s = strsep(&stringp, "|"))) {
1270 if ((var = strsep(&value, "=")) && value) {
1271 apply_option(vmu, var, value);
1277 * \brief Loads the options specific to a voicemail user.
1279 * This is called when a vm_user structure is being set up, such as from load_options.
1281 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1283 for (; var; var = var->next) {
1284 if (!strcasecmp(var->name, "vmsecret")) {
1285 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1286 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1287 if (ast_strlen_zero(retval->password))
1288 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1289 } else if (!strcasecmp(var->name, "uniqueid")) {
1290 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1291 } else if (!strcasecmp(var->name, "pager")) {
1292 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1293 } else if (!strcasecmp(var->name, "email")) {
1294 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1295 } else if (!strcasecmp(var->name, "fullname")) {
1296 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1297 } else if (!strcasecmp(var->name, "context")) {
1298 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1299 } else if (!strcasecmp(var->name, "emailsubject")) {
1300 retval->emailsubject = ast_strdup(var->value);
1301 } else if (!strcasecmp(var->name, "emailbody")) {
1302 retval->emailbody = ast_strdup(var->value);
1304 } else if (!strcasecmp(var->name, "imapuser")) {
1305 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1306 retval->imapversion = imapversion;
1307 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1308 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1309 retval->imapversion = imapversion;
1310 } else if (!strcasecmp(var->name, "imapfolder")) {
1311 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1312 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1313 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1314 retval->imapversion = imapversion;
1317 apply_option(retval, var->name, var->value);
1322 * \brief Determines if a DTMF key entered is valid.
1323 * \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.
1325 * Tests the character entered against the set of valid DTMF characters.
1326 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1328 static int is_valid_dtmf(const char *key)
1331 char *local_key = ast_strdupa(key);
1333 for (i = 0; i < strlen(key); ++i) {
1334 if (!strchr(VALID_DTMF, *local_key)) {
1335 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1344 * \brief Finds a voicemail user from the realtime engine.
1349 * 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.
1351 * \return The ast_vm_user structure for the user that was found.
1353 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1355 struct ast_variable *var;
1356 struct ast_vm_user *retval;
1358 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1360 ast_set_flag(retval, VM_ALLOCED);
1362 memset(retval, 0, sizeof(*retval));
1364 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1365 populate_defaults(retval);
1366 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1367 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1369 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1371 apply_options_full(retval, var);
1372 ast_variables_destroy(var);
1383 * \brief Finds a voicemail user from the users file or the realtime engine.
1388 * \return The ast_vm_user structure for the user that was found.
1390 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1392 /* This function could be made to generate one from a database, too */
1393 struct ast_vm_user *vmu = NULL, *cur;
1394 AST_LIST_LOCK(&users);
1396 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1397 context = "default";
1399 AST_LIST_TRAVERSE(&users, cur, list) {
1401 if (cur->imapversion != imapversion) {
1405 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1407 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1411 /* Make a copy, so that on a reload, we have no race */
1412 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1413 memcpy(vmu, cur, sizeof(*vmu));
1414 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1415 AST_LIST_NEXT(vmu, list) = NULL;
1418 vmu = find_user_realtime(ivm, context, mailbox);
1419 AST_LIST_UNLOCK(&users);
1424 * \brief Resets a user password to a specified password.
1429 * This does the actual change password work, called by the vm_change_password() function.
1431 * \return zero on success, -1 on error.
1433 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1435 /* This function could be made to generate one from a database, too */
1436 struct ast_vm_user *cur;
1438 AST_LIST_LOCK(&users);
1439 AST_LIST_TRAVERSE(&users, cur, list) {
1440 if ((!context || !strcasecmp(context, cur->context)) &&
1441 (!strcasecmp(mailbox, cur->mailbox)))
1445 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1448 AST_LIST_UNLOCK(&users);
1453 * \brief The handler for the change password option.
1454 * \param vmu The voicemail user to work with.
1455 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1456 * 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.
1457 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1459 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1461 struct ast_config *cfg = NULL;
1462 struct ast_variable *var = NULL;
1463 struct ast_category *cat = NULL;
1464 char *category = NULL, *value = NULL, *new = NULL;
1465 const char *tmp = NULL;
1466 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1467 char secretfn[PATH_MAX] = "";
1470 if (!change_password_realtime(vmu, newpassword))
1473 /* check if we should store the secret in the spool directory next to the messages */
1474 switch (vmu->passwordlocation) {
1475 case OPT_PWLOC_SPOOLDIR:
1476 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1477 if (write_password_to_file(secretfn, newpassword) == 0) {
1478 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1479 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1480 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1483 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1486 case OPT_PWLOC_VOICEMAILCONF:
1487 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1488 while ((category = ast_category_browse(cfg, category))) {
1489 if (!strcasecmp(category, vmu->context)) {
1490 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1491 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1494 value = strstr(tmp, ",");
1496 new = alloca(strlen(newpassword)+1);
1497 sprintf(new, "%s", newpassword);
1499 new = alloca((strlen(value) + strlen(newpassword) + 1));
1500 sprintf(new, "%s%s", newpassword, value);
1502 if (!(cat = ast_category_get(cfg, category))) {
1503 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1506 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1510 /* save the results */
1512 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1513 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1514 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1519 case OPT_PWLOC_USERSCONF:
1520 /* check users.conf and update the password stored for the mailbox */
1521 /* if no vmsecret entry exists create one. */
1522 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1523 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1524 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1525 ast_debug(4, "users.conf: %s\n", category);
1526 if (!strcasecmp(category, vmu->mailbox)) {
1527 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1528 ast_debug(3, "looks like we need to make vmsecret!\n");
1529 var = ast_variable_new("vmsecret", newpassword, "");
1533 new = alloca(strlen(newpassword) + 1);
1534 sprintf(new, "%s", newpassword);
1535 if (!(cat = ast_category_get(cfg, category))) {
1536 ast_debug(4, "failed to get category!\n");
1541 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1543 ast_variable_append(cat, var);
1549 /* save the results and clean things up */
1551 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1552 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1553 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1559 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1562 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1563 if (!ast_safe_system(buf)) {
1564 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1565 /* Reset the password in memory, too */
1566 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1571 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1572 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1573 * \param len The length of the path string that was written out.
1578 * The path is constructed as
1579 * VM_SPOOL_DIRcontext/ext/folder
1581 * \return zero on success, -1 on error.
1583 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1585 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1589 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1590 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1591 * \param len The length of the path string that was written out.
1595 * The path is constructed as
1596 * VM_SPOOL_DIRcontext/ext/folder
1598 * \return zero on success, -1 on error.
1600 static int make_file(char *dest, const int len, const char *dir, const int num)
1602 return snprintf(dest, len, "%s/msg%04d", dir, num);
1605 /* same as mkstemp, but return a FILE * */
1606 static FILE *vm_mkftemp(char *template)
1609 int pfd = mkstemp(template);
1610 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1612 p = fdopen(pfd, "w+");
1621 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1622 * \param dest String. base directory.
1623 * \param len Length of dest.
1624 * \param context String. Ignored if is null or empty string.
1625 * \param ext String. Ignored if is null or empty string.
1626 * \param folder String. Ignored if is null or empty string.
1627 * \return -1 on failure, 0 on success.
1629 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1631 mode_t mode = VOICEMAIL_DIR_MODE;
1634 make_dir(dest, len, context, ext, folder);
1635 if ((res = ast_mkdir(dest, mode))) {
1636 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1642 static const char * const mailbox_folders[] = {
1661 static const char *mbox(struct ast_vm_user *vmu, int id)
1664 if (vmu && id == 0) {
1665 return vmu->imapfolder;
1668 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1671 static int get_folder_by_name(const char *name)
1675 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1676 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1684 static void free_user(struct ast_vm_user *vmu)
1686 if (ast_test_flag(vmu, VM_ALLOCED)) {
1687 if (vmu->emailbody != NULL) {
1688 ast_free(vmu->emailbody);
1689 vmu->emailbody = NULL;
1691 if (vmu->emailsubject != NULL) {
1692 ast_free(vmu->emailsubject);
1693 vmu->emailsubject = NULL;
1699 /* All IMAP-specific functions should go in this block. This
1700 * keeps them from being spread out all over the code */
1702 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1705 struct vm_state *vms;
1706 unsigned long messageNum;
1708 /* If greetings aren't stored in IMAP, just delete the file */
1709 if (msgnum < 0 && !imapgreetings) {
1710 ast_filedelete(file, NULL);
1714 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1715 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);
1719 /* find real message number based on msgnum */
1720 /* this may be an index into vms->msgArray based on the msgnum. */
1721 messageNum = vms->msgArray[msgnum];
1722 if (messageNum == 0) {
1723 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1726 if (option_debug > 2)
1727 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1728 /* delete message */
1729 snprintf (arg, sizeof(arg), "%lu", messageNum);
1730 ast_mutex_lock(&vms->lock);
1731 mail_setflag (vms->mailstream, arg, "\\DELETED");
1732 mail_expunge(vms->mailstream);
1733 ast_mutex_unlock(&vms->lock);
1736 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1738 struct vm_state *vms_p;
1739 char *file, *filename;
1744 /* This function is only used for retrieval of IMAP greetings
1745 * regular messages are not retrieved this way, nor are greetings
1746 * if they are stored locally*/
1747 if (msgnum > -1 || !imapgreetings) {
1750 file = strrchr(ast_strdupa(dir), '/');
1754 ast_debug (1, "Failed to procure file name from directory passed.\n");
1759 /* check if someone is accessing this box right now... */
1760 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
1761 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1762 /* Unlike when retrieving a message, it is reasonable not to be able to find a
1763 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1764 * that's all we need to do.
1766 if (!(vms_p = create_vm_state_from_user(vmu))) {
1767 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1772 /* Greetings will never have a prepended message */
1773 *vms_p->introfn = '\0';
1775 ast_mutex_lock(&vms_p->lock);
1776 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1777 if (!vms_p->mailstream) {
1778 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1779 ast_mutex_unlock(&vms_p->lock);
1783 /*XXX Yuck, this could probably be done a lot better */
1784 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1785 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1786 /* We have the body, now we extract the file name of the first attachment. */
1787 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1788 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1790 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1791 ast_mutex_unlock(&vms_p->lock);
1794 filename = strsep(&attachment, ".");
1795 if (!strcmp(filename, file)) {
1796 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1797 vms_p->msgArray[vms_p->curmsg] = i + 1;
1798 save_body(body, vms_p, "2", attachment, 0);
1799 ast_mutex_unlock(&vms_p->lock);
1803 ast_mutex_unlock(&vms_p->lock);
1808 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1811 char *header_content;
1812 char *attachedfilefmt;
1814 struct vm_state *vms;
1815 char text_file[PATH_MAX];
1816 FILE *text_file_ptr;
1818 struct ast_vm_user *vmu;
1820 if (!(vmu = find_user(NULL, context, mailbox))) {
1821 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1826 if (imapgreetings) {
1827 res = imap_retrieve_greeting(dir, msgnum, vmu);
1835 /* Before anything can happen, we need a vm_state so that we can
1836 * actually access the imap server through the vms->mailstream
1838 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1839 /* This should not happen. If it does, then I guess we'd
1840 * need to create the vm_state, extract which mailbox to
1841 * open, and then set up the msgArray so that the correct
1842 * IMAP message could be accessed. If I have seen correctly
1843 * though, the vms should be obtainable from the vmstates list
1844 * and should have its msgArray properly set up.
1846 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1851 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1852 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1854 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1855 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1860 if (option_debug > 2)
1861 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1862 if (vms->msgArray[msgnum] == 0) {
1863 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1868 /* This will only work for new messages... */
1869 ast_mutex_lock(&vms->lock);
1870 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1871 ast_mutex_unlock(&vms->lock);
1872 /* empty string means no valid header */
1873 if (ast_strlen_zero(header_content)) {
1874 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1879 ast_mutex_lock(&vms->lock);
1880 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1881 ast_mutex_unlock(&vms->lock);
1883 /* We have the body, now we extract the file name of the first attachment. */
1884 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1885 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1887 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1892 /* Find the format of the attached file */
1894 strsep(&attachedfilefmt, ".");
1895 if (!attachedfilefmt) {
1896 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1901 save_body(body, vms, "2", attachedfilefmt, 0);
1902 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1903 *vms->introfn = '\0';
1906 /* Get info from headers!! */
1907 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1909 if (!(text_file_ptr = fopen(text_file, "w"))) {
1910 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1913 fprintf(text_file_ptr, "%s\n", "[message]");
1915 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1916 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1917 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1918 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1919 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1920 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1921 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1922 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1923 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1924 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1925 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1926 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1927 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1928 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1929 fclose(text_file_ptr);
1936 static int folder_int(const char *folder)
1938 /*assume a NULL folder means INBOX*/
1942 if (!strcasecmp(folder, imapfolder)) {
1944 } else if (!strcasecmp(folder, "Old")) {
1946 } else if (!strcasecmp(folder, "Work")) {
1948 } else if (!strcasecmp(folder, "Family")) {
1950 } else if (!strcasecmp(folder, "Friends")) {
1952 } else if (!strcasecmp(folder, "Cust1")) {
1954 } else if (!strcasecmp(folder, "Cust2")) {
1956 } else if (!strcasecmp(folder, "Cust3")) {
1958 } else if (!strcasecmp(folder, "Cust4")) {
1960 } else if (!strcasecmp(folder, "Cust5")) {
1962 } else if (!strcasecmp(folder, "Urgent")) {
1964 } else { /*assume they meant INBOX if folder is not found otherwise*/
1969 static int __messagecount(const char *context, const char *mailbox, const char *folder)
1974 struct ast_vm_user *vmu, vmus;
1975 struct vm_state *vms_p;
1977 int fold = folder_int(folder);
1980 /* If URGENT, then look at INBOX */
1986 if (ast_strlen_zero(mailbox))
1989 /* We have to get the user before we can open the stream! */
1990 vmu = find_user(&vmus, context, mailbox);
1992 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1995 /* No IMAP account available */
1996 if (vmu->imapuser[0] == '\0') {
1997 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2002 /* No IMAP account available */
2003 if (vmu->imapuser[0] == '\0') {
2004 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2009 /* check if someone is accessing this box right now... */
2010 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2012 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
2015 ast_debug(3, "Returning before search - user is logged in\n");
2016 if (fold == 0) { /* INBOX */
2017 return urgent ? vms_p->urgentmessages : vms_p->newmessages;
2019 if (fold == 1) { /* Old messages */
2020 return vms_p->oldmessages;
2024 /* add one if not there... */
2025 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2027 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2031 vms_p = create_vm_state_from_user(vmu);
2033 ret = init_mailstream(vms_p, fold);
2034 if (!vms_p->mailstream) {
2035 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2039 ast_mutex_lock(&vms_p->lock);
2040 pgm = mail_newsearchpgm ();
2041 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2042 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2044 if (fold != OLD_FOLDER) {
2048 /* In the special case where fold is 1 (old messages) we have to do things a bit
2049 * differently. Old messages are stored in the INBOX but are marked as "seen"
2055 /* look for urgent messages */
2056 if (fold == NEW_FOLDER) {
2068 vms_p->vmArrayIndex = 0;
2069 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2070 if (fold == 0 && urgent == 0)
2071 vms_p->newmessages = vms_p->vmArrayIndex;
2073 vms_p->oldmessages = vms_p->vmArrayIndex;
2074 if (fold == 0 && urgent == 1)
2075 vms_p->urgentmessages = vms_p->vmArrayIndex;
2076 /*Freeing the searchpgm also frees the searchhdr*/
2077 mail_free_searchpgm(&pgm);
2078 ast_mutex_unlock(&vms_p->lock);
2080 return vms_p->vmArrayIndex;
2082 ast_mutex_lock(&vms_p->lock);
2083 mail_ping(vms_p->mailstream);
2084 ast_mutex_unlock(&vms_p->lock);
2089 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2091 /* Check if mailbox is full */
2092 check_quota(vms, vmu->imapfolder);
2093 if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2094 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2095 ast_play_and_wait(chan, "vm-mailboxfull");
2099 /* Check if we have exceeded maxmsg */
2100 if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, 0)) {
2101 ast_log(AST_LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u > %u)\n", msgnum, vmu->maxmsg);
2102 ast_play_and_wait(chan, "vm-mailboxfull");
2110 * \brief Gets the number of messages that exist in a mailbox folder.
2115 * This method is used when IMAP backend is used.
2116 * \return The number of messages in this mailbox folder (zero or more).
2118 static int messagecount(const char *context, const char *mailbox, const char *folder)
2120 if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2121 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2123 return __messagecount(context, mailbox, folder);
2127 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)
2129 char *myserveremail = serveremail;
2131 char introfn[PATH_MAX];
2135 char tmp[80] = "/tmp/astmail-XXXXXX";
2140 int ret; /* for better error checking */
2141 char *imap_flags = NIL;
2142 int msgcount = (messagecount(vmu->context, vmu->mailbox, "INBOX") + messagecount(vmu->context, vmu->mailbox, "Old"));
2144 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2145 if (msgnum < 0 && !imapgreetings) {
2149 if (imap_check_limits(chan, vms, vmu, msgcount)) {
2153 /* Set urgent flag for IMAP message */
2154 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2155 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2156 imap_flags = "\\FLAGGED";
2159 /* Attach only the first format */
2160 fmt = ast_strdupa(fmt);
2162 strsep(&stringp, "|");
2164 if (!ast_strlen_zero(vmu->serveremail))
2165 myserveremail = vmu->serveremail;
2168 make_file(fn, sizeof(fn), dir, msgnum);
2170 ast_copy_string (fn, dir, sizeof(fn));
2172 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2173 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2177 if (ast_strlen_zero(vmu->email)) {
2178 /* We need the vmu->email to be set when we call make_email_file, but
2179 * if we keep it set, a duplicate e-mail will be created. So at the end
2180 * of this function, we will revert back to an empty string if tempcopy
2183 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2187 if (!strcmp(fmt, "wav49"))
2189 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2191 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2193 if (!(p = vm_mkftemp(tmp))) {
2194 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2196 *(vmu->email) = '\0';
2200 if (msgnum < 0 && imapgreetings) {
2201 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2202 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2205 imap_delete_old_greeting(fn, vms);
2208 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
2209 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
2210 S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
2211 fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
2212 /* read mail file to memory */
2215 if (!(buf = ast_malloc(len + 1))) {
2216 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2219 *(vmu->email) = '\0';
2222 if (fread(buf, len, 1, p) < len) {
2224 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2228 ((char *) buf)[len] = '\0';
2229 INIT(&str, mail_string, buf, len);
2230 ret = init_mailstream(vms, NEW_FOLDER);
2232 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2233 ast_mutex_lock(&vms->lock);
2234 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2235 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2236 ast_mutex_unlock(&vms->lock);
2241 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2247 ast_debug(3, "%s stored\n", fn);
2250 *(vmu->email) = '\0';
2257 * \brief Gets the number of messages that exist in the inbox folder.
2258 * \param mailbox_context
2259 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2260 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2261 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2263 * This method is used when IMAP backend is used.
2264 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2266 * \return zero on success, -1 on error.
2269 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2271 char tmp[PATH_MAX] = "";
2283 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2284 /* If no mailbox, return immediately */
2285 if (ast_strlen_zero(mailbox_context))
2288 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2289 context = strchr(tmp, '@');
2290 if (strchr(mailbox_context, ',')) {
2291 int tmpnew, tmpold, tmpurgent;
2292 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2294 while ((cur = strsep(&mb, ", "))) {
2295 if (!ast_strlen_zero(cur)) {
2296 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2304 *urgentmsgs += tmpurgent;
2315 context = "default";
2316 mailboxnc = (char *) mailbox_context;
2320 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2322 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2325 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2330 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2335 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2343 * \brief Determines if the given folder has messages.
2344 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2345 * \param folder the folder to look in
2347 * This function is used when the mailbox is stored in an IMAP back end.
2348 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2349 * \return 1 if the folder has one or more messages. zero otherwise.
2352 static int has_voicemail(const char *mailbox, const char *folder)
2354 char tmp[256], *tmp2, *box, *context;
2355 ast_copy_string(tmp, mailbox, sizeof(tmp));
2357 if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2358 while ((box = strsep(&tmp2, ",&"))) {
2359 if (!ast_strlen_zero(box)) {
2360 if (has_voicemail(box, folder)) {
2366 if ((context = strchr(tmp, '@'))) {
2369 context = "default";
2371 return __messagecount(context, tmp, folder) ? 1 : 0;
2375 * \brief Copies a message from one mailbox to another.
2385 * This works with IMAP storage based mailboxes.
2387 * \return zero on success, -1 on error.
2389 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)
2391 struct vm_state *sendvms = NULL, *destvms = NULL;
2392 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2393 if (msgnum >= recip->maxmsg) {
2394 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2397 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2398 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2401 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2402 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2405 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2406 ast_mutex_lock(&sendvms->lock);
2407 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2408 ast_mutex_unlock(&sendvms->lock);
2411 ast_mutex_unlock(&sendvms->lock);
2412 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2416 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2418 char tmp[256], *t = tmp;
2419 size_t left = sizeof(tmp);
2421 if (box == OLD_FOLDER) {
2422 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2424 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2427 if (box == NEW_FOLDER) {
2428 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2430 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2433 /* Build up server information */
2434 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2436 /* Add authentication user if present */
2437 if (!ast_strlen_zero(authuser))
2438 ast_build_string(&t, &left, "/authuser=%s", authuser);
2440 /* Add flags if present */
2441 if (!ast_strlen_zero(imapflags))
2442 ast_build_string(&t, &left, "/%s", imapflags);
2444 /* End with username */
2446 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2448 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2450 if (box == NEW_FOLDER || box == OLD_FOLDER)
2451 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2452 else if (box == GREETINGS_FOLDER)
2453 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2454 else { /* Other folders such as Friends, Family, etc... */
2455 if (!ast_strlen_zero(imapparentfolder)) {
2456 /* imapparentfolder would typically be set to INBOX */
2457 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2459 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2464 static int init_mailstream(struct vm_state *vms, int box)
2466 MAILSTREAM *stream = NIL;
2471 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2474 if (option_debug > 2)
2475 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2476 if (vms->mailstream == NIL || !vms->mailstream) {
2478 ast_log(LOG_DEBUG, "mailstream not set.\n");
2480 stream = vms->mailstream;
2482 /* debug = T; user wants protocol telemetry? */
2483 debug = NIL; /* NO protocol telemetry? */
2485 if (delimiter == '\0') { /* did not probe the server yet */
2487 #ifdef USE_SYSTEM_IMAP
2488 #include <imap/linkage.c>
2489 #elif defined(USE_SYSTEM_CCLIENT)
2490 #include <c-client/linkage.c>
2492 #include "linkage.c"
2494 /* Connect to INBOX first to get folders delimiter */
2495 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2496 ast_mutex_lock(&vms->lock);
2497 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2498 ast_mutex_unlock(&vms->lock);
2499 if (stream == NIL) {
2500 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2503 get_mailbox_delimiter(stream);
2504 /* update delimiter in imapfolder */
2505 for (cp = vms->imapfolder; *cp; cp++)
2509 /* Now connect to the target folder */
2510 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2511 if (option_debug > 2)
2512 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2513 ast_mutex_lock(&vms->lock);
2514 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2515 ast_mutex_unlock(&vms->lock);
2516 if (vms->mailstream == NIL) {
2523 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2527 int ret, urgent = 0;
2529 /* If Urgent, then look at INBOX */
2535 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2536 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2537 vms->imapversion = vmu->imapversion;
2538 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2540 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2541 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2545 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2549 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2550 check_quota(vms, (char *) mbox(vmu, box));
2553 ast_mutex_lock(&vms->lock);
2554 pgm = mail_newsearchpgm();
2556 /* Check IMAP folder for Asterisk messages only... */
2557 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2558 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2563 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2564 if (box == NEW_FOLDER && urgent == 1) {
2569 } else if (box == NEW_FOLDER && urgent == 0) {
2574 } else if (box == OLD_FOLDER) {
2579 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2581 vms->vmArrayIndex = 0;
2582 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2583 vms->lastmsg = vms->vmArrayIndex - 1;
2584 mail_free_searchpgm(&pgm);
2586 ast_mutex_unlock(&vms->lock);
2590 static void write_file(char *filename, char *buffer, unsigned long len)
2594 output = fopen (filename, "w");
2595 if (fwrite(buffer, len, 1, output) != 1) {
2596 if (ferror(output)) {
2597 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2603 static void update_messages_by_imapuser(const char *user, unsigned long number)
2605 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2607 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2611 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2612 vms->msgArray[vms->vmArrayIndex++] = number;
2615 void mm_searched(MAILSTREAM *stream, unsigned long number)
2617 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2619 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2622 update_messages_by_imapuser(user, number);
2625 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2627 struct ast_variable *var;
2628 struct ast_vm_user *vmu;
2630 vmu = ast_calloc(1, sizeof *vmu);
2633 ast_set_flag(vmu, VM_ALLOCED);
2634 populate_defaults(vmu);
2636 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2638 apply_options_full(vmu, var);
2639 ast_variables_destroy(var);
2647 /* Interfaces to C-client */
2649 void mm_exists(MAILSTREAM * stream, unsigned long number)
2651 /* mail_ping will callback here if new mail! */
2652 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2653 if (number == 0) return;
2658 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2660 /* mail_ping will callback here if expunged mail! */
2661 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2662 if (number == 0) return;
2667 void mm_flags(MAILSTREAM * stream, unsigned long number)
2669 /* mail_ping will callback here if read mail! */
2670 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2671 if (number == 0) return;
2676 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2678 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2679 mm_log (string, errflg);
2683 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2685 if (delimiter == '\0') {
2689 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2690 if (attributes & LATT_NOINFERIORS)
2691 ast_debug(5, "no inferiors\n");
2692 if (attributes & LATT_NOSELECT)
2693 ast_debug(5, "no select\n");
2694 if (attributes & LATT_MARKED)
2695 ast_debug(5, "marked\n");
2696 if (attributes & LATT_UNMARKED)
2697 ast_debug(5, "unmarked\n");
2701 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2703 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2704 if (attributes & LATT_NOINFERIORS)
2705 ast_debug(5, "no inferiors\n");
2706 if (attributes & LATT_NOSELECT)
2707 ast_debug(5, "no select\n");
2708 if (attributes & LATT_MARKED)
2709 ast_debug(5, "marked\n");
2710 if (attributes & LATT_UNMARKED)
2711 ast_debug(5, "unmarked\n");
2715 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2717 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2718 if (status->flags & SA_MESSAGES)
2719 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2720 if (status->flags & SA_RECENT)
2721 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2722 if (status->flags & SA_UNSEEN)
2723 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2724 if (status->flags & SA_UIDVALIDITY)
2725 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2726 if (status->flags & SA_UIDNEXT)
2727 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2728 ast_log(AST_LOG_NOTICE, "\n");
2732 void mm_log(char *string, long errflg)
2734 switch ((short) errflg) {
2736 ast_debug(1, "IMAP Info: %s\n", string);
2740 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2743 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2749 void mm_dlog(char *string)
2751 ast_log(AST_LOG_NOTICE, "%s\n", string);
2755 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2757 struct ast_vm_user *vmu;
2759 ast_debug(4, "Entering callback mm_login\n");
2761 ast_copy_string(user, mb->user, MAILTMPLEN);
2763 /* We should only do this when necessary */
2764 if (!ast_strlen_zero(authpassword)) {
2765 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2767 AST_LIST_TRAVERSE(&users, vmu, list) {
2768 if (!strcasecmp(mb->user, vmu->imapuser)) {
2769 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2774 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2775 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2783 void mm_critical(MAILSTREAM * stream)
2788 void mm_nocritical(MAILSTREAM * stream)
2793 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2795 kill (getpid (), SIGSTOP);
2800 void mm_fatal(char *string)
2802 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2805 /* C-client callback to handle quota */
2806 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2808 struct vm_state *vms;
2809 char *mailbox = stream->mailbox, *user;
2810 char buf[1024] = "";
2811 unsigned long usage = 0, limit = 0;
2814 usage = pquota->usage;
2815 limit = pquota->limit;
2816 pquota = pquota->next;
2819 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)))) {
2820 ast_log(AST_LOG_ERROR, "No state found.\n");
2824 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2826 vms->quota_usage = usage;
2827 vms->quota_limit = limit;
2830 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2832 char *start, *eol_pnt;
2835 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2838 taglen = strlen(tag) + 1;
2842 if (!(start = strstr(header, tag)))
2845 /* Since we can be called multiple times we should clear our buffer */
2846 memset(buf, 0, len);
2848 ast_copy_string(buf, start+taglen, len);
2849 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2854 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2856 char *start, *quote, *eol_pnt;
2858 if (ast_strlen_zero(mailbox))
2861 if (!(start = strstr(mailbox, "/user=")))
2864 ast_copy_string(buf, start+6, len);
2866 if (!(quote = strchr(buf, '\"'))) {
2867 if (!(eol_pnt = strchr(buf, '/')))
2868 eol_pnt = strchr(buf,'}');
2872 eol_pnt = strchr(buf+1,'\"');
2878 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2880 struct vm_state *vms_p;
2882 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2883 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2886 if (option_debug > 4)
2887 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2888 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2890 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2891 ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
2892 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2893 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2894 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2895 vms_p->imapversion = vmu->imapversion;
2896 if (option_debug > 4)
2897 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2899 /* set mailbox to INBOX! */
2900 ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
2901 init_vm_state(vms_p);
2902 vmstate_insert(vms_p);
2906 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2908 struct vmstate *vlist = NULL;
2911 struct vm_state *vms;
2912 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2913 vms = pthread_getspecific(ts_vmstate.key);
2917 AST_LIST_LOCK(&vmstates);
2918 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2920 ast_debug(3, "error: vms is NULL for %s\n", user);
2923 if (vlist->vms->imapversion != imapversion) {
2926 if (!vlist->vms->imapuser) {
2927 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2931 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2932 AST_LIST_UNLOCK(&vmstates);
2936 AST_LIST_UNLOCK(&vmstates);
2938 ast_debug(3, "%s not found in vmstates\n", user);
2943 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2946 struct vmstate *vlist = NULL;
2947 const char *local_context = S_OR(context, "default");
2950 struct vm_state *vms;
2951 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2952 vms = pthread_getspecific(ts_vmstate.key);
2956 AST_LIST_LOCK(&vmstates);
2957 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2959 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2962 if (vlist->vms->imapversion != imapversion) {
2965 if (!vlist->vms->username || !vlist->vms->context) {
2966 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2970 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);
2972 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2973 ast_debug(3, "Found it!\n");
2974 AST_LIST_UNLOCK(&vmstates);
2978 AST_LIST_UNLOCK(&vmstates);
2980 ast_debug(3, "%s not found in vmstates\n", mailbox);
2985 static void vmstate_insert(struct vm_state *vms)
2988 struct vm_state *altvms;
2990 /* If interactive, it probably already exists, and we should
2991 use the one we already have since it is more up to date.
2992 We can compare the username to find the duplicate */
2993 if (vms->interactive == 1) {
2994 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2996 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2997 vms->newmessages = altvms->newmessages;
2998 vms->oldmessages = altvms->oldmessages;
2999 vms->vmArrayIndex = altvms->vmArrayIndex;
3000 vms->lastmsg = altvms->lastmsg;
3001 vms->curmsg = altvms->curmsg;
3002 /* get a pointer to the persistent store */
3003 vms->persist_vms = altvms;
3004 /* Reuse the mailstream? */
3005 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
3006 vms->mailstream = altvms->mailstream;
3008 vms->mailstream = NIL;
3014 if (!(v = ast_calloc(1, sizeof(*v))))
3019 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3021 AST_LIST_LOCK(&vmstates);
3022 AST_LIST_INSERT_TAIL(&vmstates, v, list);
3023 AST_LIST_UNLOCK(&vmstates);
3026 static void vmstate_delete(struct vm_state *vms)
3028 struct vmstate *vc = NULL;
3029 struct vm_state *altvms = NULL;
3031 /* If interactive, we should copy pertinent info
3032 back to the persistent state (to make update immediate) */
3033 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
3034 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3035 altvms->newmessages = vms->newmessages;
3036 altvms->oldmessages = vms->oldmessages;
3037 altvms->updated = 1;
3038 vms->mailstream = mail_close(vms->mailstream);
3040 /* Interactive states are not stored within the persistent list */
3044 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3046 AST_LIST_LOCK(&vmstates);
3047 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
3048 if (vc->vms == vms) {
3049 AST_LIST_REMOVE_CURRENT(list);
3053 AST_LIST_TRAVERSE_SAFE_END
3054 AST_LIST_UNLOCK(&vmstates);
3057 ast_mutex_destroy(&vc->vms->lock);
3061 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3064 static void set_update(MAILSTREAM * stream)
3066 struct vm_state *vms;
3067 char *mailbox = stream->mailbox, *user;
3068 char buf[1024] = "";
3070 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
3071 if (user && option_debug > 2)
3072 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
3076 ast_debug(3, "User %s mailbox set for update.\n", user);
3078 vms->updated = 1; /* Set updated flag since mailbox changed */
3081 static void init_vm_state(struct vm_state *vms)
3084 vms->vmArrayIndex = 0;
3085 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
3086 vms->msgArray[x] = 0;
3088 ast_mutex_init(&vms->lock);
3091 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
3095 char *fn = is_intro ? vms->introfn : vms->fn;
3097 unsigned long newlen;
3100 if (!body || body == NIL)
3103 ast_mutex_lock(&vms->lock);
3104 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
3105 ast_mutex_unlock(&vms->lock);
3106 if (body_content != NIL) {
3107 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
3108 /* ast_debug(1,body_content); */
3109 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
3110 /* If the body of the file is empty, return an error */
3114 write_file(filename, (char *) body_decoded, newlen);
3116 ast_debug(5, "Body of message is NULL.\n");
3123 * \brief Get delimiter via mm_list callback
3126 * Determines the delimiter character that is used by the underlying IMAP based mail store.
3128 /* MUTEX should already be held */
3129 static void get_mailbox_delimiter(MAILSTREAM *stream) {
3131 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
3132 mail_list(stream, tmp, "*");
3136 * \brief Check Quota for user
3137 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3138 * \param mailbox the mailbox to check the quota for.
3140 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3142 static void check_quota(struct vm_state *vms, char *mailbox) {
3143 ast_mutex_lock(&vms->lock);
3144 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3145 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3146 if (vms && vms->mailstream != NULL) {
3147 imap_getquotaroot(vms->mailstream, mailbox);
3149 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3151 ast_mutex_unlock(&vms->lock);
3154 #endif /* IMAP_STORAGE */
3156 /*! \brief Lock file path
3157 only return failure if ast_lock_path returns 'timeout',
3158 not if the path does not exist or any other reason
3160 static int vm_lock_path(const char *path)
3162 switch (ast_lock_path(path)) {
3163 case AST_LOCK_TIMEOUT:
3172 struct generic_prepare_struct {
3178 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
3180 struct generic_prepare_struct *gps = data;
3184 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3185 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3186 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3189 res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
3190 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3191 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
3192 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3195 for (i = 0; i < gps->argc; i++)
3196 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
3202 * \brief Retrieves a file from an ODBC data store.
3203 * \param dir the path to the file to be retreived.
3204 * \param msgnum the message number, such as within a mailbox folder.
3206 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
3207 * 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.
3209 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
3210 * The output is the message information file with the name msgnum and the extension .txt
3211 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
3213 * \return 0 on success, -1 on error.
3215 static int retrieve_file(char *dir, int msgnum)
3221 void *fdm = MAP_FAILED;
3222 SQLSMALLINT colcount = 0;
3229 SQLSMALLINT datatype;
3230 SQLSMALLINT decimaldigits;
3231 SQLSMALLINT nullable;
3237 char full_fn[PATH_MAX];
3239 char *argv[] = { dir, msgnums };
3240 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3242 struct odbc_obj *obj;
3243 obj = ast_odbc_request_obj(odbc_database, 0);
3245 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3246 c = strchr(fmt, '|');
3249 if (!strcasecmp(fmt, "wav49"))
3251 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3253 make_file(fn, sizeof(fn), dir, msgnum);
3255 ast_copy_string(fn, dir, sizeof(fn));
3257 /* Create the information file */
3258 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3260 if (!(f = fopen(full_fn, "w+"))) {
3261 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
3265 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3266 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3267 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3269 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3270 ast_odbc_release_obj(obj);
3273 res = SQLFetch(stmt);
3274 if (res == SQL_NO_DATA) {
3275 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3276 ast_odbc_release_obj(obj);
3278 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3279 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3280 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3281 ast_odbc_release_obj(obj);
3284 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3286 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3287 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3288 ast_odbc_release_obj(obj);
3291 res = SQLNumResultCols(stmt, &colcount);
3292 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3293 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3294 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3295 ast_odbc_release_obj(obj);
3299 fprintf(f, "[message]\n");
3300 for (x = 0; x < colcount; x++) {
3302 collen = sizeof(coltitle);
3303 res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
3304 &datatype, &colsize, &decimaldigits, &nullable);
3305 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3306 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3307 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3308 ast_odbc_release_obj(obj);
3311 if (!strcasecmp(coltitle, "recording")) {
3313 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3317 lseek(fd, fdlen - 1, SEEK_SET);
3318 if (write(fd, tmp, 1) != 1) {
3323 /* Read out in small chunks */
3324 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3325 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3326 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3327 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3328 ast_odbc_release_obj(obj);
3331 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3332 munmap(fdm, CHUNKSIZE);
3333 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3334 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3336 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3337 ast_odbc_release_obj(obj);
3342 if (truncate(full_fn, fdlen) < 0) {
3343 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3347 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3348 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3349 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3350 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3351 ast_odbc_release_obj(obj);
3354 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3355 fprintf(f, "%s=%s\n", coltitle, rowdata);
3358 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3359 ast_odbc_release_obj(obj);
3361 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3371 * \brief Determines the highest message number in use for a given user and mailbox folder.
3373 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3375 * This method is used when mailboxes are stored in an ODBC back end.
3376 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3378 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3380 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3387 char *argv[] = { dir };
3388 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3390 struct odbc_obj *obj;
3391 obj = ast_odbc_request_obj(odbc_database, 0);
3393 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3394 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3396 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3397 ast_odbc_release_obj(obj);
3400 res = SQLFetch(stmt);
3401 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3402 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3403 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3404 ast_odbc_release_obj(obj);
3407 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3408 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3409 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3410 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3411 ast_odbc_release_obj(obj);
3414 if (sscanf(rowdata, "%30d", &x) != 1)
3415 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3416 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3417 ast_odbc_release_obj(obj);
3419 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3425 * \brief Determines if the specified message exists.
3426 * \param dir the folder the mailbox folder to look for messages.
3427 * \param msgnum the message index to query for.
3429 * This method is used when mailboxes are stored in an ODBC back end.
3431 * \return greater than zero if the message exists, zero when the message does not exist or on error.
3433 static int message_exists(char *dir, int msgnum)
3441 char *argv[] = { dir, msgnums };
3442 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3444 struct odbc_obj *obj;
3445 obj = ast_odbc_request_obj(odbc_database, 0);
3447 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3448 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3449 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3451 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3452 ast_odbc_release_obj(obj);
3455 res = SQLFetch(stmt);
3456 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3457 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3458 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3459 ast_odbc_release_obj(obj);
3462 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3463 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3464 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3465 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3466 ast_odbc_release_obj(obj);
3469 if (sscanf(rowdata, "%30d", &x) != 1)
3470 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3471 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3472 ast_odbc_release_obj(obj);
3474 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3480 * \brief returns the one-based count for messages.
3482 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3484 * This method is used when mailboxes are stored in an ODBC back end.
3485 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3486 * one-based messages.
3487 * This method just calls last_message_index and returns +1 of its value.
3489 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3491 static int count_messages(struct ast_vm_user *vmu, char *dir)
3493 return last_message_index(vmu, dir) + 1;
3497 * \brief Deletes a message from the mailbox folder.
3498 * \param sdir The mailbox folder to work in.
3499 * \param smsg The message index to be deleted.
3501 * This method is used when mailboxes are stored in an ODBC back end.
3502 * The specified message is directly deleted from the database 'voicemessages' table.
3504 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3506 static void delete_file(const char *sdir, int smsg)
3511 char *argv[] = { NULL, msgnums };
3512 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3513 struct odbc_obj *obj;
3515 argv[0] = ast_strdupa(sdir);
3517 obj = ast_odbc_request_obj(odbc_database, 0);
3519 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3520 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3521 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3523 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3525 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3526 ast_odbc_release_obj(obj);
3528 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3533 * \brief Copies a voicemail from one mailbox to another.
3534 * \param sdir the folder for which to look for the message to be copied.
3535 * \param smsg the index of the message to be copied.
3536 * \param ddir the destination folder to copy the message into.
3537 * \param dmsg the index to be used for the copied message.
3538 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3539 * \param dmailboxcontext The context for the destination user.
3541 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3543 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3549 struct odbc_obj *obj;
3550 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3551 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3553 delete_file(ddir, dmsg);
3554 obj = ast_odbc_request_obj(odbc_database, 0);
3556 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3557 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3558 snprintf(sql, sizeof(sql), &quo