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 * \brief Comedian Mail - Voicemail System
23 * \author Mark Spencer <markster@digium.com>
25 * \extref Unixodbc - http://www.unixodbc.org
26 * \extref A source distribution of University of Washington's IMAP
27 c-client (http://www.washington.edu/imap/
31 * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
32 * \ingroup applications
33 * \note This module requires res_adsi to load. This needs to be optional
38 * \note This file is now almost impossible to work with, due to all \#ifdefs.
39 * Feels like the database code before realtime. Someone - please come up
40 * with a plan to clean this up.
44 <depend>res_smdi</depend>
48 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_directory.o">
49 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
54 <conflict>IMAP_STORAGE</conflict>
55 <defaultenabled>no</defaultenabled>
57 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
58 <depend>imap_tk</depend>
59 <conflict>ODBC_STORAGE</conflict>
61 <defaultenabled>no</defaultenabled>
72 #ifdef USE_SYSTEM_IMAP
73 #include <imap/c-client.h>
74 #include <imap/imap4r1.h>
75 #include <imap/linkage.h>
76 #elif defined (USE_SYSTEM_CCLIENT)
77 #include <c-client/c-client.h>
78 #include <c-client/imap4r1.h>
79 #include <c-client/linkage.h>
87 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
89 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
96 #include "asterisk/logger.h"
97 #include "asterisk/lock.h"
98 #include "asterisk/file.h"
99 #include "asterisk/channel.h"
100 #include "asterisk/pbx.h"
101 #include "asterisk/config.h"
102 #include "asterisk/say.h"
103 #include "asterisk/module.h"
104 #include "asterisk/adsi.h"
105 #include "asterisk/app.h"
106 #include "asterisk/manager.h"
107 #include "asterisk/dsp.h"
108 #include "asterisk/localtime.h"
109 #include "asterisk/cli.h"
110 #include "asterisk/utils.h"
111 #include "asterisk/stringfields.h"
112 #include "asterisk/smdi.h"
113 #include "asterisk/event.h"
114 #include "asterisk/taskprocessor.h"
117 #include "asterisk/res_odbc.h"
121 <application name="VoiceMail" language="en_US">
123 Leave a Voicemail message.
126 <parameter name="mailboxs" argsep="&" required="true">
127 <argument name="mailbox1" argsep="@" required="true">
128 <argument name="mailbox" required="true" />
129 <argument name="context" />
131 <argument name="mailbox2" argsep="@" multiple="true">
132 <argument name="mailbox" required="true" />
133 <argument name="context" />
136 <parameter name="options">
139 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
142 <argument name="c" />
143 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
144 if played during the greeting. Context defaults to the current context.</para>
147 <argument name="#" required="true" />
148 <para>Use the specified amount of gain when recording the voicemail
149 message. The units are whole-number decibels (dB). Only works on supported
150 technologies, which is DAHDI only.</para>
153 <para>Skip the playback of instructions for leaving a message to the
154 calling party.</para>
157 <para>Play the <literal>unavailable</literal> greeting.</para>
160 <para>Mark message as <literal>URGENT</literal>.</para>
163 <para>Mark message as <literal>PRIORITY</literal>.</para>
169 <para>This application allows the calling party to leave a message for the specified
170 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
171 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
173 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
176 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
179 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
182 <para>This application will set the following channel variable upon completion:</para>
184 <variable name="VMSTATUS">
185 <para>This indicates the status of the execution of the VoiceMail application.</para>
186 <value name="SUCCESS" />
187 <value name="USEREXIT" />
188 <value name="FAILED" />
193 <application name="VoiceMailMain" language="en_US">
195 Check Voicemail messages.
198 <parameter name="mailbox" required="true" argsep="@">
199 <argument name="mailbox" />
200 <argument name="context" />
202 <parameter name="options">
205 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
206 the mailbox that is entered by the caller.</para>
209 <argument name="#" required="true" />
210 <para>Use the specified amount of gain when recording a voicemail message.
211 The units are whole-number decibels (dB).</para>
214 <para>Skip checking the passcode for the mailbox.</para>
217 <argument name="folder" required="true" />
218 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
219 Defaults to <literal>INBOX</literal>.</para>
225 <para>This application allows the calling party to check voicemail messages. A specific
226 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
227 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
228 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
229 <literal>default</literal> context will be used.</para>
232 <application name="MailboxExists" language="en_US">
234 Check to see if Voicemail mailbox exists.
237 <parameter name="mailbox" required="true" argsep="@">
238 <argument name="mailbox" required="true" />
239 <argument name="context" />
241 <parameter name="options">
242 <para>None options.</para>
246 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
247 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
249 <para>This application will set the following channel variable upon completion:</para>
251 <variable name="VMBOXEXISTSSTATUS">
252 <para>This will contain the status of the execution of the MailboxExists application.
253 Possible values include:</para>
254 <value name="SUCCESS" />
255 <value name="FAILED" />
260 <application name="VMAuthenticate" language="en_US">
262 Authenticate with Voicemail passwords.
265 <parameter name="mailbox" required="true" argsep="@">
266 <argument name="mailbox" />
267 <argument name="context" />
269 <parameter name="options">
272 <para>Skip playing the initial prompts.</para>
278 <para>This application behaves the same way as the Authenticate application, but the passwords
279 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
280 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
281 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
285 <function name="MAILBOX_EXISTS" language="en_US">
287 Tell if a mailbox is configured.
290 <parameter name="mailbox" required="true" />
291 <parameter name="context" />
294 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
295 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
302 static char imapserver[48];
303 static char imapport[8];
304 static char imapflags[128];
305 static char imapfolder[64];
306 static char imapparentfolder[64] = "\0";
307 static char greetingfolder[64];
308 static char authuser[32];
309 static char authpassword[42];
311 static int expungeonhangup = 1;
312 static int imapgreetings = 0;
313 static char delimiter = '\0';
318 /* Forward declarations for IMAP */
319 static int init_mailstream(struct vm_state *vms, int box);
320 static void write_file(char *filename, char *buffer, unsigned long len);
321 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
322 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
323 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
324 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
325 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
326 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
327 static void vmstate_insert(struct vm_state *vms);
328 static void vmstate_delete(struct vm_state *vms);
329 static void set_update(MAILSTREAM * stream);
330 static void init_vm_state(struct vm_state *vms);
331 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
332 static void get_mailbox_delimiter(MAILSTREAM *stream);
333 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
334 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
335 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);
336 static void update_messages_by_imapuser(const char *user, unsigned long number);
337 static int vm_delete(char *file);
339 static int imap_remove_file (char *dir, int msgnum);
340 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
341 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
342 static void check_quota(struct vm_state *vms, char *mailbox);
343 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
345 struct vm_state *vms;
346 AST_LIST_ENTRY(vmstate) list;
349 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
353 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
355 #define COMMAND_TIMEOUT 5000
356 /* Don't modify these here; set your umask at runtime instead */
357 #define VOICEMAIL_DIR_MODE 0777
358 #define VOICEMAIL_FILE_MODE 0666
359 #define CHUNKSIZE 65536
361 #define VOICEMAIL_CONFIG "voicemail.conf"
362 #define ASTERISK_USERNAME "asterisk"
364 /* Define fast-forward, pause, restart, and reverse keys
365 while listening to a voicemail message - these are
366 strings, not characters */
367 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
368 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
369 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
370 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
371 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
372 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
374 /* Default mail command to mail voicemail. Change it with the
375 mailcmd= command in voicemail.conf */
376 #define SENDMAIL "/usr/sbin/sendmail -t"
378 #define INTRO "vm-intro"
381 #define MAXMSGLIMIT 9999
383 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
385 #define BASELINELEN 72
386 #define BASEMAXINLINE 256
389 #define MAX_DATETIME_FORMAT 512
390 #define MAX_NUM_CID_CONTEXTS 10
392 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
393 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
394 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
395 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
396 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
397 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
398 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
399 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
400 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
401 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
402 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
403 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
404 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
405 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
406 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
407 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
408 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
409 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
410 #define ERROR_LOCK_PATH -100
423 OPT_SILENT = (1 << 0),
424 OPT_BUSY_GREETING = (1 << 1),
425 OPT_UNAVAIL_GREETING = (1 << 2),
426 OPT_RECORDGAIN = (1 << 3),
427 OPT_PREPEND_MAILBOX = (1 << 4),
428 OPT_AUTOPLAY = (1 << 6),
429 OPT_DTMFEXIT = (1 << 7),
430 OPT_MESSAGE_Urgent = (1 << 8),
431 OPT_MESSAGE_PRIORITY = (1 << 9)
435 OPT_ARG_RECORDGAIN = 0,
436 OPT_ARG_PLAYFOLDER = 1,
437 OPT_ARG_DTMFEXIT = 2,
438 /* This *must* be the last value in this enum! */
439 OPT_ARG_ARRAY_SIZE = 3,
442 AST_APP_OPTIONS(vm_app_options, {
443 AST_APP_OPTION('s', OPT_SILENT),
444 AST_APP_OPTION('b', OPT_BUSY_GREETING),
445 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
446 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
447 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
448 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
449 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
450 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
451 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
454 static int load_config(int reload);
456 /*! \page vmlang Voicemail Language Syntaxes Supported
458 \par Syntaxes supported, not really language codes.
465 \arg \b pt - Portuguese
466 \arg \b pt_BR - Portuguese (Brazil)
468 \arg \b no - Norwegian
470 \arg \b tw - Chinese (Taiwan)
471 \arg \b ua - Ukrainian
473 German requires the following additional soundfile:
474 \arg \b 1F einE (feminine)
476 Spanish requires the following additional soundfile:
477 \arg \b 1M un (masculine)
479 Dutch, Portuguese & Spanish require the following additional soundfiles:
480 \arg \b vm-INBOXs singular of 'new'
481 \arg \b vm-Olds singular of 'old/heard/read'
484 \arg \b vm-INBOX nieuwe (nl)
485 \arg \b vm-Old oude (nl)
488 \arg \b vm-new-a 'new', feminine singular accusative
489 \arg \b vm-new-e 'new', feminine plural accusative
490 \arg \b vm-new-ych 'new', feminine plural genitive
491 \arg \b vm-old-a 'old', feminine singular accusative
492 \arg \b vm-old-e 'old', feminine plural accusative
493 \arg \b vm-old-ych 'old', feminine plural genitive
494 \arg \b digits/1-a 'one', not always same as 'digits/1'
495 \arg \b digits/2-ie 'two', not always same as 'digits/2'
498 \arg \b vm-nytt singular of 'new'
499 \arg \b vm-nya plural of 'new'
500 \arg \b vm-gammalt singular of 'old'
501 \arg \b vm-gamla plural of 'old'
502 \arg \b digits/ett 'one', not always same as 'digits/1'
505 \arg \b vm-ny singular of 'new'
506 \arg \b vm-nye plural of 'new'
507 \arg \b vm-gammel singular of 'old'
508 \arg \b vm-gamle plural of 'old'
516 Ukrainian requires the following additional soundfile:
517 \arg \b vm-nove 'nove'
518 \arg \b vm-stare 'stare'
519 \arg \b digits/ua/1e 'odne'
521 Italian requires the following additional soundfile:
525 \arg \b vm-nuovi new plural
526 \arg \b vm-vecchio old
527 \arg \b vm-vecchi old plural
529 Chinese (Taiwan) requires the following additional soundfile:
530 \arg \b vm-tong A class-word for call (tong1)
531 \arg \b vm-ri A class-word for day (ri4)
532 \arg \b vm-you You (ni3)
533 \arg \b vm-haveno Have no (mei2 you3)
534 \arg \b vm-have Have (you3)
535 \arg \b vm-listen To listen (yao4 ting1)
538 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
539 spelled among others when you have to change folder. For the above reasons, vm-INBOX
540 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
549 unsigned char iobuf[BASEMAXINLINE];
552 /*! Structure for linked list of users
553 * Use ast_vm_user_destroy() to free one of these structures. */
555 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
556 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
557 char password[80]; /*!< Secret pin code, numbers only */
558 char fullname[80]; /*!< Full name, for directory app */
559 char email[80]; /*!< E-mail address */
560 char pager[80]; /*!< E-mail address to pager (no attachment) */
561 char serveremail[80]; /*!< From: Mail address */
562 char mailcmd[160]; /*!< Configurable mail command */
563 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
564 char zonetag[80]; /*!< Time zone */
567 char uniqueid[80]; /*!< Unique integer identifier */
569 char attachfmt[20]; /*!< Attachment format */
570 unsigned int flags; /*!< VM_ flags */
572 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
573 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
574 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
576 char imapuser[80]; /*!< IMAP server login */
577 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
579 double volgain; /*!< Volume gain for voicemails sent via email */
580 AST_LIST_ENTRY(ast_vm_user) list;
583 /*! Voicemail time zones */
585 AST_LIST_ENTRY(vm_zone) list;
588 char msg_format[512];
591 #define VMSTATE_MAX_MSG_ARRAY 256
593 /*! Voicemail mailbox state */
598 char curdir[PATH_MAX];
599 char vmbox[PATH_MAX];
601 char intro[PATH_MAX];
613 int updated; /*!< decremented on each mail check until 1 -allows delay */
614 long msgArray[VMSTATE_MAX_MSG_ARRAY];
615 MAILSTREAM *mailstream;
617 char imapuser[80]; /*!< IMAP server login */
619 char introfn[PATH_MAX]; /*!< Name of prepended file */
620 unsigned int quota_limit;
621 unsigned int quota_usage;
622 struct vm_state *persist_vms;
627 static char odbc_database[80];
628 static char odbc_table[80];
629 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
630 #define DISPOSE(a,b) remove_file(a,b)
631 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
632 #define EXISTS(a,b,c,d) (message_exists(a,b))
633 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
634 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
635 #define DELETE(a,b,c,d) (delete_file(a,b))
638 #define DISPOSE(a,b) (imap_remove_file(a,b))
639 #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))
640 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
641 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
642 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
643 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
644 #define DELETE(a,b,c,d) (vm_imap_delete(b,d))
646 #define RETRIEVE(a,b,c,d)
648 #define STORE(a,b,c,d,e,f,g,h,i,j)
649 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
650 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
651 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
652 #define DELETE(a,b,c,d) (vm_delete(c))
656 static char VM_SPOOL_DIR[PATH_MAX];
658 static char ext_pass_cmd[128];
659 static char ext_pass_check_cmd[128];
663 #define PWDCHANGE_INTERNAL (1 << 1)
664 #define PWDCHANGE_EXTERNAL (1 << 2)
665 static int pwdchange = PWDCHANGE_INTERNAL;
668 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
671 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
673 # define tdesc "Comedian Mail (Voicemail System)"
677 static char userscontext[AST_MAX_EXTENSION] = "default";
679 static char *addesc = "Comedian Mail";
681 /* Leave a message */
682 static char *app = "VoiceMail";
684 /* Check mail, control, etc */
685 static char *app2 = "VoiceMailMain";
687 static char *app3 = "MailboxExists";
688 static char *app4 = "VMAuthenticate";
690 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
691 static AST_LIST_HEAD_STATIC(zones, vm_zone);
692 static char zonetag[80];
693 static int maxsilence;
695 static int maxdeletedmsg;
696 static int silencethreshold = 128;
697 static char serveremail[80];
698 static char mailcmd[160]; /* Configurable mail cmd */
699 static char externnotify[160];
700 static struct ast_smdi_interface *smdi_iface = NULL;
701 static char vmfmts[80];
702 static double volgain;
703 static int vmminsecs;
704 static int vmmaxsecs;
707 static int maxlogins;
708 static int minpassword;
710 /*! Poll mailboxes for changes since there is something external to
711 * app_voicemail that may change them. */
712 static unsigned int poll_mailboxes;
714 /*! Polling frequency */
715 static unsigned int poll_freq;
716 /*! By default, poll every 30 seconds */
717 #define DEFAULT_POLL_FREQ 30
719 AST_MUTEX_DEFINE_STATIC(poll_lock);
720 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
721 static pthread_t poll_thread = AST_PTHREADT_NULL;
722 static unsigned char poll_thread_run;
724 /*! Subscription to ... MWI event subscriptions */
725 static struct ast_event_sub *mwi_sub_sub;
726 /*! Subscription to ... MWI event un-subscriptions */
727 static struct ast_event_sub *mwi_unsub_sub;
730 * \brief An MWI subscription
732 * This is so we can keep track of which mailboxes are subscribed to.
733 * This way, we know which mailboxes to poll when the pollmailboxes
734 * option is being used.
737 AST_RWLIST_ENTRY(mwi_sub) entry;
745 struct mwi_sub_task {
751 static struct ast_taskprocessor *mwi_subscription_tps;
753 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
755 /* custom audio control prompts for voicemail playback */
756 static char listen_control_forward_key[12];
757 static char listen_control_reverse_key[12];
758 static char listen_control_pause_key[12];
759 static char listen_control_restart_key[12];
760 static char listen_control_stop_key[12];
762 /* custom password sounds */
763 static char vm_password[80] = "vm-password";
764 static char vm_newpassword[80] = "vm-newpassword";
765 static char vm_passchanged[80] = "vm-passchanged";
766 static char vm_reenterpassword[80] = "vm-reenterpassword";
767 static char vm_mismatch[80] = "vm-mismatch";
768 static char vm_invalid_password[80] = "vm-invalid-password";
770 static struct ast_flags globalflags = {0};
772 static int saydurationminfo;
774 static char dialcontext[AST_MAX_CONTEXT] = "";
775 static char callcontext[AST_MAX_CONTEXT] = "";
776 static char exitcontext[AST_MAX_CONTEXT] = "";
778 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
781 static char *emailbody = NULL;
782 static char *emailsubject = NULL;
783 static char *pagerbody = NULL;
784 static char *pagersubject = NULL;
785 static char fromstring[100];
786 static char pagerfromstring[100];
787 static char charset[32] = "ISO-8859-1";
789 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
790 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
791 static int adsiver = 1;
792 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
794 /* Forward declarations - generic */
795 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
796 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);
797 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
798 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
799 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
800 signed char record_gain, struct vm_state *vms, char *flag);
801 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
802 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
803 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);
804 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, 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);
805 static void apply_options(struct ast_vm_user *vmu, const char *options);
806 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);
807 static int is_valid_dtmf(const char *key);
809 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
810 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
813 static char *strip_control(const char *input, char *buf, size_t buflen)
816 for (; *input; input++) {
821 if (bufptr == buf + buflen - 1) {
831 * \brief Sets default voicemail system options to a voicemail user.
833 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
834 * - all the globalflags
835 * - the saydurationminfo
839 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
842 static void populate_defaults(struct ast_vm_user *vmu)
844 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
845 if (saydurationminfo)
846 vmu->saydurationm = saydurationminfo;
847 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
848 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
849 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
850 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
852 vmu->maxsecs = vmmaxsecs;
854 vmu->maxmsg = maxmsg;
856 vmu->maxdeletedmsg = maxdeletedmsg;
857 vmu->volgain = volgain;
861 * \brief Sets a a specific property value.
862 * \param vmu The voicemail user object to work with.
863 * \param var The name of the property to be set.
864 * \param value The value to be set to the property.
866 * The property name must be one of the understood properties. See the source for details.
868 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
871 if (!strcasecmp(var, "attach")) {
872 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
873 } else if (!strcasecmp(var, "attachfmt")) {
874 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
875 } else if (!strcasecmp(var, "serveremail")) {
876 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
877 } else if (!strcasecmp(var, "language")) {
878 ast_copy_string(vmu->language, value, sizeof(vmu->language));
879 } else if (!strcasecmp(var, "tz")) {
880 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
882 } else if (!strcasecmp(var, "imapuser")) {
883 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
884 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
885 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
887 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
888 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
889 } else if (!strcasecmp(var, "saycid")){
890 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
891 } else if (!strcasecmp(var,"sendvoicemail")){
892 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
893 } else if (!strcasecmp(var, "review")){
894 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
895 } else if (!strcasecmp(var, "tempgreetwarn")){
896 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
897 } else if (!strcasecmp(var, "messagewrap")){
898 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
899 } else if (!strcasecmp(var, "operator")) {
900 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
901 } else if (!strcasecmp(var, "envelope")){
902 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
903 } else if (!strcasecmp(var, "moveheard")){
904 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
905 } else if (!strcasecmp(var, "sayduration")){
906 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
907 } else if (!strcasecmp(var, "saydurationm")){
908 if (sscanf(value, "%d", &x) == 1) {
909 vmu->saydurationm = x;
911 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
913 } else if (!strcasecmp(var, "forcename")){
914 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
915 } else if (!strcasecmp(var, "forcegreetings")){
916 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
917 } else if (!strcasecmp(var, "callback")) {
918 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
919 } else if (!strcasecmp(var, "dialout")) {
920 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
921 } else if (!strcasecmp(var, "exitcontext")) {
922 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
923 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
924 if (vmu->maxsecs <= 0) {
925 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
926 vmu->maxsecs = vmmaxsecs;
928 vmu->maxsecs = atoi(value);
930 if (!strcasecmp(var, "maxmessage"))
931 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
932 } else if (!strcasecmp(var, "maxmsg")) {
933 vmu->maxmsg = atoi(value);
934 if (vmu->maxmsg <= 0) {
935 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
936 vmu->maxmsg = MAXMSG;
937 } else if (vmu->maxmsg > MAXMSGLIMIT) {
938 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
939 vmu->maxmsg = MAXMSGLIMIT;
941 } else if (!strcasecmp(var, "backupdeleted")) {
942 if (sscanf(value, "%d", &x) == 1)
943 vmu->maxdeletedmsg = x;
944 else if (ast_true(value))
945 vmu->maxdeletedmsg = MAXMSG;
947 vmu->maxdeletedmsg = 0;
949 if (vmu->maxdeletedmsg < 0) {
950 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
951 vmu->maxdeletedmsg = MAXMSG;
952 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
953 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
954 vmu->maxdeletedmsg = MAXMSGLIMIT;
956 } else if (!strcasecmp(var, "volgain")) {
957 sscanf(value, "%lf", &vmu->volgain);
958 } else if (!strcasecmp(var, "options")) {
959 apply_options(vmu, value);
963 static char *vm_check_password_shell(char *command, char *buf, size_t len)
970 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
973 pid = ast_safe_fork(0);
979 snprintf(buf, len, "FAILURE: Fork failed");
983 if (read(fds[0], buf, len) < 0) {
984 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
989 AST_DECLARE_APP_ARGS(arg,
992 char *mycmd = ast_strdupa(command);
995 dup2(fds[1], STDOUT_FILENO);
997 ast_close_fds_above_n(STDOUT_FILENO);
999 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1001 execv(arg.v[0], arg.v);
1002 printf("FAILURE: %s", strerror(errno));
1010 * \brief Check that password meets minimum required length
1011 * \param vmu The voicemail user to change the password for.
1012 * \param password The password string to check
1014 * \return zero on ok, 1 on not ok.
1016 static int check_password(struct ast_vm_user *vmu, char *password)
1018 /* check minimum length */
1019 if (strlen(password) < minpassword)
1021 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1022 char cmd[255], buf[255];
1024 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1026 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1027 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1028 ast_debug(5, "Result: %s\n", buf);
1029 if (!strncasecmp(buf, "VALID", 5)) {
1030 ast_debug(3, "Passed password check: '%s'\n", buf);
1032 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1033 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1036 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1045 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1046 * \param vmu The voicemail user to change the password for.
1047 * \param password The new value to be set to the password for this user.
1049 * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
1050 * This is called from the (top level) vm_change_password.
1052 * \return zero on success, -1 on error.
1054 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1057 if (!ast_strlen_zero(vmu->uniqueid)) {
1058 if (strlen(password) > 10) {
1059 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1061 res = ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL);
1063 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1074 * \brief Destructively Parse options and apply.
1076 static void apply_options(struct ast_vm_user *vmu, const char *options)
1081 stringp = ast_strdupa(options);
1082 while ((s = strsep(&stringp, "|"))) {
1084 if ((var = strsep(&value, "=")) && value) {
1085 apply_option(vmu, var, value);
1091 * \brief Loads the options specific to a voicemail user.
1093 * This is called when a vm_user structure is being set up, such as from load_options.
1095 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1097 for (; var; var = var->next) {
1098 if (!strcasecmp(var->name, "vmsecret")) {
1099 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1100 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1101 if (ast_strlen_zero(retval->password))
1102 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1103 } else if (!strcasecmp(var->name, "uniqueid")) {
1104 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1105 } else if (!strcasecmp(var->name, "pager")) {
1106 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1107 } else if (!strcasecmp(var->name, "email")) {
1108 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1109 } else if (!strcasecmp(var->name, "fullname")) {
1110 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1111 } else if (!strcasecmp(var->name, "context")) {
1112 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1114 } else if (!strcasecmp(var->name, "imapuser")) {
1115 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1116 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1117 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1120 apply_option(retval, var->name, var->value);
1125 * \brief Determines if a DTMF key entered is valid.
1126 * \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.
1128 * Tests the character entered against the set of valid DTMF characters.
1129 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1131 static int is_valid_dtmf(const char *key)
1134 char *local_key = ast_strdupa(key);
1136 for (i = 0; i < strlen(key); ++i) {
1137 if (!strchr(VALID_DTMF, *local_key)) {
1138 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1147 * \brief Finds a voicemail user from the realtime engine.
1152 * 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.
1154 * \return The ast_vm_user structure for the user that was found.
1156 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1158 struct ast_variable *var;
1159 struct ast_vm_user *retval;
1161 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1163 ast_set_flag(retval, VM_ALLOCED);
1165 memset(retval, 0, sizeof(*retval));
1167 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1168 populate_defaults(retval);
1169 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1170 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1172 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1174 apply_options_full(retval, var);
1175 ast_variables_destroy(var);
1186 * \brief Finds a voicemail user from the users file or the realtime engine.
1191 * \return The ast_vm_user structure for the user that was found.
1193 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1195 /* This function could be made to generate one from a database, too */
1196 struct ast_vm_user *vmu=NULL, *cur;
1197 AST_LIST_LOCK(&users);
1199 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1200 context = "default";
1202 AST_LIST_TRAVERSE(&users, cur, list) {
1203 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1205 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1209 /* Make a copy, so that on a reload, we have no race */
1210 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1211 memcpy(vmu, cur, sizeof(*vmu));
1212 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1213 AST_LIST_NEXT(vmu, list) = NULL;
1216 vmu = find_user_realtime(ivm, context, mailbox);
1217 AST_LIST_UNLOCK(&users);
1222 * \brief Resets a user password to a specified password.
1227 * This does the actual change password work, called by the vm_change_password() function.
1229 * \return zero on success, -1 on error.
1231 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1233 /* This function could be made to generate one from a database, too */
1234 struct ast_vm_user *cur;
1236 AST_LIST_LOCK(&users);
1237 AST_LIST_TRAVERSE(&users, cur, list) {
1238 if ((!context || !strcasecmp(context, cur->context)) &&
1239 (!strcasecmp(mailbox, cur->mailbox)))
1243 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1246 AST_LIST_UNLOCK(&users);
1251 * \brief The handler for the change password option.
1252 * \param vmu The voicemail user to work with.
1253 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1254 * 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.
1255 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1257 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1259 struct ast_config *cfg=NULL;
1260 struct ast_variable *var=NULL;
1261 struct ast_category *cat=NULL;
1262 char *category=NULL, *value=NULL, *new=NULL;
1263 const char *tmp=NULL;
1264 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1265 if (!change_password_realtime(vmu, newpassword))
1268 /* check voicemail.conf */
1269 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1270 while ((category = ast_category_browse(cfg, category))) {
1271 if (!strcasecmp(category, vmu->context)) {
1272 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1273 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1276 value = strstr(tmp,",");
1278 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1281 new = alloca((strlen(value)+strlen(newpassword)+1));
1282 sprintf(new,"%s%s", newpassword, value);
1283 if (!(cat = ast_category_get(cfg, category))) {
1284 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1287 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1290 /* save the results */
1291 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1292 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1293 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1297 /* check users.conf and update the password stored for the mailbox*/
1298 /* if no vmsecret entry exists create one. */
1299 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1300 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1301 while ((category = ast_category_browse(cfg, category))) {
1302 ast_debug(4, "users.conf: %s\n", category);
1303 if (!strcasecmp(category, vmu->mailbox)) {
1304 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1305 ast_debug(3, "looks like we need to make vmsecret!\n");
1306 var = ast_variable_new("vmsecret", newpassword, "");
1308 new = alloca(strlen(newpassword)+1);
1309 sprintf(new, "%s", newpassword);
1310 if (!(cat = ast_category_get(cfg, category))) {
1311 ast_debug(4, "failed to get category!\n");
1315 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1317 ast_variable_append(cat, var);
1320 /* save the results and clean things up */
1321 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1322 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1323 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1327 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1330 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
1331 if (!ast_safe_system(buf)) {
1332 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1333 /* Reset the password in memory, too */
1334 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1339 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1340 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1341 * \param len The length of the path string that was written out.
1343 * The path is constructed as
1344 * VM_SPOOL_DIRcontext/ext/folder
1346 * \return zero on success, -1 on error.
1348 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1350 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1354 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1355 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1356 * \param len The length of the path string that was written out.
1358 * The path is constructed as
1359 * VM_SPOOL_DIRcontext/ext/folder
1361 * \return zero on success, -1 on error.
1363 static int make_file(char *dest, const int len, const char *dir, const int num)
1365 return snprintf(dest, len, "%s/msg%04d", dir, num);
1368 /* same as mkstemp, but return a FILE * */
1369 static FILE *vm_mkftemp(char *template)
1372 int pfd = mkstemp(template);
1373 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1375 p = fdopen(pfd, "w+");
1384 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1385 * \param dest String. base directory.
1386 * \param len Length of dest.
1387 * \param context String. Ignored if is null or empty string.
1388 * \param ext String. Ignored if is null or empty string.
1389 * \param folder String. Ignored if is null or empty string.
1390 * \return -1 on failure, 0 on success.
1392 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1394 mode_t mode = VOICEMAIL_DIR_MODE;
1397 make_dir(dest, len, context, ext, folder);
1398 if ((res = ast_mkdir(dest, mode))) {
1399 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1405 static const char *mbox(int id)
1407 static const char *msgs[] = {
1425 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
1428 static void free_user(struct ast_vm_user *vmu)
1430 if (ast_test_flag(vmu, VM_ALLOCED))
1434 /* All IMAP-specific functions should go in this block. This
1435 * keeps them from being spread out all over the code */
1437 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1440 struct vm_state *vms;
1441 unsigned long messageNum;
1443 /* Greetings aren't stored in IMAP, so we can't delete them there */
1448 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1449 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);
1453 /* find real message number based on msgnum */
1454 /* this may be an index into vms->msgArray based on the msgnum. */
1455 messageNum = vms->msgArray[msgnum];
1456 if (messageNum == 0) {
1457 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1460 if (option_debug > 2)
1461 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1462 /* delete message */
1463 snprintf (arg, sizeof(arg), "%lu",messageNum);
1464 mail_setflag (vms->mailstream,arg,"\\DELETED");
1467 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1469 struct vm_state *vms_p;
1470 char *file, *filename;
1475 /* This function is only used for retrieval of IMAP greetings
1476 * regular messages are not retrieved this way, nor are greetings
1477 * if they are stored locally*/
1478 if (msgnum > -1 || !imapgreetings) {
1481 file = strrchr(ast_strdupa(dir), '/');
1485 ast_debug (1, "Failed to procure file name from directory passed.\n");
1490 /* check if someone is accessing this box right now... */
1491 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) ||!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1492 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1496 /* Greetings will never have a prepended message */
1497 *vms_p->introfn = '\0';
1499 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1500 if (!vms_p->mailstream) {
1501 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1505 /*XXX Yuck, this could probably be done a lot better */
1506 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1507 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1508 /* We have the body, now we extract the file name of the first attachment. */
1509 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1510 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1512 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1515 filename = strsep(&attachment, ".");
1516 if (!strcmp(filename, file)) {
1517 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1518 vms_p->msgArray[vms_p->curmsg] = i + 1;
1519 save_body(body, vms_p, "2", attachment, 0);
1527 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1530 char *header_content;
1531 char *attachedfilefmt;
1533 struct vm_state *vms;
1534 char text_file[PATH_MAX];
1535 FILE *text_file_ptr;
1537 struct ast_vm_user *vmu;
1539 if (!(vmu = find_user(NULL, context, mailbox))) {
1540 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1545 if (imapgreetings) {
1546 res = imap_retrieve_greeting(dir, msgnum, vmu);
1554 /* Before anything can happen, we need a vm_state so that we can
1555 * actually access the imap server through the vms->mailstream
1557 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1558 /* This should not happen. If it does, then I guess we'd
1559 * need to create the vm_state, extract which mailbox to
1560 * open, and then set up the msgArray so that the correct
1561 * IMAP message could be accessed. If I have seen correctly
1562 * though, the vms should be obtainable from the vmstates list
1563 * and should have its msgArray properly set up.
1565 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1570 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1571 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1573 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1574 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1579 if (option_debug > 2)
1580 ast_log (LOG_DEBUG,"Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1581 if (vms->msgArray[msgnum] == 0) {
1582 ast_log (LOG_WARNING,"Trying to access unknown message\n");
1587 /* This will only work for new messages... */
1588 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1589 /* empty string means no valid header */
1590 if (ast_strlen_zero(header_content)) {
1591 ast_log (LOG_ERROR,"Could not fetch header for message number %ld\n",vms->msgArray[msgnum]);
1596 mail_fetchstructure (vms->mailstream,vms->msgArray[msgnum],&body);
1598 /* We have the body, now we extract the file name of the first attachment. */
1599 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1600 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1602 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1607 /* Find the format of the attached file */
1609 strsep(&attachedfilefmt, ".");
1610 if (!attachedfilefmt) {
1611 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1616 save_body(body, vms, "2", attachedfilefmt, 0);
1617 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1618 *vms->introfn = '\0';
1621 /* Get info from headers!! */
1622 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1624 if (!(text_file_ptr = fopen(text_file, "w"))) {
1625 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1628 fprintf(text_file_ptr, "%s\n", "[message]");
1630 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1631 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1632 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1633 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1634 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1635 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1636 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1637 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1638 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1639 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1640 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1641 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1642 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1643 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1644 fclose(text_file_ptr);
1651 static int folder_int(const char *folder)
1653 /*assume a NULL folder means INBOX*/
1657 if (!strcasecmp(folder, imapfolder))
1659 if (!strcasecmp(folder, "INBOX"))
1662 else if (!strcasecmp(folder, "Old"))
1664 else if (!strcasecmp(folder, "Work"))
1666 else if (!strcasecmp(folder, "Family"))
1668 else if (!strcasecmp(folder, "Friends"))
1670 else if (!strcasecmp(folder, "Cust1"))
1672 else if (!strcasecmp(folder, "Cust2"))
1674 else if (!strcasecmp(folder, "Cust3"))
1676 else if (!strcasecmp(folder, "Cust4"))
1678 else if (!strcasecmp(folder, "Cust5"))
1680 else /*assume they meant INBOX if folder is not found otherwise*/
1685 * \brief Gets the number of messages that exist in a mailbox folder.
1690 * This method is used when IMAP backend is used.
1691 * \return The number of messages in this mailbox folder (zero or more).
1693 static int messagecount(const char *context, const char *mailbox, const char *folder)
1698 struct ast_vm_user *vmu, vmus;
1699 struct vm_state *vms_p;
1701 int fold = folder_int(folder);
1704 if (ast_strlen_zero(mailbox))
1707 /* We have to get the user before we can open the stream! */
1708 vmu = find_user(&vmus, context, mailbox);
1710 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1713 /* No IMAP account available */
1714 if (vmu->imapuser[0] == '\0') {
1715 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1720 /* No IMAP account available */
1721 if (vmu->imapuser[0] == '\0') {
1722 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1727 /* check if someone is accessing this box right now... */
1728 vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
1730 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1733 ast_debug(3, "Returning before search - user is logged in\n");
1734 if (fold == 0) { /* INBOX */
1735 return vms_p->newmessages;
1737 if (fold == 1) { /* Old messages */
1738 return vms_p->oldmessages;
1740 if (fold == 11) {/*Urgent messages*/
1741 return vms_p->urgentmessages;
1745 /* add one if not there... */
1746 vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
1748 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1751 /* If URGENT, then look at INBOX */
1758 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
1759 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
1762 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
1763 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
1764 vms_p->mailstream = NIL; /* save for access from interactive entry point */
1765 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
1767 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
1768 init_vm_state(vms_p);
1769 vmstate_insert(vms_p);
1771 ret = init_mailstream(vms_p, fold);
1772 if (!vms_p->mailstream) {
1773 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1777 pgm = mail_newsearchpgm ();
1778 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
1784 /* In the special case where fold is 1 (old messages) we have to do things a bit
1785 * differently. Old messages are stored in the INBOX but are marked as "seen"
1791 /* look for urgent messages */
1799 vms_p->vmArrayIndex = 0;
1800 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1801 if (fold == 0 && urgent == 0)
1802 vms_p->newmessages = vms_p->vmArrayIndex;
1804 vms_p->oldmessages = vms_p->vmArrayIndex;
1805 if (fold == 0 && urgent == 1)
1806 vms_p->urgentmessages = vms_p->vmArrayIndex;
1807 /*Freeing the searchpgm also frees the searchhdr*/
1808 mail_free_searchpgm(&pgm);
1810 return vms_p->vmArrayIndex;
1812 mail_ping(vms_p->mailstream);
1817 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)
1819 char *myserveremail = serveremail;
1821 char introfn[PATH_MAX];
1825 char tmp[80] = "/tmp/astmail-XXXXXX";
1830 int ret; /* for better error checking */
1831 char *imap_flags = NIL;
1833 /* Set urgent flag for IMAP message */
1834 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1835 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1836 imap_flags="\\FLAGGED";
1839 /* Attach only the first format */
1840 fmt = ast_strdupa(fmt);
1842 strsep(&stringp, "|");
1844 if (!ast_strlen_zero(vmu->serveremail))
1845 myserveremail = vmu->serveremail;
1848 make_file(fn, sizeof(fn), dir, msgnum);
1850 ast_copy_string (fn, dir, sizeof(fn));
1852 snprintf(introfn, sizeof(introfn), "%sintro", fn);
1853 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1857 if (ast_strlen_zero(vmu->email)) {
1858 /* We need the vmu->email to be set when we call make_email_file, but
1859 * if we keep it set, a duplicate e-mail will be created. So at the end
1860 * of this function, we will revert back to an empty string if tempcopy
1863 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1867 if (!strcmp(fmt, "wav49"))
1869 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1871 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1873 if (!(p = vm_mkftemp(tmp))) {
1874 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1876 *(vmu->email) = '\0';
1880 if (msgnum < 0 && imapgreetings) {
1881 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1882 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1885 imap_delete_old_greeting(fn, vms);
1888 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
1889 /* read mail file to memory */
1892 if (!(buf = ast_malloc(len + 1))) {
1893 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1896 *(vmu->email) = '\0';
1899 if (fread(buf, len, 1, p) < len) {
1901 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
1905 ((char *)buf)[len] = '\0';
1906 INIT(&str, mail_string, buf, len);
1907 ret = init_mailstream(vms, NEW_FOLDER);
1909 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1910 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1911 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1916 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n",mailbox);
1922 ast_debug(3, "%s stored\n", fn);
1925 *(vmu->email) = '\0';
1932 * \brief Gets the number of messages that exist in the inbox folder.
1933 * \param mailbox_context
1934 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1935 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1936 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1938 * This method is used when IMAP backend is used.
1939 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1941 * \return zero on success, -1 on error.
1944 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1946 char tmp[PATH_MAX] = "";
1958 ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
1959 /* If no mailbox, return immediately */
1960 if (ast_strlen_zero(mailbox_context))
1963 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1964 context = strchr(tmp, '@');
1965 if (strchr(mailbox_context, ',')) {
1966 int tmpnew, tmpold, tmpurgent;
1967 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1969 while ((cur = strsep(&mb, ", "))) {
1970 if (!ast_strlen_zero(cur)) {
1971 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
1979 *urgentmsgs += tmpurgent;
1990 context = "default";
1991 mailboxnc = (char *)mailbox_context;
1994 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
1998 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2002 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
2008 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2010 return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
2014 * \brief Determines if the given folder has messages.
2015 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2016 * \param folder the folder to look in
2018 * This function is used when the mailbox is stored in an IMAP back end.
2019 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2020 * \return 1 if the folder has one or more messages. zero otherwise.
2023 static int has_voicemail(const char *mailbox, const char *folder)
2025 char tmp[256], *tmp2, *box, *context;
2026 ast_copy_string(tmp, mailbox, sizeof(tmp));
2028 if (strchr(tmp2, ',')) {
2029 while ((box = strsep(&tmp2, ","))) {
2030 if (!ast_strlen_zero(box)) {
2031 if (has_voicemail(box, folder))
2036 if ((context= strchr(tmp, '@')))
2039 context = "default";
2040 return messagecount(context, tmp, folder) ? 1 : 0;
2044 * \brief Copies a message from one mailbox to another.
2054 * This works with IMAP storage based mailboxes.
2056 * \return zero on success, -1 on error.
2058 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)
2060 struct vm_state *sendvms = NULL, *destvms = NULL;
2061 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2062 if (msgnum >= recip->maxmsg) {
2063 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2066 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2067 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2070 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2071 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2074 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2075 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
2077 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2081 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2083 char tmp[256], *t = tmp;
2084 size_t left = sizeof(tmp);
2086 if (box == OLD_FOLDER) {
2087 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
2089 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
2092 if (box == NEW_FOLDER) {
2093 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2095 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
2098 /* Build up server information */
2099 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2101 /* Add authentication user if present */
2102 if (!ast_strlen_zero(authuser))
2103 ast_build_string(&t, &left, "/authuser=%s", authuser);
2105 /* Add flags if present */
2106 if (!ast_strlen_zero(imapflags))
2107 ast_build_string(&t, &left, "/%s", imapflags);
2109 /* End with username */
2110 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2111 if (box == NEW_FOLDER || box == OLD_FOLDER)
2112 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
2113 else if (box == GREETINGS_FOLDER)
2114 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2115 else { /* Other folders such as Friends, Family, etc... */
2116 if (!ast_strlen_zero(imapparentfolder)) {
2117 /* imapparentfolder would typically be set to INBOX */
2118 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2120 snprintf(spec, len, "%s%s", tmp, mbox(box));
2125 static int init_mailstream(struct vm_state *vms, int box)
2127 MAILSTREAM *stream = NIL;
2132 ast_log (LOG_ERROR,"vm_state is NULL!\n");
2135 if (option_debug > 2)
2136 ast_log (LOG_DEBUG,"vm_state user is:%s\n",vms->imapuser);
2137 if (vms->mailstream == NIL || !vms->mailstream) {
2139 ast_log (LOG_DEBUG,"mailstream not set.\n");
2141 stream = vms->mailstream;
2143 /* debug = T; user wants protocol telemetry? */
2144 debug = NIL; /* NO protocol telemetry? */
2146 if (delimiter == '\0') { /* did not probe the server yet */
2148 #ifdef USE_SYSTEM_IMAP
2149 #include <imap/linkage.c>
2150 #elif defined(USE_SYSTEM_CCLIENT)
2151 #include <c-client/linkage.c>
2153 #include "linkage.c"
2155 /* Connect to INBOX first to get folders delimiter */
2156 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2157 ast_mutex_lock(&vms->lock);
2158 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2159 ast_mutex_unlock(&vms->lock);
2160 if (stream == NIL) {
2161 ast_log (LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2164 get_mailbox_delimiter(stream);
2165 /* update delimiter in imapfolder */
2166 for (cp = imapfolder; *cp; cp++)
2170 /* Now connect to the target folder */
2171 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2172 if (option_debug > 2)
2173 ast_log (LOG_DEBUG,"Before mail_open, server: %s, box:%d\n", tmp, box);
2174 ast_mutex_lock(&vms->lock);
2175 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2176 ast_mutex_unlock(&vms->lock);
2177 if (vms->mailstream == NIL) {
2184 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2188 int ret, urgent = 0;
2190 /* If Urgent, then look at INBOX */
2196 ast_copy_string(vms->imapuser,vmu->imapuser, sizeof(vms->imapuser));
2197 ast_debug(3,"Before init_mailstream, user is %s\n",vmu->imapuser);
2199 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2200 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2204 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2208 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2209 check_quota(vms,(char *)mbox(box));
2212 pgm = mail_newsearchpgm();
2214 /* Check IMAP folder for Asterisk messages only... */
2215 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", vmu->mailbox);
2220 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2221 if (box == NEW_FOLDER && urgent == 1) {
2226 } else if (box == NEW_FOLDER && urgent == 0) {
2231 } else if (box == OLD_FOLDER) {
2236 ast_debug(3,"Before mail_search_full, user is %s\n",vmu->imapuser);
2238 vms->vmArrayIndex = 0;
2239 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2240 vms->lastmsg = vms->vmArrayIndex - 1;
2241 mail_free_searchpgm(&pgm);
2246 static void write_file(char *filename, char *buffer, unsigned long len)
2250 output = fopen (filename, "w");
2251 if (fwrite(buffer, len, 1, output) < len) {
2252 if (ferror(output)) {
2253 ast_log(LOG_ERROR, "Short write while writing e-mail body.\n");
2259 static void update_messages_by_imapuser(const char *user, unsigned long number)
2261 struct vmstate *vlist = NULL;
2263 AST_LIST_LOCK(&vmstates);
2264 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2266 ast_debug(3, "error: vms is NULL for %s\n", user);
2269 if (!vlist->vms->imapuser) {
2270 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2273 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vlist->vms->vmArrayIndex, vlist->vms->interactive);
2274 vlist->vms->msgArray[vlist->vms->vmArrayIndex++] = number;
2276 AST_LIST_UNLOCK(&vmstates);
2279 void mm_searched(MAILSTREAM *stream, unsigned long number)
2281 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2283 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2286 update_messages_by_imapuser(user, number);
2289 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2291 struct ast_variable *var;
2292 struct ast_vm_user *vmu;
2294 vmu = ast_calloc(1, sizeof *vmu);
2297 ast_set_flag(vmu, VM_ALLOCED);
2298 populate_defaults(vmu);
2300 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2302 apply_options_full(vmu, var);
2303 ast_variables_destroy(var);
2311 /* Interfaces to C-client */
2313 void mm_exists(MAILSTREAM * stream, unsigned long number)
2315 /* mail_ping will callback here if new mail! */
2316 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2317 if (number == 0) return;
2322 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2324 /* mail_ping will callback here if expunged mail! */
2325 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2326 if (number == 0) return;
2331 void mm_flags(MAILSTREAM * stream, unsigned long number)
2333 /* mail_ping will callback here if read mail! */
2334 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2335 if (number == 0) return;
2340 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2342 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2343 mm_log (string, errflg);
2347 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2349 if (delimiter == '\0') {
2353 ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2354 if (attributes & LATT_NOINFERIORS)
2355 ast_debug(5, "no inferiors\n");
2356 if (attributes & LATT_NOSELECT)
2357 ast_debug(5, "no select\n");
2358 if (attributes & LATT_MARKED)
2359 ast_debug(5, "marked\n");
2360 if (attributes & LATT_UNMARKED)
2361 ast_debug(5, "unmarked\n");
2365 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2367 ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2368 if (attributes & LATT_NOINFERIORS)
2369 ast_debug(5, "no inferiors\n");
2370 if (attributes & LATT_NOSELECT)
2371 ast_debug(5, "no select\n");
2372 if (attributes & LATT_MARKED)
2373 ast_debug(5, "marked\n");
2374 if (attributes & LATT_UNMARKED)
2375 ast_debug(5, "unmarked\n");
2379 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2381 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2382 if (status->flags & SA_MESSAGES)
2383 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2384 if (status->flags & SA_RECENT)
2385 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2386 if (status->flags & SA_UNSEEN)
2387 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2388 if (status->flags & SA_UIDVALIDITY)
2389 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2390 if (status->flags & SA_UIDNEXT)
2391 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2392 ast_log(AST_LOG_NOTICE, "\n");
2396 void mm_log(char *string, long errflg)
2398 switch ((short) errflg) {
2400 ast_debug(1,"IMAP Info: %s\n", string);
2404 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2407 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2413 void mm_dlog(char *string)
2415 ast_log(AST_LOG_NOTICE, "%s\n", string);
2419 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2421 struct ast_vm_user *vmu;
2423 ast_debug(4, "Entering callback mm_login\n");
2425 ast_copy_string(user, mb->user, MAILTMPLEN);
2427 /* We should only do this when necessary */
2428 if (!ast_strlen_zero(authpassword)) {
2429 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2431 AST_LIST_TRAVERSE(&users, vmu, list) {
2432 if (!strcasecmp(mb->user, vmu->imapuser)) {
2433 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2438 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2439 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2447 void mm_critical(MAILSTREAM * stream)
2452 void mm_nocritical(MAILSTREAM * stream)
2457 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2459 kill (getpid (), SIGSTOP);
2464 void mm_fatal(char *string)
2466 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2469 /* C-client callback to handle quota */
2470 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2472 struct vm_state *vms;
2473 char *mailbox = stream->mailbox, *user;
2474 char buf[1024] = "";
2475 unsigned long usage = 0, limit = 0;
2478 usage = pquota->usage;
2479 limit = pquota->limit;
2480 pquota = pquota->next;
2483 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 2))) {
2484 ast_log(AST_LOG_ERROR, "No state found.\n");
2488 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2490 vms->quota_usage = usage;
2491 vms->quota_limit = limit;
2494 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2496 char *start, *eol_pnt;
2499 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2502 taglen = strlen(tag) + 1;
2506 if (!(start = strstr(header, tag)))
2509 /* Since we can be called multiple times we should clear our buffer */
2510 memset(buf, 0, len);
2512 ast_copy_string(buf, start+taglen, len);
2513 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2518 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2520 char *start, *quote, *eol_pnt;
2522 if (ast_strlen_zero(mailbox))
2525 if (!(start = strstr(mailbox, "/user=")))
2528 ast_copy_string(buf, start+6, len);
2530 if (!(quote = strchr(buf, '\"'))) {
2531 if (!(eol_pnt = strchr(buf, '/')))
2532 eol_pnt = strchr(buf,'}');
2536 eol_pnt = strchr(buf+1,'\"');
2542 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2544 struct vm_state *vms_p;
2546 if (option_debug > 4)
2547 ast_log(AST_LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
2548 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2550 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2551 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2552 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2553 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2554 if (option_debug > 4)
2555 ast_log(AST_LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2557 /* set mailbox to INBOX! */
2558 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2559 init_vm_state(vms_p);
2560 vmstate_insert(vms_p);
2564 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive)
2566 struct vmstate *vlist = NULL;
2568 AST_LIST_LOCK(&vmstates);
2569 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2571 ast_debug(3, "error: vms is NULL for %s\n", user);
2574 if (!vlist->vms->imapuser) {
2575 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2579 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2580 AST_LIST_UNLOCK(&vmstates);
2584 AST_LIST_UNLOCK(&vmstates);
2586 ast_debug(3, "%s not found in vmstates\n", user);
2591 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2594 struct vmstate *vlist = NULL;
2595 const char *local_context = S_OR(context, "default");
2597 AST_LIST_LOCK(&vmstates);
2598 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2600 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2603 if (!vlist->vms->username || !vlist->vms->context) {
2604 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2608 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);
2610 if (!strcmp(vlist->vms->username,mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2611 ast_debug(3, "Found it!\n");
2612 AST_LIST_UNLOCK(&vmstates);
2616 AST_LIST_UNLOCK(&vmstates);
2618 ast_debug(3, "%s not found in vmstates\n", mailbox);
2623 static void vmstate_insert(struct vm_state *vms)
2626 struct vm_state *altvms;
2628 /* If interactive, it probably already exists, and we should
2629 use the one we already have since it is more up to date.
2630 We can compare the username to find the duplicate */
2631 if (vms->interactive == 1) {
2632 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2634 ast_debug(3, "Duplicate mailbox %s, copying message info...\n",vms->username);
2635 vms->newmessages = altvms->newmessages;
2636 vms->oldmessages = altvms->oldmessages;
2637 vms->vmArrayIndex = altvms->vmArrayIndex;
2638 vms->lastmsg = altvms->lastmsg;
2639 vms->curmsg = altvms->curmsg;
2640 /* get a pointer to the persistent store */
2641 vms->persist_vms = altvms;
2642 /* Reuse the mailstream? */
2643 vms->mailstream = altvms->mailstream;
2644 /* vms->mailstream = NIL; */
2648 if (!(v = ast_calloc(1, sizeof(*v))))
2653 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
2655 AST_LIST_LOCK(&vmstates);
2656 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2657 AST_LIST_UNLOCK(&vmstates);
2660 static void vmstate_delete(struct vm_state *vms)
2662 struct vmstate *vc = NULL;
2663 struct vm_state *altvms = NULL;
2665 /* If interactive, we should copy pertinent info
2666 back to the persistent state (to make update immediate) */
2667 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2668 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2669 altvms->newmessages = vms->newmessages;
2670 altvms->oldmessages = vms->oldmessages;
2671 altvms->updated = 1;
2674 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2676 AST_LIST_LOCK(&vmstates);
2677 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2678 if (vc->vms == vms) {
2679 AST_LIST_REMOVE_CURRENT(list);
2683 AST_LIST_TRAVERSE_SAFE_END
2684 AST_LIST_UNLOCK(&vmstates);
2687 ast_mutex_destroy(&vc->vms->lock);
2691 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2694 static void set_update(MAILSTREAM * stream)
2696 struct vm_state *vms;
2697 char *mailbox = stream->mailbox, *user;
2698 char buf[1024] = "";
2700 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2701 if (user && option_debug > 2)
2702 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2706 ast_debug(3, "User %s mailbox set for update.\n", user);
2708 vms->updated = 1; /* Set updated flag since mailbox changed */
2711 static void init_vm_state(struct vm_state *vms)
2714 vms->vmArrayIndex = 0;
2715 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2716 vms->msgArray[x] = 0;
2718 ast_mutex_init(&vms->lock);
2721 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
2725 char *fn = is_intro ? vms->introfn : vms->fn;
2727 unsigned long newlen;
2730 if (!body || body == NIL)
2733 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2734 if (body_content != NIL) {
2735 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2736 /* ast_debug(1,body_content); */
2737 body_decoded = rfc822_base64((unsigned char *)body_content, len, &newlen);
2738 /* If the body of the file is empty, return an error */
2742 write_file(filename, (char *) body_decoded, newlen);
2744 ast_debug(5, "Body of message is NULL.\n");
2751 * \brief Get delimiter via mm_list callback
2754 * Determines the delimiter character that is used by the underlying IMAP based mail store.
2756 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2758 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2759 mail_list(stream, tmp, "*");
2763 * \brief Check Quota for user
2764 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2765 * \param mailbox the mailbox to check the quota for.
2767 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2769 static void check_quota(struct vm_state *vms, char *mailbox) {
2770 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2771 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2772 if (vms && vms->mailstream != NULL) {
2773 imap_getquotaroot(vms->mailstream, mailbox);
2775 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2779 #endif /* IMAP_STORAGE */
2781 /*! \brief Lock file path
2782 only return failure if ast_lock_path returns 'timeout',
2783 not if the path does not exist or any other reason
2785 static int vm_lock_path(const char *path)
2787 switch (ast_lock_path(path)) {
2788 case AST_LOCK_TIMEOUT:
2797 struct generic_prepare_struct {
2803 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2805 struct generic_prepare_struct *gps = data;
2809 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2810 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2811 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2814 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
2815 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2816 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2817 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2820 for (i = 0; i < gps->argc; i++)
2821 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2827 * \brief Retrieves a file from an ODBC data store.
2828 * \param dir the path to the file to be retreived.
2829 * \param msgnum the message number, such as within a mailbox folder.
2831 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2832 * 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.
2834 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2835 * The output is the message information file with the name msgnum and the extension .txt
2836 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2838 * \return 0 on success, -1 on error.
2840 static int retrieve_file(char *dir, int msgnum)
2846 void *fdm = MAP_FAILED;
2847 SQLSMALLINT colcount=0;
2854 SQLSMALLINT datatype;
2855 SQLSMALLINT decimaldigits;
2856 SQLSMALLINT nullable;
2862 char full_fn[PATH_MAX];
2864 char *argv[] = { dir, msgnums };
2865 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2867 struct odbc_obj *obj;
2868 obj = ast_odbc_request_obj(odbc_database, 0);
2870 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2871 c = strchr(fmt, '|');
2874 if (!strcasecmp(fmt, "wav49"))
2876 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
2878 make_file(fn, sizeof(fn), dir, msgnum);
2880 ast_copy_string(fn, dir, sizeof(fn));
2882 /* Create the information file */
2883 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2885 if (!(f = fopen(full_fn, "w+"))) {
2886 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2890 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2891 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2892 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2894 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2895 ast_odbc_release_obj(obj);
2898 res = SQLFetch(stmt);
2899 if (res == SQL_NO_DATA) {
2900 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2901 ast_odbc_release_obj(obj);
2903 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2904 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2905 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2906 ast_odbc_release_obj(obj);
2909 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2911 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2912 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2913 ast_odbc_release_obj(obj);
2916 res = SQLNumResultCols(stmt, &colcount);
2917 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2918 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2919 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2920 ast_odbc_release_obj(obj);
2924 fprintf(f, "[message]\n");
2925 for (x=0;x<colcount;x++) {
2927 collen = sizeof(coltitle);
2928 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
2929 &datatype, &colsize, &decimaldigits, &nullable);
2930 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2931 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
2932 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2933 ast_odbc_release_obj(obj);
2936 if (!strcasecmp(coltitle, "recording")) {
2938 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
2942 lseek(fd, fdlen - 1, SEEK_SET);
2943 if (write(fd, tmp, 1) != 1) {
2948 /* Read out in small chunks */
2949 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
2950 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
2951 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
2952 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2953 ast_odbc_release_obj(obj);
2956 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
2957 munmap(fdm, CHUNKSIZE);
2958 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2959 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2961 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2962 ast_odbc_release_obj(obj);
2967 if (truncate(full_fn, fdlen) < 0) {
2968 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
2972 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2973 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2974 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
2975 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2976 ast_odbc_release_obj(obj);
2979 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
2980 fprintf(f, "%s=%s\n", coltitle, rowdata);
2983 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2984 ast_odbc_release_obj(obj);
2986 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2996 * \brief Determines the highest message number in use for a given user and mailbox folder.
2998 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3000 * This method is used when mailboxes are stored in an ODBC back end.
3001 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3003 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3005 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3012 char *argv[] = { dir };
3013 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3015 struct odbc_obj *obj;
3016 obj = ast_odbc_request_obj(odbc_database, 0);
3018 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
3019 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3021 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3022 ast_odbc_release_obj(obj);
3025 res = SQLFetch(stmt);
3026 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3027 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3028 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3029 ast_odbc_release_obj(obj);
3032 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3033 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3034 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3035 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3036 ast_odbc_release_obj(obj);
3039 if (sscanf(rowdata, "%d", &x) != 1)
3040 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3041 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3042 ast_odbc_release_obj(obj);
3044 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3050 * \brief Determines if the specified message exists.
3051 * \param dir the folder the mailbox folder to look for messages.
3052 * \param msgnum the message index to query for.
3054 * This method is used when mailboxes are stored in an ODBC back end.
3056 * \return greater than zero if the message exists, zero when the message does not exist or on error.
3058 static int message_exists(char *dir, int msgnum)
3066 char *argv[] = { dir, msgnums };
3067 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3069 struct odbc_obj *obj;
3070 obj = ast_odbc_request_obj(odbc_database, 0);
3072 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3073 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3074 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3076 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3077 ast_odbc_release_obj(obj);
3080 res = SQLFetch(stmt);
3081 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3082 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3083 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3084 ast_odbc_release_obj(obj);
3087 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3088 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3089 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3090 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3091 ast_odbc_release_obj(obj);
3094 if (sscanf(rowdata, "%d", &x) != 1)
3095 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3096 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3097 ast_odbc_release_obj(obj);
3099 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3105 * \brief returns the one-based count for messages.
3107 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3109 * This method is used when mailboxes are stored in an ODBC back end.
3110 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3111 * one-based messages.
3112 * This method just calls last_message_index and returns +1 of its value.
3114 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3116 static int count_messages(struct ast_vm_user *vmu, char *dir)
3118 return last_message_index(vmu, dir) + 1;
3122 * \brief Deletes a message from the mailbox folder.
3123 * \param sdir The mailbox folder to work in.
3124 * \param smsg The message index to be deleted.
3126 * This method is used when mailboxes are stored in an ODBC back end.
3127 * The specified message is directly deleted from the database 'voicemessages' table.
3129 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3131 static void delete_file(char *sdir, int smsg)
3136 char *argv[] = { sdir, msgnums };
3137 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3139 struct odbc_obj *obj;
3140 obj = ast_odbc_request_obj(odbc_database, 0);
3142 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3143 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3144 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3146 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3148 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3149 ast_odbc_release_obj(obj);
3151 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3156 * \brief Copies a voicemail from one mailbox to another.
3157 * \param sdir the folder for which to look for the message to be copied.
3158 * \param smsg the index of the message to be copied.
3159 * \param ddir the destination folder to copy the message into.
3160 * \param dmsg the index to be used for the copied message.
3161 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3162 * \param dmailboxcontext The context for the destination user.
3164 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3166 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3172 struct odbc_obj *obj;
3173 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3174 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3176 delete_file(ddir, dmsg);
3177 obj = ast_odbc_request_obj(odbc_database, 0);
3179 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3180 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3181 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, mailboxuser, mailboxcontext, flag) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?",odbc_table,odbc_table);
3182 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3184 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3186 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3187 ast_odbc_release_obj(obj);
3189 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3193 struct insert_data {
3200 const char *context;
3201 const char *macrocontext;
3202 const char *callerid;
3203 const char *origtime;
3204 const char *duration;
3206 char *mailboxcontext;
3207 const char *category;
3211 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3213 struct insert_data *data = vdata;
3217 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3218 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3219 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3220 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3224 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *)data->dir, 0, NULL);
3225 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *)data->msgnums, 0, NULL);
3226 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *)data->data, data->datalen, &data->indlen);
3227 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *)data->context, 0, NULL);
3228 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *)data->macrocontext, 0, NULL);
3229 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *)data->callerid, 0, NULL);
3230 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *)data->origtime, 0, NULL);
3231 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *)data->duration, 0, NULL);
3232 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *)data->mailboxuser, 0, NULL);
3233 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *)data->mailboxcontext, 0, NULL);
3234 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *)data->flag, 0, NULL);
3235 if (!ast_strlen_zero(data->category)) {
3236 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *)data->category, 0, NULL);
3238 res = SQLExecDirect(stmt, (unsigned char *)data->sql, SQL_NTS);
3239 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3240 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3241 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3249 * \brief Stores a voicemail into the database.
3250 * \param dir the folder the mailbox folder to store the message.
3251 * \param mailboxuser the user owning the mailbox folder.
3252 * \param mailboxcontext
3253 * \param msgnum the message index for the message to be stored.
3255 * This method is used when mailboxes are stored in an ODBC back end.
3256 * The message sound file and information file is looked up on the file system.
3257 * A SQL query is invoked to store the message into the (MySQL) database.
3259 * \return the zero on success -1 on error.
3261 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3265 void *fdm = MAP_FAILED;
3271 char full_fn[PATH_MAX];
3274 struct ast_config *cfg=NULL;
3275 struct odbc_obj *obj;
3276 struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext };
3277 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3279 delete_file(dir, msgnum);
3280 if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3281 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3286 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3287 c = strchr(fmt, '|');
3290 if (!strcasecmp(fmt, "wav49"))
3292 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
3294 make_file(fn, sizeof(fn), dir, msgnum);
3296 ast_copy_string(fn, dir, sizeof(fn));
3297 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3298 cfg = ast_config_load(full_fn, config_flags);
3299 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3300 fd = open(full_fn, O_RDWR);
3302 ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3306 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
3307 if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3310 if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3311 idata.macrocontext = "";
3313 if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
3314 idata.callerid = "";
3316 if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
3317 idata.origtime = "";
3319 if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
3320 idata.duration = "";
3322 if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
3323 idata.category = "";
3325 if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
3329 fdlen = lseek(fd, 0, SEEK_END);
3330 lseek(fd, 0, SEEK_SET);
3331 printf("Length is %zd\n", fdlen);
3332 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
3333 if (fdm == MAP_FAILED) {
3334 ast_log(AST_LOG_WARNING, "Memory map failed!\n");
3339 idata.datalen = idata.indlen = fdlen;
3341 if (!ast_strlen_zero(idata.category))
3342 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
3344 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
3346 if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
3347 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3349 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3354 ast_odbc_release_obj(obj);
3357 ast_config_destroy(cfg);
3358 if (fdm != MAP_FAILED)
3366 * \brief Renames a message in a mailbox folder.
3367 * \param sdir The folder of the message to be renamed.
3368 * \param smsg The index of the message to be renamed.
3369 * \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.
3370 * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
3371 * \param ddir The destination folder for the message to be renamed into
3372 * \param dmsg The destination message for the message to be renamed.
3374 * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
3375 * 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.
3376 * 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.
3378 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
3384 struct odbc_obj *obj;
3385 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
3386 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3388 delete_file(ddir, dmsg);
3389 obj = ast_odbc_request_obj(odbc_database, 0);
3391 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3392 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3393 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
3394 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3396 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3398 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3399 ast_odbc_release_obj(obj);
3401 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3406 * \brief Removes a voicemail message file.
3407 * \param dir the path to the message file.
3408 * \param msgnum the unique number for the message within the mailbox.
3410 * Removes the message content file and the information file.
3411 * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
3412 * Typical use is to clean up after a RETRIEVE operation.
3413 * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
3414 * \return zero on success, -1 on error.
3416 static int remove_file(char *dir, int msgnum)
3419 char full_fn[PATH_MAX];
3423 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3424 make_file(fn, sizeof(fn), dir, msgnum);
3426 ast_copy_string(fn, dir, sizeof(fn));
3427 ast_filedelete(fn, NULL);
3428 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3433 #ifndef IMAP_STORAGE
3435 * \brief Find all .txt files - even if they are not in sequence from 0000.
3439 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3441 * \return the count of messages, zero or more.
3443 static int count_messages(struct ast_vm_user *vmu, char *dir)
3448 struct dirent *vment = NULL;
3450 if (vm_lock_path(dir))
3451 return ERROR_LOCK_PATH;
3453 if ((vmdir = opendir(dir))) {
3454 while ((vment = readdir(vmdir))) {
3455 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
3461 ast_unlock_path(dir);
3467 * \brief Renames a message in a mailbox folder.
3468 * \param sfn The path to the mailbox information and data file to be renamed.
3469 * \param dfn The path for where the message data and information files will be renamed to.
3471 * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3473 static void rename_file(char *sfn, char *dfn)
3475 char stxt[PATH_MAX];
3476 char dtxt[PATH_MAX];
3477 ast_filerename(sfn,dfn,NULL);
3478 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
3479 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
3480 if (ast_check_realtime("voicemail_data")) {
3481 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
3487 * \brief Determines the highest message number in use for a given user and mailbox folder.
3489 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3491 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3492 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3494 * \note Should always be called with a lock already set on dir.
3495 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3497 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3500 unsigned char map[MAXMSGLIMIT] = "";
3502 struct dirent *msgdirent;
3505 /* Reading the entire directory into a file map scales better than
3506 * doing a stat repeatedly on a predicted sequence. I suspect this
3507 * is partially due to stat(2) internally doing a readdir(2) itself to
3508 * find each file. */
3509 msgdir = opendir(dir);
3510 while ((msgdirent = readdir(msgdir))) {
3511 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
3516 for (x = 0; x < vmu->maxmsg; x++) {
3524 #endif /* #ifndef IMAP_STORAGE */
3525 #endif /* #else of #ifdef ODBC_STORAGE */
3526 #ifndef IMAP_STORAGE
3528 * \brief Utility function to copy a file.
3529 * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
3530 * \param outfile The path for which to copy the file to. The directory permissions must allow the creation (or truncation) of the file, and allow for opening the file in write only mode.
3532 * When the compiler option HARDLINK_WHEN_POSSIBLE is set, the copy operation will attempt to use the hard link facility instead of copy the file (to save disk space). If the link operation fails, it falls back to the copy operation.
3533 * The copy operation copies up to 4096 bytes at once.
3535 * \return zero on success, -1 on error.
3537 static int copy(char *infile, char *outfile)
3545 #ifdef HARDLINK_WHEN_POSSIBLE
3546 /* Hard link if possible; saves disk space & is faster */
3547 if (link(infile, outfile)) {
3549 if ((ifd = open(infile, O_RDONLY)) < 0) {
3550 ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
3553 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
3554 ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
3559 len = read(ifd, buf, sizeof(buf));
3561 ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
3567 res = write(ofd, buf, len);
3568 if (errno == ENOMEM || errno == ENOSPC || res != len) {
3569 ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
3579 #ifdef HARDLINK_WHEN_POSSIBLE
3581 /* Hard link succeeded */
3588 * \brief Copies a voicemail information (envelope) file.
3592 * Every voicemail has the data (.wav) file, and the information file.
3593 * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
3594 * This is used by the COPY macro when not using IMAP storage.
3596 static void copy_plain_file(char *frompath, char *topath)
3598 char frompath2[PATH_MAX], topath2[PATH_MAX];
3599 struct ast_variable *tmp,*var = NULL;
3600 const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;