2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \author Mark Spencer <markster@digium.com>
22 * \brief Comedian Mail - Voicemail System
24 * \extref unixODBC (http://www.unixodbc.org/)
25 * \extref A source distribution of University of Washington's IMAP c-client
26 * (http://www.washington.edu/imap/)
30 * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
31 * \ingroup applications
32 * \note This module requires res_adsi to load. This needs to be optional
35 * \note This file is now almost impossible to work with, due to all \#ifdefs.
36 * Feels like the database code before realtime. Someone - please come up
37 * with a plan to clean this up.
41 <depend>res_smdi</depend>
45 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_voicemail.so apps/app_directory.o apps/app_directory.so">
46 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
47 <conflict>ODBC_STORAGE</conflict>
48 <conflict>IMAP_STORAGE</conflict>
49 <defaultenabled>yes</defaultenabled>
51 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
52 <depend>generic_odbc</depend>
54 <conflict>IMAP_STORAGE</conflict>
55 <conflict>FILE_STORAGE</conflict>
56 <defaultenabled>no</defaultenabled>
58 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
59 <depend>imap_tk</depend>
60 <conflict>ODBC_STORAGE</conflict>
61 <conflict>FILE_STORAGE</conflict>
63 <defaultenabled>no</defaultenabled>
74 #ifdef USE_SYSTEM_IMAP
75 #include <imap/c-client.h>
76 #include <imap/imap4r1.h>
77 #include <imap/linkage.h>
78 #elif defined (USE_SYSTEM_CCLIENT)
79 #include <c-client/c-client.h>
80 #include <c-client/imap4r1.h>
81 #include <c-client/linkage.h>
89 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
91 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
98 #include "asterisk/logger.h"
99 #include "asterisk/lock.h"
100 #include "asterisk/file.h"
101 #include "asterisk/channel.h"
102 #include "asterisk/pbx.h"
103 #include "asterisk/config.h"
104 #include "asterisk/say.h"
105 #include "asterisk/module.h"
106 #include "asterisk/adsi.h"
107 #include "asterisk/app.h"
108 #include "asterisk/manager.h"
109 #include "asterisk/dsp.h"
110 #include "asterisk/localtime.h"
111 #include "asterisk/cli.h"
112 #include "asterisk/utils.h"
113 #include "asterisk/stringfields.h"
114 #include "asterisk/smdi.h"
115 #include "asterisk/event.h"
116 #include "asterisk/taskprocessor.h"
119 #include "asterisk/res_odbc.h"
123 #include "asterisk/threadstorage.h"
127 <application name="VoiceMail" language="en_US">
129 Leave a Voicemail message.
132 <parameter name="mailboxs" argsep="&" required="true">
133 <argument name="mailbox1" argsep="@" required="true">
134 <argument name="mailbox" required="true" />
135 <argument name="context" />
137 <argument name="mailbox2" argsep="@" multiple="true">
138 <argument name="mailbox" required="true" />
139 <argument name="context" />
142 <parameter name="options">
145 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
148 <argument name="c" />
149 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
150 if played during the greeting. Context defaults to the current context.</para>
153 <argument name="#" required="true" />
154 <para>Use the specified amount of gain when recording the voicemail
155 message. The units are whole-number decibels (dB). Only works on supported
156 technologies, which is DAHDI only.</para>
159 <para>Skip the playback of instructions for leaving a message to the
160 calling party.</para>
163 <para>Play the <literal>unavailable</literal> greeting.</para>
166 <para>Mark message as <literal>URGENT</literal>.</para>
169 <para>Mark message as <literal>PRIORITY</literal>.</para>
175 <para>This application allows the calling party to leave a message for the specified
176 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
177 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
179 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
182 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
185 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
188 <para>This application will set the following channel variable upon completion:</para>
190 <variable name="VMSTATUS">
191 <para>This indicates the status of the execution of the VoiceMail application.</para>
192 <value name="SUCCESS" />
193 <value name="USEREXIT" />
194 <value name="FAILED" />
199 <application name="VoiceMailMain" language="en_US">
201 Check Voicemail messages.
204 <parameter name="mailbox" required="true" argsep="@">
205 <argument name="mailbox" />
206 <argument name="context" />
208 <parameter name="options">
211 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
212 the mailbox that is entered by the caller.</para>
215 <argument name="#" required="true" />
216 <para>Use the specified amount of gain when recording a voicemail message.
217 The units are whole-number decibels (dB).</para>
220 <para>Skip checking the passcode for the mailbox.</para>
223 <argument name="folder" required="true" />
224 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
225 Defaults to <literal>INBOX</literal>.</para>
231 <para>This application allows the calling party to check voicemail messages. A specific
232 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
233 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
234 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
235 <literal>default</literal> context will be used.</para>
238 <application name="MailboxExists" language="en_US">
240 Check to see if Voicemail mailbox exists.
243 <parameter name="mailbox" required="true" argsep="@">
244 <argument name="mailbox" required="true" />
245 <argument name="context" />
247 <parameter name="options">
248 <para>None options.</para>
252 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
253 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
255 <para>This application will set the following channel variable upon completion:</para>
257 <variable name="VMBOXEXISTSSTATUS">
258 <para>This will contain the status of the execution of the MailboxExists application.
259 Possible values include:</para>
260 <value name="SUCCESS" />
261 <value name="FAILED" />
266 <application name="VMAuthenticate" language="en_US">
268 Authenticate with Voicemail passwords.
271 <parameter name="mailbox" required="true" argsep="@">
272 <argument name="mailbox" />
273 <argument name="context" />
275 <parameter name="options">
278 <para>Skip playing the initial prompts.</para>
284 <para>This application behaves the same way as the Authenticate application, but the passwords
285 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
286 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
287 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
291 <function name="MAILBOX_EXISTS" language="en_US">
293 Tell if a mailbox is configured.
296 <parameter name="mailbox" required="true" />
297 <parameter name="context" />
300 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
301 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
305 <manager name="VoicemailUsersList" language="en_US">
307 List All Voicemail User Information.
310 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
318 static char imapserver[48];
319 static char imapport[8];
320 static char imapflags[128];
321 static char imapfolder[64];
322 static char imapparentfolder[64] = "\0";
323 static char greetingfolder[64];
324 static char authuser[32];
325 static char authpassword[42];
327 static int expungeonhangup = 1;
328 static int imapgreetings = 0;
329 static char delimiter = '\0';
334 AST_THREADSTORAGE(ts_vmstate);
336 /* Forward declarations for IMAP */
337 static int init_mailstream(struct vm_state *vms, int box);
338 static void write_file(char *filename, char *buffer, unsigned long len);
339 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
340 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
341 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
342 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
343 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
344 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
345 static void vmstate_insert(struct vm_state *vms);
346 static void vmstate_delete(struct vm_state *vms);
347 static void set_update(MAILSTREAM * stream);
348 static void init_vm_state(struct vm_state *vms);
349 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
350 static void get_mailbox_delimiter(MAILSTREAM *stream);
351 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
352 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
353 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);
354 static void update_messages_by_imapuser(const char *user, unsigned long number);
355 static int vm_delete(char *file);
357 static int imap_remove_file (char *dir, int msgnum);
358 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
359 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
360 static void check_quota(struct vm_state *vms, char *mailbox);
361 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
363 struct vm_state *vms;
364 AST_LIST_ENTRY(vmstate) list;
367 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
371 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
373 #define COMMAND_TIMEOUT 5000
374 /* Don't modify these here; set your umask at runtime instead */
375 #define VOICEMAIL_DIR_MODE 0777
376 #define VOICEMAIL_FILE_MODE 0666
377 #define CHUNKSIZE 65536
379 #define VOICEMAIL_CONFIG "voicemail.conf"
380 #define ASTERISK_USERNAME "asterisk"
382 /* Define fast-forward, pause, restart, and reverse keys
383 while listening to a voicemail message - these are
384 strings, not characters */
385 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
386 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
387 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
388 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
389 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
390 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
392 /* Default mail command to mail voicemail. Change it with the
393 mailcmd= command in voicemail.conf */
394 #define SENDMAIL "/usr/sbin/sendmail -t"
396 #define INTRO "vm-intro"
399 #define MAXMSGLIMIT 9999
401 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
403 #define BASELINELEN 72
404 #define BASEMAXINLINE 256
407 #define MAX_DATETIME_FORMAT 512
408 #define MAX_NUM_CID_CONTEXTS 10
410 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
411 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
412 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
413 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
414 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
415 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
416 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
417 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
418 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
419 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
420 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
421 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
422 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
423 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
424 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
425 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
426 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
427 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
428 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
429 #define ERROR_LOCK_PATH -100
441 enum vm_option_flags {
442 OPT_SILENT = (1 << 0),
443 OPT_BUSY_GREETING = (1 << 1),
444 OPT_UNAVAIL_GREETING = (1 << 2),
445 OPT_RECORDGAIN = (1 << 3),
446 OPT_PREPEND_MAILBOX = (1 << 4),
447 OPT_AUTOPLAY = (1 << 6),
448 OPT_DTMFEXIT = (1 << 7),
449 OPT_MESSAGE_Urgent = (1 << 8),
450 OPT_MESSAGE_PRIORITY = (1 << 9)
453 enum vm_option_args {
454 OPT_ARG_RECORDGAIN = 0,
455 OPT_ARG_PLAYFOLDER = 1,
456 OPT_ARG_DTMFEXIT = 2,
457 /* This *must* be the last value in this enum! */
458 OPT_ARG_ARRAY_SIZE = 3,
461 AST_APP_OPTIONS(vm_app_options, {
462 AST_APP_OPTION('s', OPT_SILENT),
463 AST_APP_OPTION('b', OPT_BUSY_GREETING),
464 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
465 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
466 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
467 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
468 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
469 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
470 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
473 static int load_config(int reload);
475 /*! \page vmlang Voicemail Language Syntaxes Supported
477 \par Syntaxes supported, not really language codes.
484 \arg \b pt - Portuguese
485 \arg \b pt_BR - Portuguese (Brazil)
487 \arg \b no - Norwegian
489 \arg \b tw - Chinese (Taiwan)
490 \arg \b ua - Ukrainian
492 German requires the following additional soundfile:
493 \arg \b 1F einE (feminine)
495 Spanish requires the following additional soundfile:
496 \arg \b 1M un (masculine)
498 Dutch, Portuguese & Spanish require the following additional soundfiles:
499 \arg \b vm-INBOXs singular of 'new'
500 \arg \b vm-Olds singular of 'old/heard/read'
503 \arg \b vm-INBOX nieuwe (nl)
504 \arg \b vm-Old oude (nl)
507 \arg \b vm-new-a 'new', feminine singular accusative
508 \arg \b vm-new-e 'new', feminine plural accusative
509 \arg \b vm-new-ych 'new', feminine plural genitive
510 \arg \b vm-old-a 'old', feminine singular accusative
511 \arg \b vm-old-e 'old', feminine plural accusative
512 \arg \b vm-old-ych 'old', feminine plural genitive
513 \arg \b digits/1-a 'one', not always same as 'digits/1'
514 \arg \b digits/2-ie 'two', not always same as 'digits/2'
517 \arg \b vm-nytt singular of 'new'
518 \arg \b vm-nya plural of 'new'
519 \arg \b vm-gammalt singular of 'old'
520 \arg \b vm-gamla plural of 'old'
521 \arg \b digits/ett 'one', not always same as 'digits/1'
524 \arg \b vm-ny singular of 'new'
525 \arg \b vm-nye plural of 'new'
526 \arg \b vm-gammel singular of 'old'
527 \arg \b vm-gamle plural of 'old'
535 Italian requires the following additional soundfile:
539 \arg \b vm-nuovi new plural
540 \arg \b vm-vecchio old
541 \arg \b vm-vecchi old plural
543 Chinese (Taiwan) requires the following additional soundfile:
544 \arg \b vm-tong A class-word for call (tong1)
545 \arg \b vm-ri A class-word for day (ri4)
546 \arg \b vm-you You (ni3)
547 \arg \b vm-haveno Have no (mei2 you3)
548 \arg \b vm-have Have (you3)
549 \arg \b vm-listen To listen (yao4 ting1)
552 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
553 spelled among others when you have to change folder. For the above reasons, vm-INBOX
554 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
563 unsigned char iobuf[BASEMAXINLINE];
566 /*! Structure for linked list of users
567 * Use ast_vm_user_destroy() to free one of these structures. */
569 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
570 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
571 char password[80]; /*!< Secret pin code, numbers only */
572 char fullname[80]; /*!< Full name, for directory app */
573 char email[80]; /*!< E-mail address */
574 char *emailsubject; /*!< E-mail subject */
575 char *emailbody; /*!< E-mail body */
576 char pager[80]; /*!< E-mail address to pager (no attachment) */
577 char serveremail[80]; /*!< From: Mail address */
578 char mailcmd[160]; /*!< Configurable mail command */
579 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
580 char zonetag[80]; /*!< Time zone */
583 char uniqueid[80]; /*!< Unique integer identifier */
585 char attachfmt[20]; /*!< Attachment format */
586 unsigned int flags; /*!< VM_ flags */
588 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
589 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
590 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
592 char imapuser[80]; /*!< IMAP server login */
593 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
594 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
596 double volgain; /*!< Volume gain for voicemails sent via email */
597 AST_LIST_ENTRY(ast_vm_user) list;
600 /*! Voicemail time zones */
602 AST_LIST_ENTRY(vm_zone) list;
605 char msg_format[512];
608 #define VMSTATE_MAX_MSG_ARRAY 256
610 /*! Voicemail mailbox state */
615 char curdir[PATH_MAX];
616 char vmbox[PATH_MAX];
618 char intro[PATH_MAX];
630 int updated; /*!< decremented on each mail check until 1 -allows delay */
631 long msgArray[VMSTATE_MAX_MSG_ARRAY];
632 MAILSTREAM *mailstream;
634 char imapuser[80]; /*!< IMAP server login */
636 char introfn[PATH_MAX]; /*!< Name of prepended file */
637 unsigned int quota_limit;
638 unsigned int quota_usage;
639 struct vm_state *persist_vms;
644 static char odbc_database[80];
645 static char odbc_table[80];
646 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
647 #define DISPOSE(a,b) remove_file(a,b)
648 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
649 #define EXISTS(a,b,c,d) (message_exists(a,b))
650 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
651 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
652 #define DELETE(a,b,c,d) (delete_file(a,b))
655 #define DISPOSE(a,b) (imap_remove_file(a,b))
656 #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))
657 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
658 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
659 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
660 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
661 #define DELETE(a,b,c,d) (vm_imap_delete(b,d))
663 #define RETRIEVE(a,b,c,d)
665 #define STORE(a,b,c,d,e,f,g,h,i,j)
666 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
667 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
668 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
669 #define DELETE(a,b,c,d) (vm_delete(c))
673 static char VM_SPOOL_DIR[PATH_MAX];
675 static char ext_pass_cmd[128];
676 static char ext_pass_check_cmd[128];
680 #define PWDCHANGE_INTERNAL (1 << 1)
681 #define PWDCHANGE_EXTERNAL (1 << 2)
682 static int pwdchange = PWDCHANGE_INTERNAL;
685 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
688 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
690 # define tdesc "Comedian Mail (Voicemail System)"
694 static char userscontext[AST_MAX_EXTENSION] = "default";
696 static char *addesc = "Comedian Mail";
698 /* Leave a message */
699 static char *app = "VoiceMail";
701 /* Check mail, control, etc */
702 static char *app2 = "VoiceMailMain";
704 static char *app3 = "MailboxExists";
705 static char *app4 = "VMAuthenticate";
707 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
708 static AST_LIST_HEAD_STATIC(zones, vm_zone);
709 static char zonetag[80];
710 static int maxsilence;
712 static int maxdeletedmsg;
713 static int silencethreshold = 128;
714 static char serveremail[80];
715 static char mailcmd[160]; /* Configurable mail cmd */
716 static char externnotify[160];
717 static struct ast_smdi_interface *smdi_iface = NULL;
718 static char vmfmts[80];
719 static double volgain;
720 static int vmminsecs;
721 static int vmmaxsecs;
724 static int maxlogins;
725 static int minpassword;
727 /*! Poll mailboxes for changes since there is something external to
728 * app_voicemail that may change them. */
729 static unsigned int poll_mailboxes;
731 /*! Polling frequency */
732 static unsigned int poll_freq;
733 /*! By default, poll every 30 seconds */
734 #define DEFAULT_POLL_FREQ 30
736 AST_MUTEX_DEFINE_STATIC(poll_lock);
737 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
738 static pthread_t poll_thread = AST_PTHREADT_NULL;
739 static unsigned char poll_thread_run;
741 /*! Subscription to ... MWI event subscriptions */
742 static struct ast_event_sub *mwi_sub_sub;
743 /*! Subscription to ... MWI event un-subscriptions */
744 static struct ast_event_sub *mwi_unsub_sub;
747 * \brief An MWI subscription
749 * This is so we can keep track of which mailboxes are subscribed to.
750 * This way, we know which mailboxes to poll when the pollmailboxes
751 * option is being used.
754 AST_RWLIST_ENTRY(mwi_sub) entry;
762 struct mwi_sub_task {
768 static struct ast_taskprocessor *mwi_subscription_tps;
770 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
772 /* custom audio control prompts for voicemail playback */
773 static char listen_control_forward_key[12];
774 static char listen_control_reverse_key[12];
775 static char listen_control_pause_key[12];
776 static char listen_control_restart_key[12];
777 static char listen_control_stop_key[12];
779 /* custom password sounds */
780 static char vm_password[80] = "vm-password";
781 static char vm_newpassword[80] = "vm-newpassword";
782 static char vm_passchanged[80] = "vm-passchanged";
783 static char vm_reenterpassword[80] = "vm-reenterpassword";
784 static char vm_mismatch[80] = "vm-mismatch";
785 static char vm_invalid_password[80] = "vm-invalid-password";
787 static struct ast_flags globalflags = {0};
789 static int saydurationminfo;
791 static char dialcontext[AST_MAX_CONTEXT] = "";
792 static char callcontext[AST_MAX_CONTEXT] = "";
793 static char exitcontext[AST_MAX_CONTEXT] = "";
795 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
798 static char *emailbody = NULL;
799 static char *emailsubject = NULL;
800 static char *pagerbody = NULL;
801 static char *pagersubject = NULL;
802 static char fromstring[100];
803 static char pagerfromstring[100];
804 static char charset[32] = "ISO-8859-1";
806 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
807 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
808 static int adsiver = 1;
809 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
811 /* Forward declarations - generic */
812 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
813 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);
814 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
815 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
816 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
817 signed char record_gain, struct vm_state *vms, char *flag);
818 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
819 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
820 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);
821 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag);
822 static void apply_options(struct ast_vm_user *vmu, const char *options);
823 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);
824 static int is_valid_dtmf(const char *key);
826 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
827 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
830 static char *strip_control(const char *input, char *buf, size_t buflen)
833 for (; *input; input++) {
838 if (bufptr == buf + buflen - 1) {
848 * \brief Sets default voicemail system options to a voicemail user.
850 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
851 * - all the globalflags
852 * - the saydurationminfo
856 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
859 static void populate_defaults(struct ast_vm_user *vmu)
861 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
862 if (saydurationminfo)
863 vmu->saydurationm = saydurationminfo;
864 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
865 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
866 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
867 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
869 vmu->maxsecs = vmmaxsecs;
871 vmu->maxmsg = maxmsg;
873 vmu->maxdeletedmsg = maxdeletedmsg;
874 vmu->volgain = volgain;
875 vmu->emailsubject = NULL;
876 vmu->emailbody = NULL;
880 * \brief Sets a a specific property value.
881 * \param vmu The voicemail user object to work with.
882 * \param var The name of the property to be set.
883 * \param value The value to be set to the property.
885 * The property name must be one of the understood properties. See the source for details.
887 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
890 if (!strcasecmp(var, "attach")) {
891 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
892 } else if (!strcasecmp(var, "attachfmt")) {
893 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
894 } else if (!strcasecmp(var, "serveremail")) {
895 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
896 } else if (!strcasecmp(var, "language")) {
897 ast_copy_string(vmu->language, value, sizeof(vmu->language));
898 } else if (!strcasecmp(var, "tz")) {
899 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
901 } else if (!strcasecmp(var, "imapuser")) {
902 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
903 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
904 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
905 } else if (!strcasecmp(var, "imapvmshareid")) {
906 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
908 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
909 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
910 } else if (!strcasecmp(var, "saycid")){
911 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
912 } else if (!strcasecmp(var, "sendvoicemail")){
913 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
914 } else if (!strcasecmp(var, "review")){
915 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
916 } else if (!strcasecmp(var, "tempgreetwarn")){
917 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
918 } else if (!strcasecmp(var, "messagewrap")){
919 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
920 } else if (!strcasecmp(var, "operator")) {
921 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
922 } else if (!strcasecmp(var, "envelope")){
923 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
924 } else if (!strcasecmp(var, "moveheard")){
925 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
926 } else if (!strcasecmp(var, "sayduration")){
927 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
928 } else if (!strcasecmp(var, "saydurationm")){
929 if (sscanf(value, "%d", &x) == 1) {
930 vmu->saydurationm = x;
932 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
934 } else if (!strcasecmp(var, "forcename")){
935 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
936 } else if (!strcasecmp(var, "forcegreetings")){
937 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
938 } else if (!strcasecmp(var, "callback")) {
939 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
940 } else if (!strcasecmp(var, "dialout")) {
941 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
942 } else if (!strcasecmp(var, "exitcontext")) {
943 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
944 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
945 if (vmu->maxsecs <= 0) {
946 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
947 vmu->maxsecs = vmmaxsecs;
949 vmu->maxsecs = atoi(value);
951 if (!strcasecmp(var, "maxmessage"))
952 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
953 } else if (!strcasecmp(var, "maxmsg")) {
954 vmu->maxmsg = atoi(value);
955 if (vmu->maxmsg <= 0) {
956 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
957 vmu->maxmsg = MAXMSG;
958 } else if (vmu->maxmsg > MAXMSGLIMIT) {
959 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
960 vmu->maxmsg = MAXMSGLIMIT;
962 } else if (!strcasecmp(var, "backupdeleted")) {
963 if (sscanf(value, "%d", &x) == 1)
964 vmu->maxdeletedmsg = x;
965 else if (ast_true(value))
966 vmu->maxdeletedmsg = MAXMSG;
968 vmu->maxdeletedmsg = 0;
970 if (vmu->maxdeletedmsg < 0) {
971 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
972 vmu->maxdeletedmsg = MAXMSG;
973 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
974 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
975 vmu->maxdeletedmsg = MAXMSGLIMIT;
977 } else if (!strcasecmp(var, "volgain")) {
978 sscanf(value, "%lf", &vmu->volgain);
979 } else if (!strcasecmp(var, "options")) {
980 apply_options(vmu, value);
984 static char *vm_check_password_shell(char *command, char *buf, size_t len)
991 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
994 pid = ast_safe_fork(0);
1000 snprintf(buf, len, "FAILURE: Fork failed");
1004 if (read(fds[0], buf, len) < 0) {
1005 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1010 AST_DECLARE_APP_ARGS(arg,
1013 char *mycmd = ast_strdupa(command);
1016 dup2(fds[1], STDOUT_FILENO);
1018 ast_close_fds_above_n(STDOUT_FILENO);
1020 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1022 execv(arg.v[0], arg.v);
1023 printf("FAILURE: %s", strerror(errno));
1031 * \brief Check that password meets minimum required length
1032 * \param vmu The voicemail user to change the password for.
1033 * \param password The password string to check
1035 * \return zero on ok, 1 on not ok.
1037 static int check_password(struct ast_vm_user *vmu, char *password)
1039 /* check minimum length */
1040 if (strlen(password) < minpassword)
1042 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1043 char cmd[255], buf[255];
1045 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1047 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1048 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1049 ast_debug(5, "Result: %s\n", buf);
1050 if (!strncasecmp(buf, "VALID", 5)) {
1051 ast_debug(3, "Passed password check: '%s'\n", buf);
1053 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1054 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1057 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1066 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1067 * \param vmu The voicemail user to change the password for.
1068 * \param password The new value to be set to the password for this user.
1070 * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
1071 * This is called from the (top level) vm_change_password.
1073 * \return zero on success, -1 on error.
1075 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1078 if (!ast_strlen_zero(vmu->uniqueid)) {
1079 if (strlen(password) > 10) {
1080 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1082 res = ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL);
1084 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1095 * \brief Destructively Parse options and apply.
1097 static void apply_options(struct ast_vm_user *vmu, const char *options)
1102 stringp = ast_strdupa(options);
1103 while ((s = strsep(&stringp, "|"))) {
1105 if ((var = strsep(&value, "=")) && value) {
1106 apply_option(vmu, var, value);
1112 * \brief Loads the options specific to a voicemail user.
1114 * This is called when a vm_user structure is being set up, such as from load_options.
1116 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1118 for (; var; var = var->next) {
1119 if (!strcasecmp(var->name, "vmsecret")) {
1120 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1121 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1122 if (ast_strlen_zero(retval->password))
1123 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1124 } else if (!strcasecmp(var->name, "uniqueid")) {
1125 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1126 } else if (!strcasecmp(var->name, "pager")) {
1127 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1128 } else if (!strcasecmp(var->name, "email")) {
1129 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1130 } else if (!strcasecmp(var->name, "fullname")) {
1131 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1132 } else if (!strcasecmp(var->name, "context")) {
1133 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1134 } else if (!strcasecmp(var->name, "emailsubject")) {
1135 retval->emailsubject = ast_strdup(var->value);
1136 } else if (!strcasecmp(var->name, "emailbody")) {
1137 retval->emailbody = ast_strdup(var->value);
1139 } else if (!strcasecmp(var->name, "imapuser")) {
1140 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1141 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1142 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1143 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1144 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1147 apply_option(retval, var->name, var->value);
1152 * \brief Determines if a DTMF key entered is valid.
1153 * \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.
1155 * Tests the character entered against the set of valid DTMF characters.
1156 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1158 static int is_valid_dtmf(const char *key)
1161 char *local_key = ast_strdupa(key);
1163 for (i = 0; i < strlen(key); ++i) {
1164 if (!strchr(VALID_DTMF, *local_key)) {
1165 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1174 * \brief Finds a voicemail user from the realtime engine.
1179 * 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.
1181 * \return The ast_vm_user structure for the user that was found.
1183 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1185 struct ast_variable *var;
1186 struct ast_vm_user *retval;
1188 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1190 ast_set_flag(retval, VM_ALLOCED);
1192 memset(retval, 0, sizeof(*retval));
1194 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1195 populate_defaults(retval);
1196 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1197 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1199 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1201 apply_options_full(retval, var);
1202 ast_variables_destroy(var);
1213 * \brief Finds a voicemail user from the users file or the realtime engine.
1218 * \return The ast_vm_user structure for the user that was found.
1220 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1222 /* This function could be made to generate one from a database, too */
1223 struct ast_vm_user *vmu = NULL, *cur;
1224 AST_LIST_LOCK(&users);
1226 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1227 context = "default";
1229 AST_LIST_TRAVERSE(&users, cur, list) {
1230 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1232 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1236 /* Make a copy, so that on a reload, we have no race */
1237 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1238 memcpy(vmu, cur, sizeof(*vmu));
1239 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1240 AST_LIST_NEXT(vmu, list) = NULL;
1243 vmu = find_user_realtime(ivm, context, mailbox);
1244 AST_LIST_UNLOCK(&users);
1249 * \brief Resets a user password to a specified password.
1254 * This does the actual change password work, called by the vm_change_password() function.
1256 * \return zero on success, -1 on error.
1258 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1260 /* This function could be made to generate one from a database, too */
1261 struct ast_vm_user *cur;
1263 AST_LIST_LOCK(&users);
1264 AST_LIST_TRAVERSE(&users, cur, list) {
1265 if ((!context || !strcasecmp(context, cur->context)) &&
1266 (!strcasecmp(mailbox, cur->mailbox)))
1270 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1273 AST_LIST_UNLOCK(&users);
1278 * \brief The handler for the change password option.
1279 * \param vmu The voicemail user to work with.
1280 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1281 * 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.
1282 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1284 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1286 struct ast_config *cfg = NULL;
1287 struct ast_variable *var = NULL;
1288 struct ast_category *cat = NULL;
1289 char *category = NULL, *value = NULL, *new = NULL;
1290 const char *tmp = NULL;
1291 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1292 if (!change_password_realtime(vmu, newpassword))
1295 /* check voicemail.conf */
1296 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1297 while ((category = ast_category_browse(cfg, category))) {
1298 if (!strcasecmp(category, vmu->context)) {
1299 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1300 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1303 value = strstr(tmp, ",");
1305 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1308 new = alloca((strlen(value)+strlen(newpassword)+1));
1309 sprintf(new, "%s%s", newpassword, value);
1310 if (!(cat = ast_category_get(cfg, category))) {
1311 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1314 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1317 /* save the results */
1318 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1319 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1320 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1324 /* check users.conf and update the password stored for the mailbox*/
1325 /* if no vmsecret entry exists create one. */
1326 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1327 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1328 while ((category = ast_category_browse(cfg, category))) {
1329 ast_debug(4, "users.conf: %s\n", category);
1330 if (!strcasecmp(category, vmu->mailbox)) {
1331 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1332 ast_debug(3, "looks like we need to make vmsecret!\n");
1333 var = ast_variable_new("vmsecret", newpassword, "");
1335 new = alloca(strlen(newpassword)+1);
1336 sprintf(new, "%s", newpassword);
1337 if (!(cat = ast_category_get(cfg, category))) {
1338 ast_debug(4, "failed to get category!\n");
1342 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1344 ast_variable_append(cat, var);
1347 /* save the results and clean things up */
1348 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1349 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1350 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1354 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1357 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1358 if (!ast_safe_system(buf)) {
1359 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1360 /* Reset the password in memory, too */
1361 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1366 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1367 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1368 * \param len The length of the path string that was written out.
1370 * The path is constructed as
1371 * VM_SPOOL_DIRcontext/ext/folder
1373 * \return zero on success, -1 on error.
1375 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1377 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1381 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1382 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1383 * \param len The length of the path string that was written out.
1385 * The path is constructed as
1386 * VM_SPOOL_DIRcontext/ext/folder
1388 * \return zero on success, -1 on error.
1390 static int make_file(char *dest, const int len, const char *dir, const int num)
1392 return snprintf(dest, len, "%s/msg%04d", dir, num);
1395 /* same as mkstemp, but return a FILE * */
1396 static FILE *vm_mkftemp(char *template)
1399 int pfd = mkstemp(template);
1400 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1402 p = fdopen(pfd, "w+");
1411 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1412 * \param dest String. base directory.
1413 * \param len Length of dest.
1414 * \param context String. Ignored if is null or empty string.
1415 * \param ext String. Ignored if is null or empty string.
1416 * \param folder String. Ignored if is null or empty string.
1417 * \return -1 on failure, 0 on success.
1419 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1421 mode_t mode = VOICEMAIL_DIR_MODE;
1424 make_dir(dest, len, context, ext, folder);
1425 if ((res = ast_mkdir(dest, mode))) {
1426 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1432 static const char *mbox(int id)
1434 static const char * const msgs[] = {
1452 return (id >= 0 && id < ARRAY_LEN(msgs)) ? msgs[id] : "Unknown";
1455 static void free_user(struct ast_vm_user *vmu)
1457 if (ast_test_flag(vmu, VM_ALLOCED)) {
1458 if (vmu->emailbody != NULL) {
1459 ast_free(vmu->emailbody);
1460 vmu->emailbody = NULL;
1462 if (vmu->emailsubject != NULL) {
1463 ast_free(vmu->emailsubject);
1464 vmu->emailsubject = NULL;
1470 /* All IMAP-specific functions should go in this block. This
1471 * keeps them from being spread out all over the code */
1473 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1476 struct vm_state *vms;
1477 unsigned long messageNum;
1479 /* Greetings aren't stored in IMAP, so we can't delete them there */
1484 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1485 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);
1489 /* find real message number based on msgnum */
1490 /* this may be an index into vms->msgArray based on the msgnum. */
1491 messageNum = vms->msgArray[msgnum];
1492 if (messageNum == 0) {
1493 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1496 if (option_debug > 2)
1497 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1498 /* delete message */
1499 snprintf (arg, sizeof(arg), "%lu", messageNum);
1500 ast_mutex_lock(&vms->lock);
1501 mail_setflag (vms->mailstream, arg, "\\DELETED");
1502 ast_mutex_unlock(&vms->lock);
1505 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1507 struct vm_state *vms_p;
1508 char *file, *filename;
1513 /* This function is only used for retrieval of IMAP greetings
1514 * regular messages are not retrieved this way, nor are greetings
1515 * if they are stored locally*/
1516 if (msgnum > -1 || !imapgreetings) {
1519 file = strrchr(ast_strdupa(dir), '/');
1523 ast_debug (1, "Failed to procure file name from directory passed.\n");
1528 /* check if someone is accessing this box right now... */
1529 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))) {
1530 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1534 /* Greetings will never have a prepended message */
1535 *vms_p->introfn = '\0';
1537 ast_mutex_lock(&vms_p->lock);
1538 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1539 if (!vms_p->mailstream) {
1540 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1541 ast_mutex_unlock(&vms_p->lock);
1545 /*XXX Yuck, this could probably be done a lot better */
1546 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1547 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1548 /* We have the body, now we extract the file name of the first attachment. */
1549 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1550 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1552 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1553 ast_mutex_unlock(&vms_p->lock);
1556 filename = strsep(&attachment, ".");
1557 if (!strcmp(filename, file)) {
1558 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1559 vms_p->msgArray[vms_p->curmsg] = i + 1;
1560 save_body(body, vms_p, "2", attachment, 0);
1561 ast_mutex_unlock(&vms_p->lock);
1565 ast_mutex_unlock(&vms_p->lock);
1570 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1573 char *header_content;
1574 char *attachedfilefmt;
1576 struct vm_state *vms;
1577 char text_file[PATH_MAX];
1578 FILE *text_file_ptr;
1580 struct ast_vm_user *vmu;
1582 if (!(vmu = find_user(NULL, context, mailbox))) {
1583 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1588 if (imapgreetings) {
1589 res = imap_retrieve_greeting(dir, msgnum, vmu);
1597 /* Before anything can happen, we need a vm_state so that we can
1598 * actually access the imap server through the vms->mailstream
1600 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1601 /* This should not happen. If it does, then I guess we'd
1602 * need to create the vm_state, extract which mailbox to
1603 * open, and then set up the msgArray so that the correct
1604 * IMAP message could be accessed. If I have seen correctly
1605 * though, the vms should be obtainable from the vmstates list
1606 * and should have its msgArray properly set up.
1608 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1613 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1614 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1616 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1617 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1622 if (option_debug > 2)
1623 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1624 if (vms->msgArray[msgnum] == 0) {
1625 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1630 /* This will only work for new messages... */
1631 ast_mutex_lock(&vms->lock);
1632 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1633 ast_mutex_unlock(&vms->lock);
1634 /* empty string means no valid header */
1635 if (ast_strlen_zero(header_content)) {
1636 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1641 ast_mutex_lock(&vms->lock);
1642 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1643 ast_mutex_unlock(&vms->lock);
1645 /* We have the body, now we extract the file name of the first attachment. */
1646 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1647 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1649 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1654 /* Find the format of the attached file */
1656 strsep(&attachedfilefmt, ".");
1657 if (!attachedfilefmt) {
1658 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1663 save_body(body, vms, "2", attachedfilefmt, 0);
1664 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1665 *vms->introfn = '\0';
1668 /* Get info from headers!! */
1669 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1671 if (!(text_file_ptr = fopen(text_file, "w"))) {
1672 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1675 fprintf(text_file_ptr, "%s\n", "[message]");
1677 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1678 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1679 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1680 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1681 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1682 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1683 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1684 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1685 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1686 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1687 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1688 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1689 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1690 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1691 fclose(text_file_ptr);
1698 static int folder_int(const char *folder)
1700 /*assume a NULL folder means INBOX*/
1703 if (!strcasecmp(folder, imapfolder))
1705 else if (!strcasecmp(folder, "Old"))
1707 else if (!strcasecmp(folder, "Work"))
1709 else if (!strcasecmp(folder, "Family"))
1711 else if (!strcasecmp(folder, "Friends"))
1713 else if (!strcasecmp(folder, "Cust1"))
1715 else if (!strcasecmp(folder, "Cust2"))
1717 else if (!strcasecmp(folder, "Cust3"))
1719 else if (!strcasecmp(folder, "Cust4"))
1721 else if (!strcasecmp(folder, "Cust5"))
1723 else /*assume they meant INBOX if folder is not found otherwise*/
1728 * \brief Gets the number of messages that exist in a mailbox folder.
1733 * This method is used when IMAP backend is used.
1734 * \return The number of messages in this mailbox folder (zero or more).
1736 static int messagecount(const char *context, const char *mailbox, const char *folder)
1741 struct ast_vm_user *vmu, vmus;
1742 struct vm_state *vms_p;
1744 int fold = folder_int(folder);
1747 /* If URGENT, then look at INBOX */
1753 if (ast_strlen_zero(mailbox))
1756 /* We have to get the user before we can open the stream! */
1757 vmu = find_user(&vmus, context, mailbox);
1759 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1762 /* No IMAP account available */
1763 if (vmu->imapuser[0] == '\0') {
1764 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1769 /* No IMAP account available */
1770 if (vmu->imapuser[0] == '\0') {
1771 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1776 /* check if someone is accessing this box right now... */
1777 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1779 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1782 ast_debug(3, "Returning before search - user is logged in\n");
1783 if (fold == 0) { /* INBOX */
1784 return vms_p->newmessages;
1786 if (fold == 1) { /* Old messages */
1787 return vms_p->oldmessages;
1789 if (fold == 11) {/*Urgent messages*/
1790 return vms_p->urgentmessages;
1794 /* add one if not there... */
1795 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
1797 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1801 vms_p = create_vm_state_from_user(vmu);
1803 ret = init_mailstream(vms_p, fold);
1804 if (!vms_p->mailstream) {
1805 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1809 ast_mutex_lock(&vms_p->lock);
1810 pgm = mail_newsearchpgm ();
1811 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
1812 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
1818 /* In the special case where fold is 1 (old messages) we have to do things a bit
1819 * differently. Old messages are stored in the INBOX but are marked as "seen"
1825 /* look for urgent messages */
1833 vms_p->vmArrayIndex = 0;
1834 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1835 if (fold == 0 && urgent == 0)
1836 vms_p->newmessages = vms_p->vmArrayIndex;
1838 vms_p->oldmessages = vms_p->vmArrayIndex;
1839 if (fold == 0 && urgent == 1)
1840 vms_p->urgentmessages = vms_p->vmArrayIndex;
1841 /*Freeing the searchpgm also frees the searchhdr*/
1842 mail_free_searchpgm(&pgm);
1843 ast_mutex_unlock(&vms_p->lock);
1845 return vms_p->vmArrayIndex;
1847 ast_mutex_lock(&vms_p->lock);
1848 mail_ping(vms_p->mailstream);
1849 ast_mutex_unlock(&vms_p->lock);
1854 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)
1856 char *myserveremail = serveremail;
1858 char introfn[PATH_MAX];
1862 char tmp[80] = "/tmp/astmail-XXXXXX";
1867 int ret; /* for better error checking */
1868 char *imap_flags = NIL;
1870 /* Set urgent flag for IMAP message */
1871 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1872 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1873 imap_flags = "\\FLAGGED";
1876 /* Attach only the first format */
1877 fmt = ast_strdupa(fmt);
1879 strsep(&stringp, "|");
1881 if (!ast_strlen_zero(vmu->serveremail))
1882 myserveremail = vmu->serveremail;
1885 make_file(fn, sizeof(fn), dir, msgnum);
1887 ast_copy_string (fn, dir, sizeof(fn));
1889 snprintf(introfn, sizeof(introfn), "%sintro", fn);
1890 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1894 if (ast_strlen_zero(vmu->email)) {
1895 /* We need the vmu->email to be set when we call make_email_file, but
1896 * if we keep it set, a duplicate e-mail will be created. So at the end
1897 * of this function, we will revert back to an empty string if tempcopy
1900 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1904 if (!strcmp(fmt, "wav49"))
1906 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1908 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1910 if (!(p = vm_mkftemp(tmp))) {
1911 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1913 *(vmu->email) = '\0';
1917 if (msgnum < 0 && imapgreetings) {
1918 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1919 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1922 imap_delete_old_greeting(fn, vms);
1925 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX", S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
1926 /* read mail file to memory */
1929 if (!(buf = ast_malloc(len + 1))) {
1930 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1933 *(vmu->email) = '\0';
1936 if (fread(buf, len, 1, p) < len) {
1938 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
1942 ((char *) buf)[len] = '\0';
1943 INIT(&str, mail_string, buf, len);
1944 ret = init_mailstream(vms, NEW_FOLDER);
1946 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1947 ast_mutex_lock(&vms->lock);
1948 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1949 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1950 ast_mutex_unlock(&vms->lock);
1955 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
1961 ast_debug(3, "%s stored\n", fn);
1964 *(vmu->email) = '\0';
1971 * \brief Gets the number of messages that exist in the inbox folder.
1972 * \param mailbox_context
1973 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1974 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1975 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1977 * This method is used when IMAP backend is used.
1978 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1980 * \return zero on success, -1 on error.
1983 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1985 char tmp[PATH_MAX] = "";
1997 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
1998 /* If no mailbox, return immediately */
1999 if (ast_strlen_zero(mailbox_context))
2002 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2003 context = strchr(tmp, '@');
2004 if (strchr(mailbox_context, ',')) {
2005 int tmpnew, tmpold, tmpurgent;
2006 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2008 while ((cur = strsep(&mb, ", "))) {
2009 if (!ast_strlen_zero(cur)) {
2010 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2018 *urgentmsgs += tmpurgent;
2029 context = "default";
2030 mailboxnc = (char *) mailbox_context;
2033 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
2037 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
2041 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
2047 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
2049 return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
2053 * \brief Determines if the given folder has messages.
2054 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2055 * \param folder the folder to look in
2057 * This function is used when the mailbox is stored in an IMAP back end.
2058 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2059 * \return 1 if the folder has one or more messages. zero otherwise.
2062 static int has_voicemail(const char *mailbox, const char *folder)
2064 char tmp[256], *tmp2, *box, *context;
2065 ast_copy_string(tmp, mailbox, sizeof(tmp));
2067 if (strchr(tmp2, ',')) {
2068 while ((box = strsep(&tmp2, ","))) {
2069 if (!ast_strlen_zero(box)) {
2070 if (has_voicemail(box, folder))
2075 if ((context = strchr(tmp, '@')))
2078 context = "default";
2079 return messagecount(context, tmp, folder) ? 1 : 0;
2083 * \brief Copies a message from one mailbox to another.
2093 * This works with IMAP storage based mailboxes.
2095 * \return zero on success, -1 on error.
2097 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)
2099 struct vm_state *sendvms = NULL, *destvms = NULL;
2100 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2101 if (msgnum >= recip->maxmsg) {
2102 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2105 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2106 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2109 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2110 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2113 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2114 ast_mutex_lock(&sendvms->lock);
2115 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T)) {
2116 ast_mutex_unlock(&sendvms->lock);
2119 ast_mutex_unlock(&sendvms->lock);
2120 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2124 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2126 char tmp[256], *t = tmp;
2127 size_t left = sizeof(tmp);
2129 if (box == OLD_FOLDER) {
2130 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
2132 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
2135 if (box == NEW_FOLDER) {
2136 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2138 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
2141 /* Build up server information */
2142 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2144 /* Add authentication user if present */
2145 if (!ast_strlen_zero(authuser))
2146 ast_build_string(&t, &left, "/authuser=%s", authuser);
2148 /* Add flags if present */
2149 if (!ast_strlen_zero(imapflags))
2150 ast_build_string(&t, &left, "/%s", imapflags);
2152 /* End with username */
2153 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2154 if (box == NEW_FOLDER || box == OLD_FOLDER)
2155 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
2156 else if (box == GREETINGS_FOLDER)
2157 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2158 else { /* Other folders such as Friends, Family, etc... */
2159 if (!ast_strlen_zero(imapparentfolder)) {
2160 /* imapparentfolder would typically be set to INBOX */
2161 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2163 snprintf(spec, len, "%s%s", tmp, mbox(box));
2168 static int init_mailstream(struct vm_state *vms, int box)
2170 MAILSTREAM *stream = NIL;
2175 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2178 if (option_debug > 2)
2179 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2180 if (vms->mailstream == NIL || !vms->mailstream) {
2182 ast_log(LOG_DEBUG, "mailstream not set.\n");
2184 stream = vms->mailstream;
2186 /* debug = T; user wants protocol telemetry? */
2187 debug = NIL; /* NO protocol telemetry? */
2189 if (delimiter == '\0') { /* did not probe the server yet */
2191 #ifdef USE_SYSTEM_IMAP
2192 #include <imap/linkage.c>
2193 #elif defined(USE_SYSTEM_CCLIENT)
2194 #include <c-client/linkage.c>
2196 #include "linkage.c"
2198 /* Connect to INBOX first to get folders delimiter */
2199 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2200 ast_mutex_lock(&vms->lock);
2201 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2202 ast_mutex_unlock(&vms->lock);
2203 if (stream == NIL) {
2204 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2207 get_mailbox_delimiter(stream);
2208 /* update delimiter in imapfolder */
2209 for (cp = imapfolder; *cp; cp++)
2213 /* Now connect to the target folder */
2214 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2215 if (option_debug > 2)
2216 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2217 ast_mutex_lock(&vms->lock);
2218 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2219 ast_mutex_unlock(&vms->lock);
2220 if (vms->mailstream == NIL) {
2227 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2231 int ret, urgent = 0;
2233 /* If Urgent, then look at INBOX */
2239 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2240 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2242 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2243 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2247 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2251 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2252 check_quota(vms, (char *) mbox(box));
2255 ast_mutex_lock(&vms->lock);
2256 pgm = mail_newsearchpgm();
2258 /* Check IMAP folder for Asterisk messages only... */
2259 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2260 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2265 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2266 if (box == NEW_FOLDER && urgent == 1) {
2271 } else if (box == NEW_FOLDER && urgent == 0) {
2276 } else if (box == OLD_FOLDER) {
2281 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2283 vms->vmArrayIndex = 0;
2284 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2285 vms->lastmsg = vms->vmArrayIndex - 1;
2286 mail_free_searchpgm(&pgm);
2288 ast_mutex_unlock(&vms->lock);
2292 static void write_file(char *filename, char *buffer, unsigned long len)
2296 output = fopen (filename, "w");
2297 if (fwrite(buffer, len, 1, output) != 1) {
2298 if (ferror(output)) {
2299 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2305 static void update_messages_by_imapuser(const char *user, unsigned long number)
2307 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2309 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2313 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2314 vms->msgArray[vms->vmArrayIndex++] = number;
2317 void mm_searched(MAILSTREAM *stream, unsigned long number)
2319 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2321 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2324 update_messages_by_imapuser(user, number);
2327 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2329 struct ast_variable *var;
2330 struct ast_vm_user *vmu;
2332 vmu = ast_calloc(1, sizeof *vmu);
2335 ast_set_flag(vmu, VM_ALLOCED);
2336 populate_defaults(vmu);
2338 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2340 apply_options_full(vmu, var);
2341 ast_variables_destroy(var);
2349 /* Interfaces to C-client */
2351 void mm_exists(MAILSTREAM * stream, unsigned long number)
2353 /* mail_ping will callback here if new mail! */
2354 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2355 if (number == 0) return;
2360 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2362 /* mail_ping will callback here if expunged mail! */
2363 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2364 if (number == 0) return;
2369 void mm_flags(MAILSTREAM * stream, unsigned long number)
2371 /* mail_ping will callback here if read mail! */
2372 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2373 if (number == 0) return;
2378 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2380 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2381 mm_log (string, errflg);
2385 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2387 if (delimiter == '\0') {
2391 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2392 if (attributes & LATT_NOINFERIORS)
2393 ast_debug(5, "no inferiors\n");
2394 if (attributes & LATT_NOSELECT)
2395 ast_debug(5, "no select\n");
2396 if (attributes & LATT_MARKED)
2397 ast_debug(5, "marked\n");
2398 if (attributes & LATT_UNMARKED)
2399 ast_debug(5, "unmarked\n");
2403 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2405 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2406 if (attributes & LATT_NOINFERIORS)
2407 ast_debug(5, "no inferiors\n");
2408 if (attributes & LATT_NOSELECT)
2409 ast_debug(5, "no select\n");
2410 if (attributes & LATT_MARKED)
2411 ast_debug(5, "marked\n");
2412 if (attributes & LATT_UNMARKED)
2413 ast_debug(5, "unmarked\n");
2417 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2419 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2420 if (status->flags & SA_MESSAGES)
2421 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2422 if (status->flags & SA_RECENT)
2423 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2424 if (status->flags & SA_UNSEEN)
2425 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2426 if (status->flags & SA_UIDVALIDITY)
2427 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2428 if (status->flags & SA_UIDNEXT)
2429 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2430 ast_log(AST_LOG_NOTICE, "\n");
2434 void mm_log(char *string, long errflg)
2436 switch ((short) errflg) {
2438 ast_debug(1, "IMAP Info: %s\n", string);
2442 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2445 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2451 void mm_dlog(char *string)
2453 ast_log(AST_LOG_NOTICE, "%s\n", string);
2457 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2459 struct ast_vm_user *vmu;
2461 ast_debug(4, "Entering callback mm_login\n");
2463 ast_copy_string(user, mb->user, MAILTMPLEN);
2465 /* We should only do this when necessary */
2466 if (!ast_strlen_zero(authpassword)) {
2467 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2469 AST_LIST_TRAVERSE(&users, vmu, list) {
2470 if (!strcasecmp(mb->user, vmu->imapuser)) {
2471 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2476 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2477 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2485 void mm_critical(MAILSTREAM * stream)
2490 void mm_nocritical(MAILSTREAM * stream)
2495 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2497 kill (getpid (), SIGSTOP);
2502 void mm_fatal(char *string)
2504 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2507 /* C-client callback to handle quota */
2508 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2510 struct vm_state *vms;
2511 char *mailbox = stream->mailbox, *user;
2512 char buf[1024] = "";
2513 unsigned long usage = 0, limit = 0;
2516 usage = pquota->usage;
2517 limit = pquota->limit;
2518 pquota = pquota->next;
2521 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || (!(vms = get_vm_state_by_imapuser(user, 2)) && !(vms = get_vm_state_by_imapuser(user, 0)))) {
2522 ast_log(AST_LOG_ERROR, "No state found.\n");
2526 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2528 vms->quota_usage = usage;
2529 vms->quota_limit = limit;
2532 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2534 char *start, *eol_pnt;
2537 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2540 taglen = strlen(tag) + 1;
2544 if (!(start = strstr(header, tag)))
2547 /* Since we can be called multiple times we should clear our buffer */
2548 memset(buf, 0, len);
2550 ast_copy_string(buf, start+taglen, len);
2551 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2556 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2558 char *start, *quote, *eol_pnt;
2560 if (ast_strlen_zero(mailbox))
2563 if (!(start = strstr(mailbox, "/user=")))
2566 ast_copy_string(buf, start+6, len);
2568 if (!(quote = strchr(buf, '\"'))) {
2569 if (!(eol_pnt = strchr(buf, '/')))
2570 eol_pnt = strchr(buf,'}');
2574 eol_pnt = strchr(buf+1,'\"');
2580 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2582 struct vm_state *vms_p;
2584 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2585 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2588 if (option_debug > 4)
2589 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2590 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2592 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2593 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2594 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2595 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2596 if (option_debug > 4)
2597 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2599 /* set mailbox to INBOX! */
2600 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2601 init_vm_state(vms_p);
2602 vmstate_insert(vms_p);
2606 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2608 struct vmstate *vlist = NULL;
2611 struct vm_state *vms;
2612 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2613 vms = pthread_getspecific(ts_vmstate.key);
2617 AST_LIST_LOCK(&vmstates);
2618 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2620 ast_debug(3, "error: vms is NULL for %s\n", user);
2623 if (!vlist->vms->imapuser) {
2624 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2628 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2629 AST_LIST_UNLOCK(&vmstates);
2633 AST_LIST_UNLOCK(&vmstates);
2635 ast_debug(3, "%s not found in vmstates\n", user);
2640 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2643 struct vmstate *vlist = NULL;
2644 const char *local_context = S_OR(context, "default");
2647 struct vm_state *vms;
2648 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2649 vms = pthread_getspecific(ts_vmstate.key);
2653 AST_LIST_LOCK(&vmstates);
2654 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2656 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2659 if (!vlist->vms->username || !vlist->vms->context) {
2660 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2664 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);
2666 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2667 ast_debug(3, "Found it!\n");
2668 AST_LIST_UNLOCK(&vmstates);
2672 AST_LIST_UNLOCK(&vmstates);
2674 ast_debug(3, "%s not found in vmstates\n", mailbox);
2679 static void vmstate_insert(struct vm_state *vms)
2682 struct vm_state *altvms;
2684 /* If interactive, it probably already exists, and we should
2685 use the one we already have since it is more up to date.
2686 We can compare the username to find the duplicate */
2687 if (vms->interactive == 1) {
2688 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2690 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2691 vms->newmessages = altvms->newmessages;
2692 vms->oldmessages = altvms->oldmessages;
2693 vms->vmArrayIndex = altvms->vmArrayIndex;
2694 vms->lastmsg = altvms->lastmsg;
2695 vms->curmsg = altvms->curmsg;
2696 /* get a pointer to the persistent store */
2697 vms->persist_vms = altvms;
2698 /* Reuse the mailstream? */
2699 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2700 vms->mailstream = altvms->mailstream;
2702 vms->mailstream = NIL;
2708 if (!(v = ast_calloc(1, sizeof(*v))))
2713 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2715 AST_LIST_LOCK(&vmstates);
2716 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2717 AST_LIST_UNLOCK(&vmstates);
2720 static void vmstate_delete(struct vm_state *vms)
2722 struct vmstate *vc = NULL;
2723 struct vm_state *altvms = NULL;
2725 /* If interactive, we should copy pertinent info
2726 back to the persistent state (to make update immediate) */
2727 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2728 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2729 altvms->newmessages = vms->newmessages;
2730 altvms->oldmessages = vms->oldmessages;
2731 altvms->updated = 1;
2732 vms->mailstream = mail_close(vms->mailstream);
2734 /* Interactive states are not stored within the persistent list */
2738 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2740 AST_LIST_LOCK(&vmstates);
2741 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2742 if (vc->vms == vms) {
2743 AST_LIST_REMOVE_CURRENT(list);
2747 AST_LIST_TRAVERSE_SAFE_END
2748 AST_LIST_UNLOCK(&vmstates);
2751 ast_mutex_destroy(&vc->vms->lock);
2755 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2758 static void set_update(MAILSTREAM * stream)
2760 struct vm_state *vms;
2761 char *mailbox = stream->mailbox, *user;
2762 char buf[1024] = "";
2764 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2765 if (user && option_debug > 2)
2766 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2770 ast_debug(3, "User %s mailbox set for update.\n", user);
2772 vms->updated = 1; /* Set updated flag since mailbox changed */
2775 static void init_vm_state(struct vm_state *vms)
2778 vms->vmArrayIndex = 0;
2779 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2780 vms->msgArray[x] = 0;
2782 ast_mutex_init(&vms->lock);
2785 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
2789 char *fn = is_intro ? vms->introfn : vms->fn;
2791 unsigned long newlen;
2794 if (!body || body == NIL)
2797 ast_mutex_lock(&vms->lock);
2798 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2799 ast_mutex_unlock(&vms->lock);
2800 if (body_content != NIL) {
2801 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2802 /* ast_debug(1,body_content); */
2803 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
2804 /* If the body of the file is empty, return an error */
2808 write_file(filename, (char *) body_decoded, newlen);
2810 ast_debug(5, "Body of message is NULL.\n");
2817 * \brief Get delimiter via mm_list callback
2820 * Determines the delimiter character that is used by the underlying IMAP based mail store.
2822 /* MUTEX should already be held */
2823 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2825 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2826 mail_list(stream, tmp, "*");
2830 * \brief Check Quota for user
2831 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2832 * \param mailbox the mailbox to check the quota for.
2834 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2836 static void check_quota(struct vm_state *vms, char *mailbox) {
2837 ast_mutex_lock(&vms->lock);
2838 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2839 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2840 if (vms && vms->mailstream != NULL) {
2841 imap_getquotaroot(vms->mailstream, mailbox);
2843 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2845 ast_mutex_unlock(&vms->lock);
2848 #endif /* IMAP_STORAGE */
2850 /*! \brief Lock file path
2851 only return failure if ast_lock_path returns 'timeout',
2852 not if the path does not exist or any other reason
2854 static int vm_lock_path(const char *path)
2856 switch (ast_lock_path(path)) {
2857 case AST_LOCK_TIMEOUT:
2866 struct generic_prepare_struct {
2872 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2874 struct generic_prepare_struct *gps = data;
2878 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2879 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2880 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2883 res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
2884 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2885 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2886 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2889 for (i = 0; i < gps->argc; i++)
2890 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2896 * \brief Retrieves a file from an ODBC data store.
2897 * \param dir the path to the file to be retreived.
2898 * \param msgnum the message number, such as within a mailbox folder.
2900 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2901 * 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.
2903 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2904 * The output is the message information file with the name msgnum and the extension .txt
2905 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2907 * \return 0 on success, -1 on error.
2909 static int retrieve_file(char *dir, int msgnum)
2915 void *fdm = MAP_FAILED;
2916 SQLSMALLINT colcount = 0;
2923 SQLSMALLINT datatype;
2924 SQLSMALLINT decimaldigits;
2925 SQLSMALLINT nullable;
2931 char full_fn[PATH_MAX];
2933 char *argv[] = { dir, msgnums };
2934 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2936 struct odbc_obj *obj;
2937 obj = ast_odbc_request_obj(odbc_database, 0);
2939 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2940 c = strchr(fmt, '|');
2943 if (!strcasecmp(fmt, "wav49"))
2945 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
2947 make_file(fn, sizeof(fn), dir, msgnum);
2949 ast_copy_string(fn, dir, sizeof(fn));
2951 /* Create the information file */
2952 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2954 if (!(f = fopen(full_fn, "w+"))) {
2955 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2959 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2960 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
2961 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2963 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2964 ast_odbc_release_obj(obj);
2967 res = SQLFetch(stmt);
2968 if (res == SQL_NO_DATA) {
2969 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2970 ast_odbc_release_obj(obj);
2972 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2973 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2974 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2975 ast_odbc_release_obj(obj);
2978 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2980 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2981 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2982 ast_odbc_release_obj(obj);
2985 res = SQLNumResultCols(stmt, &colcount);
2986 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2987 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2988 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2989 ast_odbc_release_obj(obj);
2993 fprintf(f, "[message]\n");
2994 for (x = 0; x < colcount; x++) {
2996 collen = sizeof(coltitle);
2997 res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
2998 &datatype, &colsize, &decimaldigits, &nullable);
2999 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3000 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3001 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3002 ast_odbc_release_obj(obj);
3005 if (!strcasecmp(coltitle, "recording")) {
3007 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3011 lseek(fd, fdlen - 1, SEEK_SET);
3012 if (write(fd, tmp, 1) != 1) {
3017 /* Read out in small chunks */
3018 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3019 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3020 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3021 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3022 ast_odbc_release_obj(obj);
3025 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3026 munmap(fdm, CHUNKSIZE);
3027 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3028 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3030 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3031 ast_odbc_release_obj(obj);
3036 if (truncate(full_fn, fdlen) < 0) {
3037 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3041 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3042 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3043 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3044 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3045 ast_odbc_release_obj(obj);
3048 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3049 fprintf(f, "%s=%s\n", coltitle, rowdata);
3052 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3053 ast_odbc_release_obj(obj);
3055 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3065 * \brief Determines the highest message number in use for a given user and mailbox folder.
3067 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3069 * This method is used when mailboxes are stored in an ODBC back end.
3070 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3072 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3074 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3081 char *argv[] = { dir };
3082 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3084 struct odbc_obj *obj;
3085 obj = ast_odbc_request_obj(odbc_database, 0);
3087 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3088 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3090 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3091 ast_odbc_release_obj(obj);
3094 res = SQLFetch(stmt);
3095 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3096 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3097 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3098 ast_odbc_release_obj(obj);
3101 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3102 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3103 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3104 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3105 ast_odbc_release_obj(obj);
3108 if (sscanf(rowdata, "%d", &x) != 1)
3109 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3110 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3111 ast_odbc_release_obj(obj);
3113 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3119 * \brief Determines if the specified message exists.
3120 * \param dir the folder the mailbox folder to look for messages.
3121 * \param msgnum the message index to query for.
3123 * This method is used when mailboxes are stored in an ODBC back end.
3125 * \return greater than zero if the message exists, zero when the message does not exist or on error.