2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \author Mark Spencer <markster@digium.com>
22 * \brief Comedian Mail - Voicemail System
24 * \extref unixODBC (http://www.unixodbc.org/)
25 * \extref A source distribution of University of Washington's IMAP c-client
26 * (http://www.washington.edu/imap/)
30 * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
31 * \ingroup applications
32 * \note This module requires res_adsi to load. This needs to be optional
35 * \note This file is now almost impossible to work with, due to all \#ifdefs.
36 * Feels like the database code before realtime. Someone - please come up
37 * with a plan to clean this up.
41 <depend>res_smdi</depend>
45 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_voicemail.so apps/app_directory.o apps/app_directory.so">
46 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
47 <conflict>ODBC_STORAGE</conflict>
48 <conflict>IMAP_STORAGE</conflict>
49 <defaultenabled>yes</defaultenabled>
51 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
52 <depend>generic_odbc</depend>
54 <conflict>IMAP_STORAGE</conflict>
55 <conflict>FILE_STORAGE</conflict>
56 <defaultenabled>no</defaultenabled>
58 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
59 <depend>imap_tk</depend>
60 <conflict>ODBC_STORAGE</conflict>
61 <conflict>FILE_STORAGE</conflict>
63 <defaultenabled>no</defaultenabled>
74 #ifdef USE_SYSTEM_IMAP
75 #include <imap/c-client.h>
76 #include <imap/imap4r1.h>
77 #include <imap/linkage.h>
78 #elif defined (USE_SYSTEM_CCLIENT)
79 #include <c-client/c-client.h>
80 #include <c-client/imap4r1.h>
81 #include <c-client/linkage.h>
89 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
91 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
98 #include "asterisk/logger.h"
99 #include "asterisk/lock.h"
100 #include "asterisk/file.h"
101 #include "asterisk/channel.h"
102 #include "asterisk/pbx.h"
103 #include "asterisk/config.h"
104 #include "asterisk/say.h"
105 #include "asterisk/module.h"
106 #include "asterisk/adsi.h"
107 #include "asterisk/app.h"
108 #include "asterisk/manager.h"
109 #include "asterisk/dsp.h"
110 #include "asterisk/localtime.h"
111 #include "asterisk/cli.h"
112 #include "asterisk/utils.h"
113 #include "asterisk/stringfields.h"
114 #include "asterisk/smdi.h"
115 #include "asterisk/event.h"
116 #include "asterisk/taskprocessor.h"
119 #include "asterisk/res_odbc.h"
123 #include "asterisk/threadstorage.h"
127 <application name="VoiceMail" language="en_US">
129 Leave a Voicemail message.
132 <parameter name="mailboxs" argsep="&" required="true">
133 <argument name="mailbox1" argsep="@" required="true">
134 <argument name="mailbox" required="true" />
135 <argument name="context" />
137 <argument name="mailbox2" argsep="@" multiple="true">
138 <argument name="mailbox" required="true" />
139 <argument name="context" />
142 <parameter name="options">
145 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
148 <argument name="c" />
149 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
150 if played during the greeting. Context defaults to the current context.</para>
153 <argument name="#" required="true" />
154 <para>Use the specified amount of gain when recording the voicemail
155 message. The units are whole-number decibels (dB). Only works on supported
156 technologies, which is DAHDI only.</para>
159 <para>Skip the playback of instructions for leaving a message to the
160 calling party.</para>
163 <para>Play the <literal>unavailable</literal> greeting.</para>
166 <para>Mark message as <literal>URGENT</literal>.</para>
169 <para>Mark message as <literal>PRIORITY</literal>.</para>
175 <para>This application allows the calling party to leave a message for the specified
176 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
177 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
179 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
182 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
185 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
188 <para>This application will set the following channel variable upon completion:</para>
190 <variable name="VMSTATUS">
191 <para>This indicates the status of the execution of the VoiceMail application.</para>
192 <value name="SUCCESS" />
193 <value name="USEREXIT" />
194 <value name="FAILED" />
199 <application name="VoiceMailMain" language="en_US">
201 Check Voicemail messages.
204 <parameter name="mailbox" required="true" argsep="@">
205 <argument name="mailbox" />
206 <argument name="context" />
208 <parameter name="options">
211 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
212 the mailbox that is entered by the caller.</para>
215 <argument name="#" required="true" />
216 <para>Use the specified amount of gain when recording a voicemail message.
217 The units are whole-number decibels (dB).</para>
220 <para>Skip checking the passcode for the mailbox.</para>
223 <argument name="folder" required="true" />
224 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
225 Defaults to <literal>INBOX</literal>.</para>
231 <para>This application allows the calling party to check voicemail messages. A specific
232 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
233 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
234 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
235 <literal>default</literal> context will be used.</para>
238 <application name="MailboxExists" language="en_US">
240 Check to see if Voicemail mailbox exists.
243 <parameter name="mailbox" required="true" argsep="@">
244 <argument name="mailbox" required="true" />
245 <argument name="context" />
247 <parameter name="options">
248 <para>None options.</para>
252 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
253 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
255 <para>This application will set the following channel variable upon completion:</para>
257 <variable name="VMBOXEXISTSSTATUS">
258 <para>This will contain the status of the execution of the MailboxExists application.
259 Possible values include:</para>
260 <value name="SUCCESS" />
261 <value name="FAILED" />
266 <application name="VMAuthenticate" language="en_US">
268 Authenticate with Voicemail passwords.
271 <parameter name="mailbox" required="true" argsep="@">
272 <argument name="mailbox" />
273 <argument name="context" />
275 <parameter name="options">
278 <para>Skip playing the initial prompts.</para>
284 <para>This application behaves the same way as the Authenticate application, but the passwords
285 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
286 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
287 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
291 <function name="MAILBOX_EXISTS" language="en_US">
293 Tell if a mailbox is configured.
296 <parameter name="mailbox" required="true" />
297 <parameter name="context" />
300 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
301 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
305 <manager name="VoicemailUsersList" language="en_US">
307 List All Voicemail User Information.
310 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
318 static char imapserver[48];
319 static char imapport[8];
320 static char imapflags[128];
321 static char imapfolder[64];
322 static char imapparentfolder[64] = "\0";
323 static char greetingfolder[64];
324 static char authuser[32];
325 static char authpassword[42];
326 static int imapversion = 1;
328 static int expungeonhangup = 1;
329 static int imapgreetings = 0;
330 static char delimiter = '\0';
335 AST_THREADSTORAGE(ts_vmstate);
337 /* Forward declarations for IMAP */
338 static int init_mailstream(struct vm_state *vms, int box);
339 static void write_file(char *filename, char *buffer, unsigned long len);
340 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
341 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
342 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
343 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
344 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
345 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
346 static void vmstate_insert(struct vm_state *vms);
347 static void vmstate_delete(struct vm_state *vms);
348 static void set_update(MAILSTREAM * stream);
349 static void init_vm_state(struct vm_state *vms);
350 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
351 static void get_mailbox_delimiter(MAILSTREAM *stream);
352 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
353 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
354 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
355 static void update_messages_by_imapuser(const char *user, unsigned long number);
356 static int vm_delete(char *file);
358 static int imap_remove_file (char *dir, int msgnum);
359 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
360 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
361 static void check_quota(struct vm_state *vms, char *mailbox);
362 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
364 struct vm_state *vms;
365 AST_LIST_ENTRY(vmstate) list;
368 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
372 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
374 #define COMMAND_TIMEOUT 5000
375 /* Don't modify these here; set your umask at runtime instead */
376 #define VOICEMAIL_DIR_MODE 0777
377 #define VOICEMAIL_FILE_MODE 0666
378 #define CHUNKSIZE 65536
380 #define VOICEMAIL_CONFIG "voicemail.conf"
381 #define ASTERISK_USERNAME "asterisk"
383 /* Define fast-forward, pause, restart, and reverse keys
384 while listening to a voicemail message - these are
385 strings, not characters */
386 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
387 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
388 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
389 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
390 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
391 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
393 /* Default mail command to mail voicemail. Change it with the
394 mailcmd= command in voicemail.conf */
395 #define SENDMAIL "/usr/sbin/sendmail -t"
397 #define INTRO "vm-intro"
400 #define MAXMSGLIMIT 9999
402 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
404 #define BASELINELEN 72
405 #define BASEMAXINLINE 256
408 #define MAX_DATETIME_FORMAT 512
409 #define MAX_NUM_CID_CONTEXTS 10
411 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
412 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
413 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
414 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
415 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
416 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
417 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
418 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
419 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
420 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
421 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
422 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
423 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
424 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
425 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
426 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
427 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
428 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
429 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
430 #define ERROR_LOCK_PATH -100
442 enum vm_option_flags {
443 OPT_SILENT = (1 << 0),
444 OPT_BUSY_GREETING = (1 << 1),
445 OPT_UNAVAIL_GREETING = (1 << 2),
446 OPT_RECORDGAIN = (1 << 3),
447 OPT_PREPEND_MAILBOX = (1 << 4),
448 OPT_AUTOPLAY = (1 << 6),
449 OPT_DTMFEXIT = (1 << 7),
450 OPT_MESSAGE_Urgent = (1 << 8),
451 OPT_MESSAGE_PRIORITY = (1 << 9)
454 enum vm_option_args {
455 OPT_ARG_RECORDGAIN = 0,
456 OPT_ARG_PLAYFOLDER = 1,
457 OPT_ARG_DTMFEXIT = 2,
458 /* This *must* be the last value in this enum! */
459 OPT_ARG_ARRAY_SIZE = 3,
462 AST_APP_OPTIONS(vm_app_options, {
463 AST_APP_OPTION('s', OPT_SILENT),
464 AST_APP_OPTION('b', OPT_BUSY_GREETING),
465 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
466 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
467 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
468 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
469 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
470 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
471 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
474 static int load_config(int reload);
476 /*! \page vmlang Voicemail Language Syntaxes Supported
478 \par Syntaxes supported, not really language codes.
485 \arg \b pt - Portuguese
486 \arg \b pt_BR - Portuguese (Brazil)
488 \arg \b no - Norwegian
490 \arg \b tw - Chinese (Taiwan)
491 \arg \b ua - Ukrainian
493 German requires the following additional soundfile:
494 \arg \b 1F einE (feminine)
496 Spanish requires the following additional soundfile:
497 \arg \b 1M un (masculine)
499 Dutch, Portuguese & Spanish require the following additional soundfiles:
500 \arg \b vm-INBOXs singular of 'new'
501 \arg \b vm-Olds singular of 'old/heard/read'
504 \arg \b vm-INBOX nieuwe (nl)
505 \arg \b vm-Old oude (nl)
508 \arg \b vm-new-a 'new', feminine singular accusative
509 \arg \b vm-new-e 'new', feminine plural accusative
510 \arg \b vm-new-ych 'new', feminine plural genitive
511 \arg \b vm-old-a 'old', feminine singular accusative
512 \arg \b vm-old-e 'old', feminine plural accusative
513 \arg \b vm-old-ych 'old', feminine plural genitive
514 \arg \b digits/1-a 'one', not always same as 'digits/1'
515 \arg \b digits/2-ie 'two', not always same as 'digits/2'
518 \arg \b vm-nytt singular of 'new'
519 \arg \b vm-nya plural of 'new'
520 \arg \b vm-gammalt singular of 'old'
521 \arg \b vm-gamla plural of 'old'
522 \arg \b digits/ett 'one', not always same as 'digits/1'
525 \arg \b vm-ny singular of 'new'
526 \arg \b vm-nye plural of 'new'
527 \arg \b vm-gammel singular of 'old'
528 \arg \b vm-gamle plural of 'old'
536 Italian requires the following additional soundfile:
540 \arg \b vm-nuovi new plural
541 \arg \b vm-vecchio old
542 \arg \b vm-vecchi old plural
544 Chinese (Taiwan) requires the following additional soundfile:
545 \arg \b vm-tong A class-word for call (tong1)
546 \arg \b vm-ri A class-word for day (ri4)
547 \arg \b vm-you You (ni3)
548 \arg \b vm-haveno Have no (mei2 you3)
549 \arg \b vm-have Have (you3)
550 \arg \b vm-listen To listen (yao4 ting1)
553 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
554 spelled among others when you have to change folder. For the above reasons, vm-INBOX
555 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
564 unsigned char iobuf[BASEMAXINLINE];
567 /*! Structure for linked list of users
568 * Use ast_vm_user_destroy() to free one of these structures. */
570 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
571 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
572 char password[80]; /*!< Secret pin code, numbers only */
573 char fullname[80]; /*!< Full name, for directory app */
574 char email[80]; /*!< E-mail address */
575 char *emailsubject; /*!< E-mail subject */
576 char *emailbody; /*!< E-mail body */
577 char pager[80]; /*!< E-mail address to pager (no attachment) */
578 char serveremail[80]; /*!< From: Mail address */
579 char mailcmd[160]; /*!< Configurable mail command */
580 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
581 char zonetag[80]; /*!< Time zone */
584 char uniqueid[80]; /*!< Unique integer identifier */
586 char attachfmt[20]; /*!< Attachment format */
587 unsigned int flags; /*!< VM_ flags */
589 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
590 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
591 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
593 char imapuser[80]; /*!< IMAP server login */
594 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
595 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
596 int imapversion; /*!< If configuration changes, use the new values */
598 double volgain; /*!< Volume gain for voicemails sent via email */
599 AST_LIST_ENTRY(ast_vm_user) list;
602 /*! Voicemail time zones */
604 AST_LIST_ENTRY(vm_zone) list;
607 char msg_format[512];
610 #define VMSTATE_MAX_MSG_ARRAY 256
612 /*! Voicemail mailbox state */
617 char curdir[PATH_MAX];
618 char vmbox[PATH_MAX];
620 char intro[PATH_MAX];
632 int updated; /*!< decremented on each mail check until 1 -allows delay */
633 long msgArray[VMSTATE_MAX_MSG_ARRAY];
634 MAILSTREAM *mailstream;
636 char imapuser[80]; /*!< IMAP server login */
639 char introfn[PATH_MAX]; /*!< Name of prepended file */
640 unsigned int quota_limit;
641 unsigned int quota_usage;
642 struct vm_state *persist_vms;
647 static char odbc_database[80];
648 static char odbc_table[80];
649 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
650 #define DISPOSE(a,b) remove_file(a,b)
651 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
652 #define EXISTS(a,b,c,d) (message_exists(a,b))
653 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
654 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
655 #define DELETE(a,b,c,d) (delete_file(a,b))
658 #define DISPOSE(a,b) (imap_remove_file(a,b))
659 #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))
660 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
661 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
662 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
663 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
664 #define DELETE(a,b,c,d) (vm_imap_delete(b,d))
666 #define RETRIEVE(a,b,c,d)
668 #define STORE(a,b,c,d,e,f,g,h,i,j)
669 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
670 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
671 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
672 #define DELETE(a,b,c,d) (vm_delete(c))
676 static char VM_SPOOL_DIR[PATH_MAX];
678 static char ext_pass_cmd[128];
679 static char ext_pass_check_cmd[128];
683 #define PWDCHANGE_INTERNAL (1 << 1)
684 #define PWDCHANGE_EXTERNAL (1 << 2)
685 static int pwdchange = PWDCHANGE_INTERNAL;
688 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
691 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
693 # define tdesc "Comedian Mail (Voicemail System)"
697 static char userscontext[AST_MAX_EXTENSION] = "default";
699 static char *addesc = "Comedian Mail";
701 /* Leave a message */
702 static char *app = "VoiceMail";
704 /* Check mail, control, etc */
705 static char *app2 = "VoiceMailMain";
707 static char *app3 = "MailboxExists";
708 static char *app4 = "VMAuthenticate";
710 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
711 static AST_LIST_HEAD_STATIC(zones, vm_zone);
712 static char zonetag[80];
713 static int maxsilence;
715 static int maxdeletedmsg;
716 static int silencethreshold = 128;
717 static char serveremail[80];
718 static char mailcmd[160]; /* Configurable mail cmd */
719 static char externnotify[160];
720 static struct ast_smdi_interface *smdi_iface = NULL;
721 static char vmfmts[80];
722 static double volgain;
723 static int vmminsecs;
724 static int vmmaxsecs;
727 static int maxlogins;
728 static int minpassword;
730 /*! Poll mailboxes for changes since there is something external to
731 * app_voicemail that may change them. */
732 static unsigned int poll_mailboxes;
734 /*! Polling frequency */
735 static unsigned int poll_freq;
736 /*! By default, poll every 30 seconds */
737 #define DEFAULT_POLL_FREQ 30
739 AST_MUTEX_DEFINE_STATIC(poll_lock);
740 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
741 static pthread_t poll_thread = AST_PTHREADT_NULL;
742 static unsigned char poll_thread_run;
744 /*! Subscription to ... MWI event subscriptions */
745 static struct ast_event_sub *mwi_sub_sub;
746 /*! Subscription to ... MWI event un-subscriptions */
747 static struct ast_event_sub *mwi_unsub_sub;
750 * \brief An MWI subscription
752 * This is so we can keep track of which mailboxes are subscribed to.
753 * This way, we know which mailboxes to poll when the pollmailboxes
754 * option is being used.
757 AST_RWLIST_ENTRY(mwi_sub) entry;
765 struct mwi_sub_task {
771 static struct ast_taskprocessor *mwi_subscription_tps;
773 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
775 /* custom audio control prompts for voicemail playback */
776 static char listen_control_forward_key[12];
777 static char listen_control_reverse_key[12];
778 static char listen_control_pause_key[12];
779 static char listen_control_restart_key[12];
780 static char listen_control_stop_key[12];
782 /* custom password sounds */
783 static char vm_password[80] = "vm-password";
784 static char vm_newpassword[80] = "vm-newpassword";
785 static char vm_passchanged[80] = "vm-passchanged";
786 static char vm_reenterpassword[80] = "vm-reenterpassword";
787 static char vm_mismatch[80] = "vm-mismatch";
788 static char vm_invalid_password[80] = "vm-invalid-password";
789 static char vm_pls_try_again[80] = "vm-pls-try-again";
791 static struct ast_flags globalflags = {0};
793 static int saydurationminfo;
795 static char dialcontext[AST_MAX_CONTEXT] = "";
796 static char callcontext[AST_MAX_CONTEXT] = "";
797 static char exitcontext[AST_MAX_CONTEXT] = "";
799 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
802 static char *emailbody = NULL;
803 static char *emailsubject = NULL;
804 static char *pagerbody = NULL;
805 static char *pagersubject = NULL;
806 static char fromstring[100];
807 static char pagerfromstring[100];
808 static char charset[32] = "ISO-8859-1";
810 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
811 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
812 static int adsiver = 1;
813 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
815 /* Forward declarations - generic */
816 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
817 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);
818 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
819 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
820 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
821 signed char record_gain, struct vm_state *vms, char *flag);
822 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
823 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
824 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);
825 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);
826 static void apply_options(struct ast_vm_user *vmu, const char *options);
827 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);
828 static int is_valid_dtmf(const char *key);
830 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
831 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
834 static char *strip_control(const char *input, char *buf, size_t buflen)
837 for (; *input; input++) {
842 if (bufptr == buf + buflen - 1) {
852 * \brief Sets default voicemail system options to a voicemail user.
854 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
855 * - all the globalflags
856 * - the saydurationminfo
860 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
863 static void populate_defaults(struct ast_vm_user *vmu)
865 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
866 if (saydurationminfo)
867 vmu->saydurationm = saydurationminfo;
868 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
869 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
870 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
871 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
873 vmu->maxsecs = vmmaxsecs;
875 vmu->maxmsg = maxmsg;
877 vmu->maxdeletedmsg = maxdeletedmsg;
878 vmu->volgain = volgain;
879 vmu->emailsubject = NULL;
880 vmu->emailbody = NULL;
884 * \brief Sets a a specific property value.
885 * \param vmu The voicemail user object to work with.
886 * \param var The name of the property to be set.
887 * \param value The value to be set to the property.
889 * The property name must be one of the understood properties. See the source for details.
891 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
894 if (!strcasecmp(var, "attach")) {
895 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
896 } else if (!strcasecmp(var, "attachfmt")) {
897 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
898 } else if (!strcasecmp(var, "serveremail")) {
899 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
900 } else if (!strcasecmp(var, "language")) {
901 ast_copy_string(vmu->language, value, sizeof(vmu->language));
902 } else if (!strcasecmp(var, "tz")) {
903 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
905 } else if (!strcasecmp(var, "imapuser")) {
906 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
907 vmu->imapversion = imapversion;
908 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
909 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
910 vmu->imapversion = imapversion;
911 } else if (!strcasecmp(var, "imapvmshareid")) {
912 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
913 vmu->imapversion = imapversion;
915 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
916 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
917 } else if (!strcasecmp(var, "saycid")){
918 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
919 } else if (!strcasecmp(var, "sendvoicemail")){
920 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
921 } else if (!strcasecmp(var, "review")){
922 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
923 } else if (!strcasecmp(var, "tempgreetwarn")){
924 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
925 } else if (!strcasecmp(var, "messagewrap")){
926 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
927 } else if (!strcasecmp(var, "operator")) {
928 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
929 } else if (!strcasecmp(var, "envelope")){
930 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
931 } else if (!strcasecmp(var, "moveheard")){
932 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
933 } else if (!strcasecmp(var, "sayduration")){
934 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
935 } else if (!strcasecmp(var, "saydurationm")){
936 if (sscanf(value, "%30d", &x) == 1) {
937 vmu->saydurationm = x;
939 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
941 } else if (!strcasecmp(var, "forcename")){
942 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
943 } else if (!strcasecmp(var, "forcegreetings")){
944 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
945 } else if (!strcasecmp(var, "callback")) {
946 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
947 } else if (!strcasecmp(var, "dialout")) {
948 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
949 } else if (!strcasecmp(var, "exitcontext")) {
950 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
951 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
952 vmu->maxsecs = atoi(value);
953 if (vmu->maxsecs <= 0) {
954 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
955 vmu->maxsecs = vmmaxsecs;
957 vmu->maxsecs = atoi(value);
959 if (!strcasecmp(var, "maxmessage"))
960 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
961 } else if (!strcasecmp(var, "maxmsg")) {
962 vmu->maxmsg = atoi(value);
963 if (vmu->maxmsg <= 0) {
964 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
965 vmu->maxmsg = MAXMSG;
966 } else if (vmu->maxmsg > MAXMSGLIMIT) {
967 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
968 vmu->maxmsg = MAXMSGLIMIT;
970 } else if (!strcasecmp(var, "backupdeleted")) {
971 if (sscanf(value, "%30d", &x) == 1)
972 vmu->maxdeletedmsg = x;
973 else if (ast_true(value))
974 vmu->maxdeletedmsg = MAXMSG;
976 vmu->maxdeletedmsg = 0;
978 if (vmu->maxdeletedmsg < 0) {
979 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
980 vmu->maxdeletedmsg = MAXMSG;
981 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
982 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
983 vmu->maxdeletedmsg = MAXMSGLIMIT;
985 } else if (!strcasecmp(var, "volgain")) {
986 sscanf(value, "%30lf", &vmu->volgain);
987 } else if (!strcasecmp(var, "options")) {
988 apply_options(vmu, value);
992 static char *vm_check_password_shell(char *command, char *buf, size_t len)
999 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1002 pid = ast_safe_fork(0);
1008 snprintf(buf, len, "FAILURE: Fork failed");
1012 if (read(fds[0], buf, len) < 0) {
1013 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1018 AST_DECLARE_APP_ARGS(arg,
1021 char *mycmd = ast_strdupa(command);
1024 dup2(fds[1], STDOUT_FILENO);
1026 ast_close_fds_above_n(STDOUT_FILENO);
1028 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1030 execv(arg.v[0], arg.v);
1031 printf("FAILURE: %s", strerror(errno));
1039 * \brief Check that password meets minimum required length
1040 * \param vmu The voicemail user to change the password for.
1041 * \param password The password string to check
1043 * \return zero on ok, 1 on not ok.
1045 static int check_password(struct ast_vm_user *vmu, char *password)
1047 /* check minimum length */
1048 if (strlen(password) < minpassword)
1050 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1051 char cmd[255], buf[255];
1053 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1055 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1056 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1057 ast_debug(5, "Result: %s\n", buf);
1058 if (!strncasecmp(buf, "VALID", 5)) {
1059 ast_debug(3, "Passed password check: '%s'\n", buf);
1061 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1062 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1065 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1074 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1075 * \param vmu The voicemail user to change the password for.
1076 * \param password The new value to be set to the password for this user.
1078 * This only works if there is a realtime engine configured.
1079 * This is called from the (top level) vm_change_password.
1081 * \return zero on success, -1 on error.
1083 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1086 if (!strcmp(vmu->password, password)) {
1087 /* No change (but an update would return 0 rows updated, so we opt out here) */
1091 if (strlen(password) > 10) {
1092 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1094 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1095 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1102 * \brief Destructively Parse options and apply.
1104 static void apply_options(struct ast_vm_user *vmu, const char *options)
1109 stringp = ast_strdupa(options);
1110 while ((s = strsep(&stringp, "|"))) {
1112 if ((var = strsep(&value, "=")) && value) {
1113 apply_option(vmu, var, value);
1119 * \brief Loads the options specific to a voicemail user.
1121 * This is called when a vm_user structure is being set up, such as from load_options.
1123 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1125 for (; var; var = var->next) {
1126 if (!strcasecmp(var->name, "vmsecret")) {
1127 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1128 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1129 if (ast_strlen_zero(retval->password))
1130 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1131 } else if (!strcasecmp(var->name, "uniqueid")) {
1132 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1133 } else if (!strcasecmp(var->name, "pager")) {
1134 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1135 } else if (!strcasecmp(var->name, "email")) {
1136 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1137 } else if (!strcasecmp(var->name, "fullname")) {
1138 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1139 } else if (!strcasecmp(var->name, "context")) {
1140 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1141 } else if (!strcasecmp(var->name, "emailsubject")) {
1142 retval->emailsubject = ast_strdup(var->value);
1143 } else if (!strcasecmp(var->name, "emailbody")) {
1144 retval->emailbody = ast_strdup(var->value);
1146 } else if (!strcasecmp(var->name, "imapuser")) {
1147 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1148 retval->imapversion = imapversion;
1149 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1150 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1151 retval->imapversion = imapversion;
1152 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1153 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1154 retval->imapversion = imapversion;
1157 apply_option(retval, var->name, var->value);
1162 * \brief Determines if a DTMF key entered is valid.
1163 * \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.
1165 * Tests the character entered against the set of valid DTMF characters.
1166 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1168 static int is_valid_dtmf(const char *key)
1171 char *local_key = ast_strdupa(key);
1173 for (i = 0; i < strlen(key); ++i) {
1174 if (!strchr(VALID_DTMF, *local_key)) {
1175 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1184 * \brief Finds a voicemail user from the realtime engine.
1189 * 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.
1191 * \return The ast_vm_user structure for the user that was found.
1193 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1195 struct ast_variable *var;
1196 struct ast_vm_user *retval;
1198 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1200 ast_set_flag(retval, VM_ALLOCED);
1202 memset(retval, 0, sizeof(*retval));
1204 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1205 populate_defaults(retval);
1206 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1207 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1209 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1211 apply_options_full(retval, var);
1212 ast_variables_destroy(var);
1223 * \brief Finds a voicemail user from the users file or the realtime engine.
1228 * \return The ast_vm_user structure for the user that was found.
1230 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1232 /* This function could be made to generate one from a database, too */
1233 struct ast_vm_user *vmu = NULL, *cur;
1234 AST_LIST_LOCK(&users);
1236 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1237 context = "default";
1239 AST_LIST_TRAVERSE(&users, cur, list) {
1241 if (cur->imapversion != imapversion) {
1245 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1247 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1251 /* Make a copy, so that on a reload, we have no race */
1252 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1253 memcpy(vmu, cur, sizeof(*vmu));
1254 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1255 AST_LIST_NEXT(vmu, list) = NULL;
1258 vmu = find_user_realtime(ivm, context, mailbox);
1259 AST_LIST_UNLOCK(&users);
1264 * \brief Resets a user password to a specified password.
1269 * This does the actual change password work, called by the vm_change_password() function.
1271 * \return zero on success, -1 on error.
1273 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1275 /* This function could be made to generate one from a database, too */
1276 struct ast_vm_user *cur;
1278 AST_LIST_LOCK(&users);
1279 AST_LIST_TRAVERSE(&users, cur, list) {
1280 if ((!context || !strcasecmp(context, cur->context)) &&
1281 (!strcasecmp(mailbox, cur->mailbox)))
1285 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1288 AST_LIST_UNLOCK(&users);
1293 * \brief The handler for the change password option.
1294 * \param vmu The voicemail user to work with.
1295 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1296 * 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.
1297 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1299 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1301 struct ast_config *cfg = NULL;
1302 struct ast_variable *var = NULL;
1303 struct ast_category *cat = NULL;
1304 char *category = NULL, *value = NULL, *new = NULL;
1305 const char *tmp = NULL;
1306 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1307 if (!change_password_realtime(vmu, newpassword))
1310 /* check voicemail.conf */
1311 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1312 while ((category = ast_category_browse(cfg, category))) {
1313 if (!strcasecmp(category, vmu->context)) {
1314 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1315 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1318 value = strstr(tmp, ",");
1320 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1323 new = alloca((strlen(value)+strlen(newpassword)+1));
1324 sprintf(new, "%s%s", newpassword, value);
1325 if (!(cat = ast_category_get(cfg, category))) {
1326 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1329 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1332 /* save the results */
1333 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1334 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1335 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1339 /* check users.conf and update the password stored for the mailbox*/
1340 /* if no vmsecret entry exists create one. */
1341 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1342 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1343 while ((category = ast_category_browse(cfg, category))) {
1344 ast_debug(4, "users.conf: %s\n", category);
1345 if (!strcasecmp(category, vmu->mailbox)) {
1346 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1347 ast_debug(3, "looks like we need to make vmsecret!\n");
1348 var = ast_variable_new("vmsecret", newpassword, "");
1350 new = alloca(strlen(newpassword)+1);
1351 sprintf(new, "%s", newpassword);
1352 if (!(cat = ast_category_get(cfg, category))) {
1353 ast_debug(4, "failed to get category!\n");
1357 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1359 ast_variable_append(cat, var);
1362 /* save the results and clean things up */
1363 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1364 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1365 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1369 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1372 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1373 if (!ast_safe_system(buf)) {
1374 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1375 /* Reset the password in memory, too */
1376 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1381 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1382 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1383 * \param len The length of the path string that was written out.
1385 * The path is constructed as
1386 * VM_SPOOL_DIRcontext/ext/folder
1388 * \return zero on success, -1 on error.
1390 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1392 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1396 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1397 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1398 * \param len The length of the path string that was written out.
1400 * The path is constructed as
1401 * VM_SPOOL_DIRcontext/ext/folder
1403 * \return zero on success, -1 on error.
1405 static int make_file(char *dest, const int len, const char *dir, const int num)
1407 return snprintf(dest, len, "%s/msg%04d", dir, num);
1410 /* same as mkstemp, but return a FILE * */
1411 static FILE *vm_mkftemp(char *template)
1414 int pfd = mkstemp(template);
1415 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1417 p = fdopen(pfd, "w+");
1426 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1427 * \param dest String. base directory.
1428 * \param len Length of dest.
1429 * \param context String. Ignored if is null or empty string.
1430 * \param ext String. Ignored if is null or empty string.
1431 * \param folder String. Ignored if is null or empty string.
1432 * \return -1 on failure, 0 on success.
1434 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1436 mode_t mode = VOICEMAIL_DIR_MODE;
1439 make_dir(dest, len, context, ext, folder);
1440 if ((res = ast_mkdir(dest, mode))) {
1441 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1447 static const char *mbox(int id)
1449 static const char * const msgs[] = {
1467 return (id >= 0 && id < ARRAY_LEN(msgs)) ? msgs[id] : "Unknown";
1470 static void free_user(struct ast_vm_user *vmu)
1472 if (ast_test_flag(vmu, VM_ALLOCED)) {
1473 if (vmu->emailbody != NULL) {
1474 ast_free(vmu->emailbody);
1475 vmu->emailbody = NULL;
1477 if (vmu->emailsubject != NULL) {
1478 ast_free(vmu->emailsubject);
1479 vmu->emailsubject = NULL;
1485 /* All IMAP-specific functions should go in this block. This
1486 * keeps them from being spread out all over the code */
1488 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1491 struct vm_state *vms;
1492 unsigned long messageNum;
1494 /* Greetings aren't stored in IMAP, so we can't delete them there */
1499 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1500 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);
1504 /* find real message number based on msgnum */
1505 /* this may be an index into vms->msgArray based on the msgnum. */
1506 messageNum = vms->msgArray[msgnum];
1507 if (messageNum == 0) {
1508 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1511 if (option_debug > 2)
1512 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1513 /* delete message */
1514 snprintf (arg, sizeof(arg), "%lu", messageNum);
1515 ast_mutex_lock(&vms->lock);
1516 mail_setflag (vms->mailstream, arg, "\\DELETED");
1517 ast_mutex_unlock(&vms->lock);
1520 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1522 struct vm_state *vms_p;
1523 char *file, *filename;
1528 /* This function is only used for retrieval of IMAP greetings
1529 * regular messages are not retrieved this way, nor are greetings
1530 * if they are stored locally*/
1531 if (msgnum > -1 || !imapgreetings) {
1534 file = strrchr(ast_strdupa(dir), '/');
1538 ast_debug (1, "Failed to procure file name from directory passed.\n");
1543 /* check if someone is accessing this box right now... */
1544 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
1545 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1546 /* Unlike when retrieving a message, it is reasonable not to be able to find a
1547 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1548 * that's all we need to do.
1550 if (!(vms_p = create_vm_state_from_user(vmu))) {
1551 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1556 /* Greetings will never have a prepended message */
1557 *vms_p->introfn = '\0';
1559 ast_mutex_lock(&vms_p->lock);
1560 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1561 if (!vms_p->mailstream) {
1562 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1563 ast_mutex_unlock(&vms_p->lock);
1567 /*XXX Yuck, this could probably be done a lot better */
1568 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1569 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1570 /* We have the body, now we extract the file name of the first attachment. */
1571 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1572 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1574 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1575 ast_mutex_unlock(&vms_p->lock);
1578 filename = strsep(&attachment, ".");
1579 if (!strcmp(filename, file)) {
1580 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1581 vms_p->msgArray[vms_p->curmsg] = i + 1;
1582 save_body(body, vms_p, "2", attachment, 0);
1583 ast_mutex_unlock(&vms_p->lock);
1587 ast_mutex_unlock(&vms_p->lock);
1592 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1595 char *header_content;
1596 char *attachedfilefmt;
1598 struct vm_state *vms;
1599 char text_file[PATH_MAX];
1600 FILE *text_file_ptr;
1602 struct ast_vm_user *vmu;
1604 if (!(vmu = find_user(NULL, context, mailbox))) {
1605 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1610 if (imapgreetings) {
1611 res = imap_retrieve_greeting(dir, msgnum, vmu);
1619 /* Before anything can happen, we need a vm_state so that we can
1620 * actually access the imap server through the vms->mailstream
1622 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1623 /* This should not happen. If it does, then I guess we'd
1624 * need to create the vm_state, extract which mailbox to
1625 * open, and then set up the msgArray so that the correct
1626 * IMAP message could be accessed. If I have seen correctly
1627 * though, the vms should be obtainable from the vmstates list
1628 * and should have its msgArray properly set up.
1630 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1635 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1636 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1638 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1639 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1644 if (option_debug > 2)
1645 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1646 if (vms->msgArray[msgnum] == 0) {
1647 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1652 /* This will only work for new messages... */
1653 ast_mutex_lock(&vms->lock);
1654 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1655 ast_mutex_unlock(&vms->lock);
1656 /* empty string means no valid header */
1657 if (ast_strlen_zero(header_content)) {
1658 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1663 ast_mutex_lock(&vms->lock);
1664 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1665 ast_mutex_unlock(&vms->lock);
1667 /* We have the body, now we extract the file name of the first attachment. */
1668 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1669 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1671 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1676 /* Find the format of the attached file */
1678 strsep(&attachedfilefmt, ".");
1679 if (!attachedfilefmt) {
1680 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1685 save_body(body, vms, "2", attachedfilefmt, 0);
1686 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1687 *vms->introfn = '\0';
1690 /* Get info from headers!! */
1691 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1693 if (!(text_file_ptr = fopen(text_file, "w"))) {
1694 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1697 fprintf(text_file_ptr, "%s\n", "[message]");
1699 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1700 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1701 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1702 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1703 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1704 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1705 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1706 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1707 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1708 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1709 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1710 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1711 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1712 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1713 fclose(text_file_ptr);
1720 static int folder_int(const char *folder)
1722 /*assume a NULL folder means INBOX*/
1725 if (!strcasecmp(folder, imapfolder))
1727 else if (!strcasecmp(folder, "Old"))
1729 else if (!strcasecmp(folder, "Work"))
1731 else if (!strcasecmp(folder, "Family"))
1733 else if (!strcasecmp(folder, "Friends"))
1735 else if (!strcasecmp(folder, "Cust1"))
1737 else if (!strcasecmp(folder, "Cust2"))
1739 else if (!strcasecmp(folder, "Cust3"))
1741 else if (!strcasecmp(folder, "Cust4"))
1743 else if (!strcasecmp(folder, "Cust5"))
1745 else /*assume they meant INBOX if folder is not found otherwise*/
1750 * \brief Gets the number of messages that exist in a mailbox folder.
1755 * This method is used when IMAP backend is used.
1756 * \return The number of messages in this mailbox folder (zero or more).
1758 static int messagecount(const char *context, const char *mailbox, const char *folder)
1763 struct ast_vm_user *vmu, vmus;
1764 struct vm_state *vms_p;
1766 int fold = folder_int(folder);
1769 /* If URGENT, then look at INBOX */
1775 if (ast_strlen_zero(mailbox))
1778 /* We have to get the user before we can open the stream! */
1779 vmu = find_user(&vmus, context, mailbox);
1781 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1784 /* No IMAP account available */
1785 if (vmu->imapuser[0] == '\0') {
1786 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1791 /* No IMAP account available */
1792 if (vmu->imapuser[0] == '\0') {
1793 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1798 /* check if someone is accessing this box right now... */
1799 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1801 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1804 ast_debug(3, "Returning before search - user is logged in\n");
1805 if (fold == 0) { /* INBOX */
1806 return vms_p->newmessages;
1808 if (fold == 1) { /* Old messages */
1809 return vms_p->oldmessages;
1811 if (fold == 11) {/*Urgent messages*/
1812 return vms_p->urgentmessages;
1816 /* add one if not there... */
1817 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
1819 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1823 vms_p = create_vm_state_from_user(vmu);
1825 ret = init_mailstream(vms_p, fold);
1826 if (!vms_p->mailstream) {
1827 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1831 ast_mutex_lock(&vms_p->lock);
1832 pgm = mail_newsearchpgm ();
1833 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
1834 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
1840 /* In the special case where fold is 1 (old messages) we have to do things a bit
1841 * differently. Old messages are stored in the INBOX but are marked as "seen"
1847 /* look for urgent messages */
1855 vms_p->vmArrayIndex = 0;
1856 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1857 if (fold == 0 && urgent == 0)
1858 vms_p->newmessages = vms_p->vmArrayIndex;
1860 vms_p->oldmessages = vms_p->vmArrayIndex;
1861 if (fold == 0 && urgent == 1)
1862 vms_p->urgentmessages = vms_p->vmArrayIndex;
1863 /*Freeing the searchpgm also frees the searchhdr*/
1864 mail_free_searchpgm(&pgm);
1865 ast_mutex_unlock(&vms_p->lock);
1867 return vms_p->vmArrayIndex;
1869 ast_mutex_lock(&vms_p->lock);
1870 mail_ping(vms_p->mailstream);
1871 ast_mutex_unlock(&vms_p->lock);
1876 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
1878 char *myserveremail = serveremail;
1880 char introfn[PATH_MAX];
1884 char tmp[80] = "/tmp/astmail-XXXXXX";
1889 int ret; /* for better error checking */
1890 char *imap_flags = NIL;
1892 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
1893 if (msgnum < 0 && !imapgreetings) {
1897 /* Set urgent flag for IMAP message */
1898 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1899 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1900 imap_flags = "\\FLAGGED";
1903 /* Attach only the first format */
1904 fmt = ast_strdupa(fmt);
1906 strsep(&stringp, "|");
1908 if (!ast_strlen_zero(vmu->serveremail))
1909 myserveremail = vmu->serveremail;
1912 make_file(fn, sizeof(fn), dir, msgnum);
1914 ast_copy_string (fn, dir, sizeof(fn));
1916 snprintf(introfn, sizeof(introfn), "%sintro", fn);
1917 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1921 if (ast_strlen_zero(vmu->email)) {
1922 /* We need the vmu->email to be set when we call make_email_file, but
1923 * if we keep it set, a duplicate e-mail will be created. So at the end
1924 * of this function, we will revert back to an empty string if tempcopy
1927 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1931 if (!strcmp(fmt, "wav49"))
1933 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1935 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1937 if (!(p = vm_mkftemp(tmp))) {
1938 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1940 *(vmu->email) = '\0';
1944 if (msgnum < 0 && imapgreetings) {
1945 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1946 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1949 imap_delete_old_greeting(fn, vms);
1952 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX", S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
1953 /* read mail file to memory */
1956 if (!(buf = ast_malloc(len + 1))) {
1957 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1960 *(vmu->email) = '\0';
1963 if (fread(buf, len, 1, p) < len) {
1965 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
1969 ((char *) buf)[len] = '\0';
1970 INIT(&str, mail_string, buf, len);
1971 ret = init_mailstream(vms, NEW_FOLDER);
1973 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1974 ast_mutex_lock(&vms->lock);
1975 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1976 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1977 ast_mutex_unlock(&vms->lock);
1982 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
1988 ast_debug(3, "%s stored\n", fn);
1991 *(vmu->email) = '\0';
1998 * \brief Gets the number of messages that exist in the inbox folder.
1999 * \param mailbox_context
2000 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2001 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2002 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2004 * This method is used when IMAP backend is used.
2005 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2007 * \return zero on success, -1 on error.
2010 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2012 char tmp[PATH_MAX] = "";
2024 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2025 /* If no mailbox, return immediately */
2026 if (ast_strlen_zero(mailbox_context))
2029 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2030 context = strchr(tmp, '@');
2031 if (strchr(mailbox_context, ',')) {
2032 int tmpnew, tmpold, tmpurgent;
2033 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2035 while ((cur = strsep(&mb, ", "))) {
2036 if (!ast_strlen_zero(cur)) {
2037 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2045 *urgentmsgs += tmpurgent;
2056 context = "default";
2057 mailboxnc = (char *) mailbox_context;
2060 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
2064 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2068 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
2074 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2076 return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
2080 * \brief Determines if the given folder has messages.
2081 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2082 * \param folder the folder to look in
2084 * This function is used when the mailbox is stored in an IMAP back end.
2085 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2086 * \return 1 if the folder has one or more messages. zero otherwise.
2089 static int has_voicemail(const char *mailbox, const char *folder)
2091 char tmp[256], *tmp2, *box, *context;
2092 ast_copy_string(tmp, mailbox, sizeof(tmp));
2094 if (strchr(tmp2, ',')) {
2095 while ((box = strsep(&tmp2, ","))) {
2096 if (!ast_strlen_zero(box)) {
2097 if (has_voicemail(box, folder))
2102 if ((context = strchr(tmp, '@')))
2105 context = "default";
2106 return messagecount(context, tmp, folder) ? 1 : 0;
2110 * \brief Copies a message from one mailbox to another.
2120 * This works with IMAP storage based mailboxes.
2122 * \return zero on success, -1 on error.
2124 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)
2126 struct vm_state *sendvms = NULL, *destvms = NULL;
2127 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2128 if (msgnum >= recip->maxmsg) {
2129 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2132 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2133 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2136 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2137 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2140 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2141 ast_mutex_lock(&sendvms->lock);
2142 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T)) {
2143 ast_mutex_unlock(&sendvms->lock);
2146 ast_mutex_unlock(&sendvms->lock);
2147 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2151 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2153 char tmp[256], *t = tmp;
2154 size_t left = sizeof(tmp);
2156 if (box == OLD_FOLDER) {
2157 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
2159 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
2162 if (box == NEW_FOLDER) {
2163 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2165 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
2168 /* Build up server information */
2169 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2171 /* Add authentication user if present */
2172 if (!ast_strlen_zero(authuser))
2173 ast_build_string(&t, &left, "/authuser=%s", authuser);
2175 /* Add flags if present */
2176 if (!ast_strlen_zero(imapflags))
2177 ast_build_string(&t, &left, "/%s", imapflags);
2179 /* End with username */
2181 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2183 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2185 if (box == NEW_FOLDER || box == OLD_FOLDER)
2186 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
2187 else if (box == GREETINGS_FOLDER)
2188 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2189 else { /* Other folders such as Friends, Family, etc... */
2190 if (!ast_strlen_zero(imapparentfolder)) {
2191 /* imapparentfolder would typically be set to INBOX */
2192 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2194 snprintf(spec, len, "%s%s", tmp, mbox(box));
2199 static int init_mailstream(struct vm_state *vms, int box)
2201 MAILSTREAM *stream = NIL;
2206 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2209 if (option_debug > 2)
2210 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2211 if (vms->mailstream == NIL || !vms->mailstream) {
2213 ast_log(LOG_DEBUG, "mailstream not set.\n");
2215 stream = vms->mailstream;
2217 /* debug = T; user wants protocol telemetry? */
2218 debug = NIL; /* NO protocol telemetry? */
2220 if (delimiter == '\0') { /* did not probe the server yet */
2222 #ifdef USE_SYSTEM_IMAP
2223 #include <imap/linkage.c>
2224 #elif defined(USE_SYSTEM_CCLIENT)
2225 #include <c-client/linkage.c>
2227 #include "linkage.c"
2229 /* Connect to INBOX first to get folders delimiter */
2230 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2231 ast_mutex_lock(&vms->lock);
2232 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2233 ast_mutex_unlock(&vms->lock);
2234 if (stream == NIL) {
2235 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2238 get_mailbox_delimiter(stream);
2239 /* update delimiter in imapfolder */
2240 for (cp = imapfolder; *cp; cp++)
2244 /* Now connect to the target folder */
2245 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2246 if (option_debug > 2)
2247 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2248 ast_mutex_lock(&vms->lock);
2249 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2250 ast_mutex_unlock(&vms->lock);
2251 if (vms->mailstream == NIL) {
2258 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2262 int ret, urgent = 0;
2264 /* If Urgent, then look at INBOX */
2270 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2271 vms->imapversion = vmu->imapversion;
2272 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2274 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2275 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2279 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2283 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2284 check_quota(vms, (char *) mbox(box));
2287 ast_mutex_lock(&vms->lock);
2288 pgm = mail_newsearchpgm();
2290 /* Check IMAP folder for Asterisk messages only... */
2291 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2292 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2297 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2298 if (box == NEW_FOLDER && urgent == 1) {
2303 } else if (box == NEW_FOLDER && urgent == 0) {
2308 } else if (box == OLD_FOLDER) {
2313 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2315 vms->vmArrayIndex = 0;
2316 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2317 vms->lastmsg = vms->vmArrayIndex - 1;
2318 mail_free_searchpgm(&pgm);
2320 ast_mutex_unlock(&vms->lock);
2324 static void write_file(char *filename, char *buffer, unsigned long len)
2328 output = fopen (filename, "w");
2329 if (fwrite(buffer, len, 1, output) != 1) {
2330 if (ferror(output)) {
2331 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2337 static void update_messages_by_imapuser(const char *user, unsigned long number)
2339 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2341 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2345 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2346 vms->msgArray[vms->vmArrayIndex++] = number;
2349 void mm_searched(MAILSTREAM *stream, unsigned long number)
2351 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2353 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2356 update_messages_by_imapuser(user, number);
2359 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2361 struct ast_variable *var;
2362 struct ast_vm_user *vmu;
2364 vmu = ast_calloc(1, sizeof *vmu);
2367 ast_set_flag(vmu, VM_ALLOCED);
2368 populate_defaults(vmu);
2370 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2372 apply_options_full(vmu, var);
2373 ast_variables_destroy(var);
2381 /* Interfaces to C-client */
2383 void mm_exists(MAILSTREAM * stream, unsigned long number)
2385 /* mail_ping will callback here if new mail! */
2386 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2387 if (number == 0) return;
2392 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2394 /* mail_ping will callback here if expunged mail! */
2395 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2396 if (number == 0) return;
2401 void mm_flags(MAILSTREAM * stream, unsigned long number)
2403 /* mail_ping will callback here if read mail! */
2404 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2405 if (number == 0) return;
2410 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2412 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2413 mm_log (string, errflg);
2417 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2419 if (delimiter == '\0') {
2423 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2424 if (attributes & LATT_NOINFERIORS)
2425 ast_debug(5, "no inferiors\n");
2426 if (attributes & LATT_NOSELECT)
2427 ast_debug(5, "no select\n");
2428 if (attributes & LATT_MARKED)
2429 ast_debug(5, "marked\n");
2430 if (attributes & LATT_UNMARKED)
2431 ast_debug(5, "unmarked\n");
2435 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2437 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2438 if (attributes & LATT_NOINFERIORS)
2439 ast_debug(5, "no inferiors\n");
2440 if (attributes & LATT_NOSELECT)
2441 ast_debug(5, "no select\n");
2442 if (attributes & LATT_MARKED)
2443 ast_debug(5, "marked\n");
2444 if (attributes & LATT_UNMARKED)
2445 ast_debug(5, "unmarked\n");
2449 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2451 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2452 if (status->flags & SA_MESSAGES)
2453 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2454 if (status->flags & SA_RECENT)
2455 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2456 if (status->flags & SA_UNSEEN)
2457 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2458 if (status->flags & SA_UIDVALIDITY)
2459 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2460 if (status->flags & SA_UIDNEXT)
2461 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2462 ast_log(AST_LOG_NOTICE, "\n");
2466 void mm_log(char *string, long errflg)
2468 switch ((short) errflg) {
2470 ast_debug(1, "IMAP Info: %s\n", string);
2474 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2477 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2483 void mm_dlog(char *string)
2485 ast_log(AST_LOG_NOTICE, "%s\n", string);
2489 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2491 struct ast_vm_user *vmu;
2493 ast_debug(4, "Entering callback mm_login\n");
2495 ast_copy_string(user, mb->user, MAILTMPLEN);
2497 /* We should only do this when necessary */
2498 if (!ast_strlen_zero(authpassword)) {
2499 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2501 AST_LIST_TRAVERSE(&users, vmu, list) {
2502 if (!strcasecmp(mb->user, vmu->imapuser)) {
2503 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2508 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2509 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2517 void mm_critical(MAILSTREAM * stream)
2522 void mm_nocritical(MAILSTREAM * stream)
2527 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2529 kill (getpid (), SIGSTOP);
2534 void mm_fatal(char *string)
2536 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2539 /* C-client callback to handle quota */
2540 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2542 struct vm_state *vms;
2543 char *mailbox = stream->mailbox, *user;
2544 char buf[1024] = "";
2545 unsigned long usage = 0, limit = 0;
2548 usage = pquota->usage;
2549 limit = pquota->limit;
2550 pquota = pquota->next;
2553 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)))) {
2554 ast_log(AST_LOG_ERROR, "No state found.\n");
2558 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2560 vms->quota_usage = usage;
2561 vms->quota_limit = limit;
2564 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2566 char *start, *eol_pnt;
2569 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2572 taglen = strlen(tag) + 1;
2576 if (!(start = strstr(header, tag)))
2579 /* Since we can be called multiple times we should clear our buffer */
2580 memset(buf, 0, len);
2582 ast_copy_string(buf, start+taglen, len);
2583 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2588 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2590 char *start, *quote, *eol_pnt;
2592 if (ast_strlen_zero(mailbox))
2595 if (!(start = strstr(mailbox, "/user=")))
2598 ast_copy_string(buf, start+6, len);
2600 if (!(quote = strchr(buf, '\"'))) {
2601 if (!(eol_pnt = strchr(buf, '/')))
2602 eol_pnt = strchr(buf,'}');
2606 eol_pnt = strchr(buf+1,'\"');
2612 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2614 struct vm_state *vms_p;
2616 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2617 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2620 if (option_debug > 4)
2621 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2622 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2624 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2625 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2626 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2627 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2628 vms_p->imapversion = vmu->imapversion;
2629 if (option_debug > 4)
2630 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2632 /* set mailbox to INBOX! */
2633 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2634 init_vm_state(vms_p);
2635 vmstate_insert(vms_p);
2639 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2641 struct vmstate *vlist = NULL;
2644 struct vm_state *vms;
2645 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2646 vms = pthread_getspecific(ts_vmstate.key);
2650 AST_LIST_LOCK(&vmstates);
2651 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2653 ast_debug(3, "error: vms is NULL for %s\n", user);
2656 if (vlist->vms->imapversion != imapversion) {
2659 if (!vlist->vms->imapuser) {
2660 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2664 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2665 AST_LIST_UNLOCK(&vmstates);
2669 AST_LIST_UNLOCK(&vmstates);
2671 ast_debug(3, "%s not found in vmstates\n", user);
2676 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2679 struct vmstate *vlist = NULL;
2680 const char *local_context = S_OR(context, "default");
2683 struct vm_state *vms;
2684 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2685 vms = pthread_getspecific(ts_vmstate.key);
2689 AST_LIST_LOCK(&vmstates);
2690 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2692 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2695 if (vlist->vms->imapversion != imapversion) {
2698 if (!vlist->vms->username || !vlist->vms->context) {
2699 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2703 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);
2705 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2706 ast_debug(3, "Found it!\n");
2707 AST_LIST_UNLOCK(&vmstates);
2711 AST_LIST_UNLOCK(&vmstates);
2713 ast_debug(3, "%s not found in vmstates\n", mailbox);
2718 static void vmstate_insert(struct vm_state *vms)
2721 struct vm_state *altvms;
2723 /* If interactive, it probably already exists, and we should
2724 use the one we already have since it is more up to date.
2725 We can compare the username to find the duplicate */
2726 if (vms->interactive == 1) {
2727 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2729 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2730 vms->newmessages = altvms->newmessages;
2731 vms->oldmessages = altvms->oldmessages;
2732 vms->vmArrayIndex = altvms->vmArrayIndex;
2733 vms->lastmsg = altvms->lastmsg;
2734 vms->curmsg = altvms->curmsg;
2735 /* get a pointer to the persistent store */
2736 vms->persist_vms = altvms;
2737 /* Reuse the mailstream? */
2738 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2739 vms->mailstream = altvms->mailstream;
2741 vms->mailstream = NIL;
2747 if (!(v = ast_calloc(1, sizeof(*v))))
2752 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2754 AST_LIST_LOCK(&vmstates);
2755 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2756 AST_LIST_UNLOCK(&vmstates);
2759 static void vmstate_delete(struct vm_state *vms)
2761 struct vmstate *vc = NULL;
2762 struct vm_state *altvms = NULL;
2764 /* If interactive, we should copy pertinent info
2765 back to the persistent state (to make update immediate) */
2766 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2767 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2768 altvms->newmessages = vms->newmessages;
2769 altvms->oldmessages = vms->oldmessages;
2770 altvms->updated = 1;
2771 vms->mailstream = mail_close(vms->mailstream);
2773 /* Interactive states are not stored within the persistent list */
2777 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2779 AST_LIST_LOCK(&vmstates);
2780 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2781 if (vc->vms == vms) {
2782 AST_LIST_REMOVE_CURRENT(list);
2786 AST_LIST_TRAVERSE_SAFE_END
2787 AST_LIST_UNLOCK(&vmstates);
2790 ast_mutex_destroy(&vc->vms->lock);
2794 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2797 static void set_update(MAILSTREAM * stream)
2799 struct vm_state *vms;
2800 char *mailbox = stream->mailbox, *user;
2801 char buf[1024] = "";
2803 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2804 if (user && option_debug > 2)
2805 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2809 ast_debug(3, "User %s mailbox set for update.\n", user);
2811 vms->updated = 1; /* Set updated flag since mailbox changed */
2814 static void init_vm_state(struct vm_state *vms)
2817 vms->vmArrayIndex = 0;
2818 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2819 vms->msgArray[x] = 0;
2821 ast_mutex_init(&vms->lock);
2824 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
2828 char *fn = is_intro ? vms->introfn : vms->fn;
2830 unsigned long newlen;
2833 if (!body || body == NIL)
2836 ast_mutex_lock(&vms->lock);
2837 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2838 ast_mutex_unlock(&vms->lock);
2839 if (body_content != NIL) {
2840 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2841 /* ast_debug(1,body_content); */
2842 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
2843 /* If the body of the file is empty, return an error */
2847 write_file(filename, (char *) body_decoded, newlen);
2849 ast_debug(5, "Body of message is NULL.\n");
2856 * \brief Get delimiter via mm_list callback
2859 * Determines the delimiter character that is used by the underlying IMAP based mail store.
2861 /* MUTEX should already be held */
2862 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2864 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2865 mail_list(stream, tmp, "*");
2869 * \brief Check Quota for user
2870 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2871 * \param mailbox the mailbox to check the quota for.
2873 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2875 static void check_quota(struct vm_state *vms, char *mailbox) {
2876 ast_mutex_lock(&vms->lock);
2877 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2878 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2879 if (vms && vms->mailstream != NULL) {
2880 imap_getquotaroot(vms->mailstream, mailbox);
2882 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2884 ast_mutex_unlock(&vms->lock);
2887 #endif /* IMAP_STORAGE */
2889 /*! \brief Lock file path
2890 only return failure if ast_lock_path returns 'timeout',
2891 not if the path does not exist or any other reason
2893 static int vm_lock_path(const char *path)
2895 switch (ast_lock_path(path)) {
2896 case AST_LOCK_TIMEOUT:
2905 struct generic_prepare_struct {
2911 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2913 struct generic_prepare_struct *gps = data;
2917 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2918 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2919 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2922 res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
2923 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2924 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2925 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2928 for (i = 0; i < gps->argc; i++)
2929 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2935 * \brief Retrieves a file from an ODBC data store.
2936 * \param dir the path to the file to be retreived.
2937 * \param msgnum the message number, such as within a mailbox folder.
2939 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2940 * 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.
2942 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2943 * The output is the message information file with the name msgnum and the extension .txt
2944 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2946 * \return 0 on success, -1 on error.
2948 static int retrieve_file(char *dir, int msgnum)
2954 void *fdm = MAP_FAILED;
2955 SQLSMALLINT colcount = 0;
2962 SQLSMALLINT datatype;
2963 SQLSMALLINT decimaldigits;
2964 SQLSMALLINT nullable;
2970 char full_fn[PATH_MAX];
2972 char *argv[] = { dir, msgnums };
2973 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2975 struct odbc_obj *obj;
2976 obj = ast_odbc_request_obj(odbc_database, 0);
2978 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2979 c = strchr(fmt, '|');
2982 if (!strcasecmp(fmt, "wav49"))
2984 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
2986 make_file(fn, sizeof(fn), dir, msgnum);
2988 ast_copy_string(fn, dir, sizeof(fn));
2990 /* Create the information file */
2991 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2993 if (!(f = fopen(full_fn, "w+"))) {
2994 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2998 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2999 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3000 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3002 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3003 ast_odbc_release_obj(obj);
3006 res = SQLFetch(stmt);
3007 if (res == SQL_NO_DATA) {
3008 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3009 ast_odbc_release_obj(obj);
3011 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3012 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3013 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3014 ast_odbc_release_obj(obj);
3017 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3019 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3020 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3021 ast_odbc_release_obj(obj);
3024 res = SQLNumResultCols(stmt, &colcount);
3025 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3026 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3027 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3028 ast_odbc_release_obj(obj);
3032 fprintf(f, "[message]\n");
3033 for (x = 0; x < colcount; x++) {
3035 collen = sizeof(coltitle);
3036 res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
3037 &datatype, &colsize, &decimaldigits, &nullable);
3038 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3039 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3040 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3041 ast_odbc_release_obj(obj);
3044 if (!strcasecmp(coltitle, "recording")) {
3046 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3050 lseek(fd, fdlen - 1, SEEK_SET);
3051 if (write(fd, tmp, 1) != 1) {
3056 /* Read out in small chunks */
3057 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3058 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3059 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3060 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3061 ast_odbc_release_obj(obj);
3064 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3065 munmap(fdm, CHUNKSIZE);
3066 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3067 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3069 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3070 ast_odbc_release_obj(obj);
3075 if (truncate(full_fn, fdlen) < 0) {
3076 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3080 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3081 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3082 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3083 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3084 ast_odbc_release_obj(obj);
3087 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3088 fprintf(f, "%s=%s\n", coltitle, rowdata);
3091 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3092 ast_odbc_release_obj(obj);
3094 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3104 * \brief Determines the highest message number in use for a given user and mailbox folder.
3106 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3108 * This method is used when mailboxes are stored in an ODBC back end.
3109 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3111 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3113 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3120 char *argv[] = { dir };
3121 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3123 struct odbc_obj *obj;
3124 obj = ast_odbc_request_obj(odbc_database, 0);
3126 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3127 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3129 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3130 ast_odbc_release_obj(obj);
3133 res = SQLFetch(stmt);
3134 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3135 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3136 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3137 ast_odbc_release_obj(obj);
3140 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3141 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3142 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3143 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3144 ast_odbc_release_obj(obj);
3147 if (sscanf(rowdata, "%30d", &x) != 1)
3148 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3149 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3150 ast_odbc_release_obj(obj);
3152 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3158 * \brief Determines if the specified message exists.
3159 * \param dir the folder the mailbox folder to look for messages.
3160 * \param msgnum the message index to query for.
3162 * This method is used when mailboxes are stored in an ODBC back end.
3164 * \return greater than zero if the message exists, zero when the message does not exist or on error.
3166 static int message_exists(char *dir, int msgnum)
3174 char *argv[] = { dir, msgnums };
3175 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3177 struct odbc_obj *obj;
3178 obj = ast_odbc_request_obj(odbc_database, 0);
3180 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3181 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3182 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3184 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3185 ast_odbc_release_obj(obj);
3188 res = SQLFetch(stmt);
3189 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3190 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3191 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3192 ast_odbc_release_obj(obj);
3195 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3196 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3197 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3198 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3199 ast_odbc_release_obj(obj);
3202 if (sscanf(rowdata, "%30d", &x) != 1)
3203 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3204 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3205 ast_odbc_release_obj(obj);
3207 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3213 * \brief returns the one-based count for messages.
3215 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3217 * This method is used when mailboxes are stored in an ODBC back end.
3218 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3219 * one-based messages.
3220 * This method just calls last_message_index and returns +1 of its value.
3222 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3224 static int count_messages(struct ast_vm_user *vmu, char *dir)
3226 return last_message_index(vmu, dir) + 1;
3230 * \brief Deletes a message from the mailbox folder.
3231 * \param sdir The mailbox folder to work in.
3232 * \param smsg The message index to be deleted.
3234 * This method is used when mailboxes are stored in an ODBC back end.
3235 * The specified message is directly deleted from the database 'voicemessages' table.
3237 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3239 static void delete_file(char *sdir, int smsg)
3244 char *argv[] = { sdir, msgnums };
3245 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3247 struct odbc_obj *obj;
3248 obj = ast_odbc_request_obj(odbc_database, 0);
3250 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3251 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3252 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3254 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3256 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3257 ast_odbc_release_obj(obj);
3259 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3264 * \brief Copies a voicemail from one mailbox to another.
3265 * \param sdir the folder for which to look for the message to be copied.
3266 * \param smsg the index of the message to be copied.
3267 * \param ddir the destination folder to copy the message into.
3268 * \param dmsg the index to be used for the copied message.
3269 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3270 * \param dmailboxcontext The context for the destination user.
3272 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3274 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3280 struct odbc_obj *obj;
3281 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3282 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3284 delete_file(ddir, dmsg);
3285 obj = ast_odbc_request_obj(odbc_database, 0);
3287 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3288 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3289 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
3290 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3292 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3294 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3295 ast_odbc_release_obj(obj);
3297 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3301 struct insert_data {
3308 const char *context;
3309 const char *macrocontext;
3310 const char *callerid;
3311 const char *origtime;
3312 const char *duration;
3314 char *mailboxcontext;
3315 const char *category;
3319 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3321 struct insert_data *data = vdata;
3325 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3326 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3327 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3328 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3332 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
3333 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
3334 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
3335 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
3336 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
3337 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
3338 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
3339 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
3340 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
3341 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
3342 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
3343 if (!ast_strlen_zero(data->category)) {
3344 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
3346 res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
3347 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3348 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3349 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3357 * \brief Stores a voicemail into the database.
3358 * \param dir the folder the mailbox folder to store the message.
3359 * \param mailboxuser the user owning the mailbox folder.
3360 * \param mailboxcontext
3361 * \param msgnum the message index for the message to be stored.
3363 * This method is used when mailboxes are stored in an ODBC back end.
3364 * The message sound file and information file is looked up on the file system.
3365 * A SQL query is invoked to store the message into the (MySQL) database.
3367 * \return the zero on success -1 on error.
3369 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3373 void *fdm = MAP_FAILED;
3379 char full_fn[PATH_MAX];
3382 struct ast_config *cfg = NULL;
3383 struct odbc_obj *obj;
3384 struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
3385 .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "" };
3386 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3388 delete_file(dir, msgnum);
3389 if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3390 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3395 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3396 c = strchr(fmt, '|');
3399 if (!strcasecmp(fmt, "wav49"))
3401 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3403 make_file(fn, sizeof(fn), dir, msgnum);
3405 ast_copy_string(fn, dir, sizeof(fn));
3406 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3407 cfg = ast_config_load(full_fn, config_flags);
3408 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3409 fd = open(full_fn, O_RDWR);
3411 ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3415 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
3416 if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3419 if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3420 idata.macrocontext = "";
3422 if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
3423 idata.callerid = "";
3425 if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
3426 idata.origtime = "";
3428 if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
3429 idata.duration = "";
3431 if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
3432 idata.category = "";
3434 if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
3438 fdlen = lseek(fd, 0, SEEK_END);
3439 lseek(fd, 0, SEEK_SET);
3440 printf("Length is %zd\n", fdlen);
3441 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
3442 if (fdm == MAP_FAILED) {
3443 ast_log(AST_LOG_WARNING, "Memory map failed!\n");
3448 idata.datalen = idata.indlen = fdlen;
3450 if (!ast_strlen_zero(idata.category))
3451 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
3453 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
3455 if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
3456 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3458 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3463 ast_odbc_release_obj(obj);
3466 ast_config_destroy(cfg);
3467 if (fdm != MAP_FAILED)
3475 * \brief Renames a message in a mailbox folder.
3476 * \param sdir The folder of the message to be renamed.
3477 * \param smsg The index of the message to be renamed.
3478 * \param mailboxuser The user to become the owner of the message after it is renamed. Usually this will be the same as the original owner.
3479 * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
3480 * \param ddir The destination folder for the message to be renamed into
3481 * \param dmsg The destination message for the message to be renamed.
3483 * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
3484 * The is usually used to resequence the messages in the mailbox, such as to delete messag index 0, it would be called successively to slide all the other messages down one index.
3485 * But in theory, because the SQL query performs an update on (dir, msgnum, mailboxuser, mailboxcontext) in the database, it should be possible to have the message relocated to another mailbox or context as well.
3487 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
3493 struct odbc_obj *obj;
3494 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
3495 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3497 delete_file(ddir, dmsg);
3498 obj = ast_odbc_request_obj(odbc_database, 0);
3500 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3501 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3502 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
3503 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3505 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3507 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3508 ast_odbc_release_obj(obj);
3510 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3515 * \brief Removes a voicemail message file.
3516 * \param dir the path to the message file.
3517 * \param msgnum the unique number for the message within the mailbox.
3519 * Removes the message content file and the information file.
3520 * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
3521 * Typical use is to clean up after a RETRIEVE operation.
3522 * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
3523 * \return zero on success, -1 on error.
3525 static int remove_file(char *dir, int msgnum)
3528 char full_fn[PATH_MAX];
3532 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3533 make_file(fn, sizeof(fn), dir, msgnum);
3535 ast_copy_string(fn, dir, sizeof(fn));
3536 ast_filedelete(fn, NULL);
3537 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3542 #ifndef IMAP_STORAGE
3544 * \brief Find all .txt files - even if they are not in sequence from 0000.
3548 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3550 * \return the count of messages, zero or more.
3552 static int count_messages(struct ast_vm_user *vmu, char *dir)
3557 struct dirent *vment = NULL;
3559 if (vm_lock_path(dir))
3560 return ERROR_LOCK_PATH;
3562 if ((vmdir = opendir(dir))) {
3563 while ((vment = readdir(vmdir))) {
3564 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
3570 ast_unlock_path(dir);
3576 * \brief Renames a message in a mailbox folder.
3577 * \param sfn The path to the mailbox information and data file to be renamed.
3578 * \param dfn The path for where the message data and information files will be renamed to.
3580 * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3582 static void rename_file(char *sfn, char *dfn)
3584 char stxt[PATH_MAX];
3585 char dtxt[PATH_MAX];
3586 ast_filerename(sfn, dfn, NULL);
3587 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
3588 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
3589 if (ast_check_realtime("voicemail_data")) {
3590 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
3596 * \brief Determines the highest message number in use for a given user and mailbox folder.