2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
21 * \author Mark Spencer <markster@digium.com>
22 * \brief Comedian Mail - Voicemail System
24 * \extref unixODBC (http://www.unixodbc.org/)
25 * \extref A source distribution of University of Washington's IMAP c-client
26 * (http://www.washington.edu/imap/)
30 * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
31 * \ingroup applications
32 * \note This module requires res_adsi to load. This needs to be optional
35 * \note This file is now almost impossible to work with, due to all \#ifdefs.
36 * Feels like the database code before realtime. Someone - please come up
37 * with a plan to clean this up.
41 <depend>res_smdi</depend>
45 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" touch_on_change="apps/app_voicemail.c apps/app_directory.c">
46 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
47 <conflict>ODBC_STORAGE</conflict>
48 <conflict>IMAP_STORAGE</conflict>
49 <defaultenabled>yes</defaultenabled>
51 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
52 <depend>generic_odbc</depend>
54 <conflict>IMAP_STORAGE</conflict>
55 <conflict>FILE_STORAGE</conflict>
56 <defaultenabled>no</defaultenabled>
58 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
59 <depend>imap_tk</depend>
60 <conflict>ODBC_STORAGE</conflict>
61 <conflict>FILE_STORAGE</conflict>
63 <defaultenabled>no</defaultenabled>
74 #ifdef USE_SYSTEM_IMAP
75 #include <imap/c-client.h>
76 #include <imap/imap4r1.h>
77 #include <imap/linkage.h>
78 #elif defined (USE_SYSTEM_CCLIENT)
79 #include <c-client/c-client.h>
80 #include <c-client/imap4r1.h>
81 #include <c-client/linkage.h>
89 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
91 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
97 #if defined(__FreeBSD__) || defined(__OpenBSD__)
101 #include "asterisk/logger.h"
102 #include "asterisk/lock.h"
103 #include "asterisk/file.h"
104 #include "asterisk/channel.h"
105 #include "asterisk/pbx.h"
106 #include "asterisk/config.h"
107 #include "asterisk/say.h"
108 #include "asterisk/module.h"
109 #include "asterisk/adsi.h"
110 #include "asterisk/app.h"
111 #include "asterisk/manager.h"
112 #include "asterisk/dsp.h"
113 #include "asterisk/localtime.h"
114 #include "asterisk/cli.h"
115 #include "asterisk/utils.h"
116 #include "asterisk/stringfields.h"
117 #include "asterisk/smdi.h"
118 #include "asterisk/astobj2.h"
119 #include "asterisk/event.h"
120 #include "asterisk/taskprocessor.h"
121 #include "asterisk/test.h"
124 #include "asterisk/res_odbc.h"
128 #include "asterisk/threadstorage.h"
132 <application name="VoiceMail" language="en_US">
134 Leave a Voicemail message.
137 <parameter name="mailboxs" argsep="&" required="true">
138 <argument name="mailbox1" argsep="@" required="true">
139 <argument name="mailbox" required="true" />
140 <argument name="context" />
142 <argument name="mailbox2" argsep="@" multiple="true">
143 <argument name="mailbox" required="true" />
144 <argument name="context" />
147 <parameter name="options">
150 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
153 <argument name="c" />
154 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
155 if played during the greeting. Context defaults to the current context.</para>
158 <argument name="#" required="true" />
159 <para>Use the specified amount of gain when recording the voicemail
160 message. The units are whole-number decibels (dB). Only works on supported
161 technologies, which is DAHDI only.</para>
164 <para>Skip the playback of instructions for leaving a message to the
165 calling party.</para>
168 <para>Play the <literal>unavailable</literal> greeting.</para>
171 <para>Mark message as <literal>URGENT</literal>.</para>
174 <para>Mark message as <literal>PRIORITY</literal>.</para>
180 <para>This application allows the calling party to leave a message for the specified
181 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
182 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
184 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
187 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
190 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
193 <para>This application will set the following channel variable upon completion:</para>
195 <variable name="VMSTATUS">
196 <para>This indicates the status of the execution of the VoiceMail application.</para>
197 <value name="SUCCESS" />
198 <value name="USEREXIT" />
199 <value name="FAILED" />
204 <application name="VoiceMailMain" language="en_US">
206 Check Voicemail messages.
209 <parameter name="mailbox" required="true" argsep="@">
210 <argument name="mailbox" />
211 <argument name="context" />
213 <parameter name="options">
216 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
217 the mailbox that is entered by the caller.</para>
220 <argument name="#" required="true" />
221 <para>Use the specified amount of gain when recording a voicemail message.
222 The units are whole-number decibels (dB).</para>
225 <para>Skip checking the passcode for the mailbox.</para>
228 <argument name="folder" required="true" />
229 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
230 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
232 <enum name="0"><para>INBOX</para></enum>
233 <enum name="1"><para>Old</para></enum>
234 <enum name="2"><para>Work</para></enum>
235 <enum name="3"><para>Family</para></enum>
236 <enum name="4"><para>Friends</para></enum>
237 <enum name="5"><para>Cust1</para></enum>
238 <enum name="6"><para>Cust2</para></enum>
239 <enum name="7"><para>Cust3</para></enum>
240 <enum name="8"><para>Cust4</para></enum>
241 <enum name="9"><para>Cust5</para></enum>
248 <para>This application allows the calling party to check voicemail messages. A specific
249 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
250 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
251 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
252 <literal>default</literal> context will be used.</para>
255 <application name="MailboxExists" language="en_US">
257 Check to see if Voicemail mailbox exists.
260 <parameter name="mailbox" required="true" argsep="@">
261 <argument name="mailbox" required="true" />
262 <argument name="context" />
264 <parameter name="options">
265 <para>None options.</para>
269 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
270 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
272 <para>This application will set the following channel variable upon completion:</para>
274 <variable name="VMBOXEXISTSSTATUS">
275 <para>This will contain the status of the execution of the MailboxExists application.
276 Possible values include:</para>
277 <value name="SUCCESS" />
278 <value name="FAILED" />
283 <application name="VMAuthenticate" language="en_US">
285 Authenticate with Voicemail passwords.
288 <parameter name="mailbox" required="true" argsep="@">
289 <argument name="mailbox" />
290 <argument name="context" />
292 <parameter name="options">
295 <para>Skip playing the initial prompts.</para>
301 <para>This application behaves the same way as the Authenticate application, but the passwords
302 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
303 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
304 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
308 <application name="VMSayName" language="en_US">
310 Play the name of a voicemail user
313 <parameter name="mailbox" required="true" argsep="@">
314 <argument name="mailbox" />
315 <argument name="context" />
319 <para>This application will say the recorded name of the voicemail user specified as the
320 argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
323 <function name="MAILBOX_EXISTS" language="en_US">
325 Tell if a mailbox is configured.
328 <parameter name="mailbox" required="true" />
329 <parameter name="context" />
332 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
333 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
337 <manager name="VoicemailUsersList" language="en_US">
339 List All Voicemail User Information.
342 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
350 static char imapserver[48];
351 static char imapport[8];
352 static char imapflags[128];
353 static char imapfolder[64];
354 static char imapparentfolder[64] = "\0";
355 static char greetingfolder[64];
356 static char authuser[32];
357 static char authpassword[42];
358 static int imapversion = 1;
360 static int expungeonhangup = 1;
361 static int imapgreetings = 0;
362 static char delimiter = '\0';
367 AST_THREADSTORAGE(ts_vmstate);
369 /* Forward declarations for IMAP */
370 static int init_mailstream(struct vm_state *vms, int box);
371 static void write_file(char *filename, char *buffer, unsigned long len);
372 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
373 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
374 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
375 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
376 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
377 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
378 static void vmstate_insert(struct vm_state *vms);
379 static void vmstate_delete(struct vm_state *vms);
380 static void set_update(MAILSTREAM * stream);
381 static void init_vm_state(struct vm_state *vms);
382 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
383 static void get_mailbox_delimiter(MAILSTREAM *stream);
384 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
385 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
386 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
387 static void update_messages_by_imapuser(const char *user, unsigned long number);
388 static int vm_delete(char *file);
390 static int imap_remove_file (char *dir, int msgnum);
391 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
392 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
393 static void check_quota(struct vm_state *vms, char *mailbox);
394 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
396 struct vm_state *vms;
397 AST_LIST_ENTRY(vmstate) list;
400 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
404 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
406 #define COMMAND_TIMEOUT 5000
407 /* Don't modify these here; set your umask at runtime instead */
408 #define VOICEMAIL_DIR_MODE 0777
409 #define VOICEMAIL_FILE_MODE 0666
410 #define CHUNKSIZE 65536
412 #define VOICEMAIL_CONFIG "voicemail.conf"
413 #define ASTERISK_USERNAME "asterisk"
415 /* Define fast-forward, pause, restart, and reverse keys
416 while listening to a voicemail message - these are
417 strings, not characters */
418 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
419 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
420 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
421 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
422 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
423 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
425 /* Default mail command to mail voicemail. Change it with the
426 mailcmd= command in voicemail.conf */
427 #define SENDMAIL "/usr/sbin/sendmail -t"
429 #define INTRO "vm-intro"
432 #define MAXMSGLIMIT 9999
434 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
436 #define BASELINELEN 72
437 #define BASEMAXINLINE 256
444 #define MAX_DATETIME_FORMAT 512
445 #define MAX_NUM_CID_CONTEXTS 10
447 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
448 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
449 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
450 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
451 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
452 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
453 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
454 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
455 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
456 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
457 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
458 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
459 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
460 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
461 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
462 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
463 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
464 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
465 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
466 #define ERROR_LOCK_PATH -100
467 #define OPERATOR_EXIT 300
479 enum vm_option_flags {
480 OPT_SILENT = (1 << 0),
481 OPT_BUSY_GREETING = (1 << 1),
482 OPT_UNAVAIL_GREETING = (1 << 2),
483 OPT_RECORDGAIN = (1 << 3),
484 OPT_PREPEND_MAILBOX = (1 << 4),
485 OPT_AUTOPLAY = (1 << 6),
486 OPT_DTMFEXIT = (1 << 7),
487 OPT_MESSAGE_Urgent = (1 << 8),
488 OPT_MESSAGE_PRIORITY = (1 << 9)
491 enum vm_option_args {
492 OPT_ARG_RECORDGAIN = 0,
493 OPT_ARG_PLAYFOLDER = 1,
494 OPT_ARG_DTMFEXIT = 2,
495 /* This *must* be the last value in this enum! */
496 OPT_ARG_ARRAY_SIZE = 3,
499 enum vm_passwordlocation {
500 OPT_PWLOC_VOICEMAILCONF = 0,
501 OPT_PWLOC_SPOOLDIR = 1,
502 OPT_PWLOC_USERSCONF = 2,
505 AST_APP_OPTIONS(vm_app_options, {
506 AST_APP_OPTION('s', OPT_SILENT),
507 AST_APP_OPTION('b', OPT_BUSY_GREETING),
508 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
509 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
510 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
511 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
512 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
513 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
514 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
517 static int load_config(int reload);
519 /*! \page vmlang Voicemail Language Syntaxes Supported
521 \par Syntaxes supported, not really language codes.
528 \arg \b pt - Portuguese
529 \arg \b pt_BR - Portuguese (Brazil)
531 \arg \b no - Norwegian
533 \arg \b tw - Chinese (Taiwan)
534 \arg \b ua - Ukrainian
536 German requires the following additional soundfile:
537 \arg \b 1F einE (feminine)
539 Spanish requires the following additional soundfile:
540 \arg \b 1M un (masculine)
542 Dutch, Portuguese & Spanish require the following additional soundfiles:
543 \arg \b vm-INBOXs singular of 'new'
544 \arg \b vm-Olds singular of 'old/heard/read'
547 \arg \b vm-INBOX nieuwe (nl)
548 \arg \b vm-Old oude (nl)
551 \arg \b vm-new-a 'new', feminine singular accusative
552 \arg \b vm-new-e 'new', feminine plural accusative
553 \arg \b vm-new-ych 'new', feminine plural genitive
554 \arg \b vm-old-a 'old', feminine singular accusative
555 \arg \b vm-old-e 'old', feminine plural accusative
556 \arg \b vm-old-ych 'old', feminine plural genitive
557 \arg \b digits/1-a 'one', not always same as 'digits/1'
558 \arg \b digits/2-ie 'two', not always same as 'digits/2'
561 \arg \b vm-nytt singular of 'new'
562 \arg \b vm-nya plural of 'new'
563 \arg \b vm-gammalt singular of 'old'
564 \arg \b vm-gamla plural of 'old'
565 \arg \b digits/ett 'one', not always same as 'digits/1'
568 \arg \b vm-ny singular of 'new'
569 \arg \b vm-nye plural of 'new'
570 \arg \b vm-gammel singular of 'old'
571 \arg \b vm-gamle plural of 'old'
579 Italian requires the following additional soundfile:
583 \arg \b vm-nuovi new plural
584 \arg \b vm-vecchio old
585 \arg \b vm-vecchi old plural
587 Chinese (Taiwan) requires the following additional soundfile:
588 \arg \b vm-tong A class-word for call (tong1)
589 \arg \b vm-ri A class-word for day (ri4)
590 \arg \b vm-you You (ni3)
591 \arg \b vm-haveno Have no (mei2 you3)
592 \arg \b vm-have Have (you3)
593 \arg \b vm-listen To listen (yao4 ting1)
596 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
597 spelled among others when you have to change folder. For the above reasons, vm-INBOX
598 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
607 unsigned char iobuf[BASEMAXINLINE];
610 /*! Structure for linked list of users
611 * Use ast_vm_user_destroy() to free one of these structures. */
613 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
614 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
615 char password[80]; /*!< Secret pin code, numbers only */
616 char fullname[80]; /*!< Full name, for directory app */
617 char email[80]; /*!< E-mail address */
618 char *emailsubject; /*!< E-mail subject */
619 char *emailbody; /*!< E-mail body */
620 char pager[80]; /*!< E-mail address to pager (no attachment) */
621 char serveremail[80]; /*!< From: Mail address */
622 char mailcmd[160]; /*!< Configurable mail command */
623 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
624 char zonetag[80]; /*!< Time zone */
627 char uniqueid[80]; /*!< Unique integer identifier */
629 char attachfmt[20]; /*!< Attachment format */
630 unsigned int flags; /*!< VM_ flags */
632 int minsecs; /*!< Minimum number of seconds per message for this mailbox */
633 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
634 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
635 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
636 int passwordlocation; /*!< Storage location of the password */
638 char imapuser[80]; /*!< IMAP server login */
639 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
640 char imapfolder[64]; /*!< IMAP voicemail folder */
641 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
642 int imapversion; /*!< If configuration changes, use the new values */
644 double volgain; /*!< Volume gain for voicemails sent via email */
645 AST_LIST_ENTRY(ast_vm_user) list;
648 /*! Voicemail time zones */
650 AST_LIST_ENTRY(vm_zone) list;
653 char msg_format[512];
656 #define VMSTATE_MAX_MSG_ARRAY 256
658 /*! Voicemail mailbox state */
663 char curdir[PATH_MAX];
664 char vmbox[PATH_MAX];
666 char intro[PATH_MAX];
678 int updated; /*!< decremented on each mail check until 1 -allows delay */
679 long msgArray[VMSTATE_MAX_MSG_ARRAY];
680 MAILSTREAM *mailstream;
682 char imapuser[80]; /*!< IMAP server login */
683 char imapfolder[64]; /*!< IMAP voicemail folder */
686 char introfn[PATH_MAX]; /*!< Name of prepended file */
687 unsigned int quota_limit;
688 unsigned int quota_usage;
689 struct vm_state *persist_vms;
694 static char odbc_database[80];
695 static char odbc_table[80];
696 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
697 #define DISPOSE(a,b) remove_file(a,b)
698 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
699 #define EXISTS(a,b,c,d) (message_exists(a,b))
700 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
701 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
702 #define DELETE(a,b,c,d) (delete_file(a,b))
705 #define DISPOSE(a,b) (imap_remove_file(a,b))
706 #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))
707 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
708 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
709 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
710 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
711 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
713 #define RETRIEVE(a,b,c,d)
715 #define STORE(a,b,c,d,e,f,g,h,i,j)
716 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
717 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
718 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
719 #define DELETE(a,b,c,d) (vm_delete(c))
723 static char VM_SPOOL_DIR[PATH_MAX];
725 static char ext_pass_cmd[128];
726 static char ext_pass_check_cmd[128];
730 #define PWDCHANGE_INTERNAL (1 << 1)
731 #define PWDCHANGE_EXTERNAL (1 << 2)
732 static int pwdchange = PWDCHANGE_INTERNAL;
735 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
738 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
740 # define tdesc "Comedian Mail (Voicemail System)"
744 static char userscontext[AST_MAX_EXTENSION] = "default";
746 static char *addesc = "Comedian Mail";
748 /* Leave a message */
749 static char *app = "VoiceMail";
751 /* Check mail, control, etc */
752 static char *app2 = "VoiceMailMain";
754 static char *app3 = "MailboxExists";
755 static char *app4 = "VMAuthenticate";
757 static char *sayname_app = "VMSayName";
759 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
760 static AST_LIST_HEAD_STATIC(zones, vm_zone);
761 static char zonetag[80];
762 static int maxsilence;
764 static int maxdeletedmsg;
765 static int silencethreshold = 128;
766 static char serveremail[80];
767 static char mailcmd[160]; /* Configurable mail cmd */
768 static char externnotify[160];
769 static struct ast_smdi_interface *smdi_iface = NULL;
770 static char vmfmts[80];
771 static double volgain;
772 static int vmminsecs;
773 static int vmmaxsecs;
776 static int maxlogins;
777 static int minpassword;
778 static int passwordlocation;
780 /*! Poll mailboxes for changes since there is something external to
781 * app_voicemail that may change them. */
782 static unsigned int poll_mailboxes;
784 /*! Polling frequency */
785 static unsigned int poll_freq;
786 /*! By default, poll every 30 seconds */
787 #define DEFAULT_POLL_FREQ 30
789 AST_MUTEX_DEFINE_STATIC(poll_lock);
790 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
791 static pthread_t poll_thread = AST_PTHREADT_NULL;
792 static unsigned char poll_thread_run;
794 /*! Subscription to ... MWI event subscriptions */
795 static struct ast_event_sub *mwi_sub_sub;
796 /*! Subscription to ... MWI event un-subscriptions */
797 static struct ast_event_sub *mwi_unsub_sub;
800 * \brief An MWI subscription
802 * This is so we can keep track of which mailboxes are subscribed to.
803 * This way, we know which mailboxes to poll when the pollmailboxes
804 * option is being used.
807 AST_RWLIST_ENTRY(mwi_sub) entry;
815 struct mwi_sub_task {
821 static struct ast_taskprocessor *mwi_subscription_tps;
823 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
825 /* custom audio control prompts for voicemail playback */
826 static char listen_control_forward_key[12];
827 static char listen_control_reverse_key[12];
828 static char listen_control_pause_key[12];
829 static char listen_control_restart_key[12];
830 static char listen_control_stop_key[12];
832 /* custom password sounds */
833 static char vm_password[80] = "vm-password";
834 static char vm_newpassword[80] = "vm-newpassword";
835 static char vm_passchanged[80] = "vm-passchanged";
836 static char vm_reenterpassword[80] = "vm-reenterpassword";
837 static char vm_mismatch[80] = "vm-mismatch";
838 static char vm_invalid_password[80] = "vm-invalid-password";
839 static char vm_pls_try_again[80] = "vm-pls-try-again";
841 static struct ast_flags globalflags = {0};
843 static int saydurationminfo;
845 static char dialcontext[AST_MAX_CONTEXT] = "";
846 static char callcontext[AST_MAX_CONTEXT] = "";
847 static char exitcontext[AST_MAX_CONTEXT] = "";
849 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
852 static char *emailbody = NULL;
853 static char *emailsubject = NULL;
854 static char *pagerbody = NULL;
855 static char *pagersubject = NULL;
856 static char fromstring[100];
857 static char pagerfromstring[100];
858 static char charset[32] = "ISO-8859-1";
860 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
861 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
862 static int adsiver = 1;
863 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
864 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
866 /* Forward declarations - generic */
867 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
868 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);
869 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
870 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
871 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
872 signed char record_gain, struct vm_state *vms, char *flag);
873 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
874 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
875 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);
876 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);
877 static void apply_options(struct ast_vm_user *vmu, const char *options);
878 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);
879 static int is_valid_dtmf(const char *key);
880 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
881 static int write_password_to_file(const char *secretfn, const char *password);
883 struct ao2_container *inprocess_container;
891 static int inprocess_hash_fn(const void *obj, const int flags)
893 const struct inprocess *i = obj;
894 return atoi(i->mailbox);
897 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
899 struct inprocess *i = obj, *j = arg;
900 if (!strcmp(i->mailbox, j->mailbox)) {
903 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
906 static int inprocess_count(const char *context, const char *mailbox, int delta)
908 struct inprocess *i, *arg = alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
909 arg->context = arg->mailbox + strlen(mailbox) + 1;
910 strcpy(arg->mailbox, mailbox); /* SAFE */
911 strcpy(arg->context, context); /* SAFE */
912 ao2_lock(inprocess_container);
913 if ((i = ao2_find(inprocess_container, arg, 0))) {
914 int ret = ast_atomic_fetchadd_int(&i->count, delta);
915 ao2_unlock(inprocess_container);
919 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
920 ao2_unlock(inprocess_container);
923 i->context = i->mailbox + strlen(mailbox) + 1;
924 strcpy(i->mailbox, mailbox); /* SAFE */
925 strcpy(i->context, context); /* SAFE */
927 ao2_link(inprocess_container, i);
928 ao2_unlock(inprocess_container);
933 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
934 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
938 * \brief Strips control and non 7-bit clean characters from input string.
940 * \note To map control and none 7-bit characters to a 7-bit clean characters
941 * please use ast_str_encode_mine().
943 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
946 for (; *input; input++) {
951 if (bufptr == buf + buflen - 1) {
961 * \brief Sets default voicemail system options to a voicemail user.
963 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
964 * - all the globalflags
965 * - the saydurationminfo
969 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
972 static void populate_defaults(struct ast_vm_user *vmu)
974 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
975 vmu->passwordlocation = passwordlocation;
976 if (saydurationminfo) {
977 vmu->saydurationm = saydurationminfo;
979 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
980 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
981 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
982 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
984 vmu->minsecs = vmminsecs;
987 vmu->maxsecs = vmmaxsecs;
990 vmu->maxmsg = maxmsg;
993 vmu->maxdeletedmsg = maxdeletedmsg;
995 vmu->volgain = volgain;
996 vmu->emailsubject = NULL;
997 vmu->emailbody = NULL;
999 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
1004 * \brief Sets a a specific property value.
1005 * \param vmu The voicemail user object to work with.
1006 * \param var The name of the property to be set.
1007 * \param value The value to be set to the property.
1009 * The property name must be one of the understood properties. See the source for details.
1011 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1014 if (!strcasecmp(var, "attach")) {
1015 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1016 } else if (!strcasecmp(var, "attachfmt")) {
1017 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1018 } else if (!strcasecmp(var, "serveremail")) {
1019 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1020 } else if (!strcasecmp(var, "language")) {
1021 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1022 } else if (!strcasecmp(var, "tz")) {
1023 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1025 } else if (!strcasecmp(var, "imapuser")) {
1026 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1027 vmu->imapversion = imapversion;
1028 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1029 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1030 vmu->imapversion = imapversion;
1031 } else if (!strcasecmp(var, "imapfolder")) {
1032 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1033 } else if (!strcasecmp(var, "imapvmshareid")) {
1034 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1035 vmu->imapversion = imapversion;
1037 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1038 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1039 } else if (!strcasecmp(var, "saycid")){
1040 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1041 } else if (!strcasecmp(var, "sendvoicemail")){
1042 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1043 } else if (!strcasecmp(var, "review")){
1044 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1045 } else if (!strcasecmp(var, "tempgreetwarn")){
1046 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1047 } else if (!strcasecmp(var, "messagewrap")){
1048 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1049 } else if (!strcasecmp(var, "operator")) {
1050 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1051 } else if (!strcasecmp(var, "envelope")){
1052 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1053 } else if (!strcasecmp(var, "moveheard")){
1054 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1055 } else if (!strcasecmp(var, "sayduration")){
1056 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1057 } else if (!strcasecmp(var, "saydurationm")){
1058 if (sscanf(value, "%30d", &x) == 1) {
1059 vmu->saydurationm = x;
1061 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1063 } else if (!strcasecmp(var, "forcename")){
1064 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1065 } else if (!strcasecmp(var, "forcegreetings")){
1066 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1067 } else if (!strcasecmp(var, "callback")) {
1068 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1069 } else if (!strcasecmp(var, "dialout")) {
1070 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1071 } else if (!strcasecmp(var, "exitcontext")) {
1072 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1073 } else if (!strcasecmp(var, "minsecs")) {
1074 if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1077 ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1078 vmu->minsecs = vmminsecs;
1080 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1081 vmu->maxsecs = atoi(value);
1082 if (vmu->maxsecs <= 0) {
1083 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1084 vmu->maxsecs = vmmaxsecs;
1086 vmu->maxsecs = atoi(value);
1088 if (!strcasecmp(var, "maxmessage"))
1089 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1090 } else if (!strcasecmp(var, "maxmsg")) {
1091 vmu->maxmsg = atoi(value);
1092 /* Accept maxmsg=0 (Greetings only voicemail) */
1093 if (vmu->maxmsg < 0) {
1094 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1095 vmu->maxmsg = MAXMSG;
1096 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1097 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1098 vmu->maxmsg = MAXMSGLIMIT;
1100 } else if (!strcasecmp(var, "nextaftercmd")) {
1101 ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
1102 } else if (!strcasecmp(var, "backupdeleted")) {
1103 if (sscanf(value, "%30d", &x) == 1)
1104 vmu->maxdeletedmsg = x;
1105 else if (ast_true(value))
1106 vmu->maxdeletedmsg = MAXMSG;
1108 vmu->maxdeletedmsg = 0;
1110 if (vmu->maxdeletedmsg < 0) {
1111 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1112 vmu->maxdeletedmsg = MAXMSG;
1113 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1114 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1115 vmu->maxdeletedmsg = MAXMSGLIMIT;
1117 } else if (!strcasecmp(var, "volgain")) {
1118 sscanf(value, "%30lf", &vmu->volgain);
1119 } else if (!strcasecmp(var, "passwordlocation")) {
1120 if (!strcasecmp(value, "spooldir")) {
1121 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1123 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1125 } else if (!strcasecmp(var, "options")) {
1126 apply_options(vmu, value);
1130 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1132 int fds[2], pid = 0;
1134 memset(buf, 0, len);
1137 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1140 pid = ast_safe_fork(0);
1146 snprintf(buf, len, "FAILURE: Fork failed");
1150 if (read(fds[0], buf, len) < 0) {
1151 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1156 AST_DECLARE_APP_ARGS(arg,
1159 char *mycmd = ast_strdupa(command);
1162 dup2(fds[1], STDOUT_FILENO);
1164 ast_close_fds_above_n(STDOUT_FILENO);
1166 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1168 execv(arg.v[0], arg.v);
1169 printf("FAILURE: %s", strerror(errno));
1177 * \brief Check that password meets minimum required length
1178 * \param vmu The voicemail user to change the password for.
1179 * \param password The password string to check
1181 * \return zero on ok, 1 on not ok.
1183 static int check_password(struct ast_vm_user *vmu, char *password)
1185 /* check minimum length */
1186 if (strlen(password) < minpassword)
1188 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1189 char cmd[255], buf[255];
1191 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1193 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1194 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1195 ast_debug(5, "Result: %s\n", buf);
1196 if (!strncasecmp(buf, "VALID", 5)) {
1197 ast_debug(3, "Passed password check: '%s'\n", buf);
1199 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1200 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1203 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1212 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1213 * \param vmu The voicemail user to change the password for.
1214 * \param password The new value to be set to the password for this user.
1216 * This only works if there is a realtime engine configured.
1217 * This is called from the (top level) vm_change_password.
1219 * \return zero on success, -1 on error.
1221 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1224 if (!strcmp(vmu->password, password)) {
1225 /* No change (but an update would return 0 rows updated, so we opt out here) */
1229 if (strlen(password) > 10) {
1230 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1232 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1233 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1240 * \brief Destructively Parse options and apply.
1242 static void apply_options(struct ast_vm_user *vmu, const char *options)
1247 stringp = ast_strdupa(options);
1248 while ((s = strsep(&stringp, "|"))) {
1250 if ((var = strsep(&value, "=")) && value) {
1251 apply_option(vmu, var, value);
1257 * \brief Loads the options specific to a voicemail user.
1259 * This is called when a vm_user structure is being set up, such as from load_options.
1261 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1263 for (; var; var = var->next) {
1264 if (!strcasecmp(var->name, "vmsecret")) {
1265 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1266 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1267 if (ast_strlen_zero(retval->password))
1268 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1269 } else if (!strcasecmp(var->name, "uniqueid")) {
1270 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1271 } else if (!strcasecmp(var->name, "pager")) {
1272 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1273 } else if (!strcasecmp(var->name, "email")) {
1274 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1275 } else if (!strcasecmp(var->name, "fullname")) {
1276 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1277 } else if (!strcasecmp(var->name, "context")) {
1278 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1279 } else if (!strcasecmp(var->name, "emailsubject")) {
1280 retval->emailsubject = ast_strdup(var->value);
1281 } else if (!strcasecmp(var->name, "emailbody")) {
1282 retval->emailbody = ast_strdup(var->value);
1284 } else if (!strcasecmp(var->name, "imapuser")) {
1285 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1286 retval->imapversion = imapversion;
1287 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1288 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1289 retval->imapversion = imapversion;
1290 } else if (!strcasecmp(var->name, "imapfolder")) {
1291 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1292 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1293 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1294 retval->imapversion = imapversion;
1297 apply_option(retval, var->name, var->value);
1302 * \brief Determines if a DTMF key entered is valid.
1303 * \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.
1305 * Tests the character entered against the set of valid DTMF characters.
1306 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1308 static int is_valid_dtmf(const char *key)
1311 char *local_key = ast_strdupa(key);
1313 for (i = 0; i < strlen(key); ++i) {
1314 if (!strchr(VALID_DTMF, *local_key)) {
1315 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1324 * \brief Finds a voicemail user from the realtime engine.
1329 * 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.
1331 * \return The ast_vm_user structure for the user that was found.
1333 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1335 struct ast_variable *var;
1336 struct ast_vm_user *retval;
1338 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1340 ast_set_flag(retval, VM_ALLOCED);
1342 memset(retval, 0, sizeof(*retval));
1344 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1345 populate_defaults(retval);
1346 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1347 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1349 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1351 apply_options_full(retval, var);
1352 ast_variables_destroy(var);
1363 * \brief Finds a voicemail user from the users file or the realtime engine.
1368 * \return The ast_vm_user structure for the user that was found.
1370 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1372 /* This function could be made to generate one from a database, too */
1373 struct ast_vm_user *vmu = NULL, *cur;
1374 AST_LIST_LOCK(&users);
1376 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1377 context = "default";
1379 AST_LIST_TRAVERSE(&users, cur, list) {
1381 if (cur->imapversion != imapversion) {
1385 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1387 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1391 /* Make a copy, so that on a reload, we have no race */
1392 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1393 memcpy(vmu, cur, sizeof(*vmu));
1394 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1395 AST_LIST_NEXT(vmu, list) = NULL;
1398 vmu = find_user_realtime(ivm, context, mailbox);
1399 AST_LIST_UNLOCK(&users);
1404 * \brief Resets a user password to a specified password.
1409 * This does the actual change password work, called by the vm_change_password() function.
1411 * \return zero on success, -1 on error.
1413 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1415 /* This function could be made to generate one from a database, too */
1416 struct ast_vm_user *cur;
1418 AST_LIST_LOCK(&users);
1419 AST_LIST_TRAVERSE(&users, cur, list) {
1420 if ((!context || !strcasecmp(context, cur->context)) &&
1421 (!strcasecmp(mailbox, cur->mailbox)))
1425 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1428 AST_LIST_UNLOCK(&users);
1433 * \brief The handler for the change password option.
1434 * \param vmu The voicemail user to work with.
1435 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1436 * 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.
1437 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1439 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1441 struct ast_config *cfg = NULL;
1442 struct ast_variable *var = NULL;
1443 struct ast_category *cat = NULL;
1444 char *category = NULL, *value = NULL, *new = NULL;
1445 const char *tmp = NULL;
1446 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1447 char secretfn[PATH_MAX] = "";
1450 if (!change_password_realtime(vmu, newpassword))
1453 /* check if we should store the secret in the spool directory next to the messages */
1454 switch (vmu->passwordlocation) {
1455 case OPT_PWLOC_SPOOLDIR:
1456 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1457 if (write_password_to_file(secretfn, newpassword) == 0) {
1458 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1459 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1460 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1463 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1466 case OPT_PWLOC_VOICEMAILCONF:
1467 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1468 while ((category = ast_category_browse(cfg, category))) {
1469 if (!strcasecmp(category, vmu->context)) {
1470 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1471 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1474 value = strstr(tmp, ",");
1476 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1479 new = alloca((strlen(value) + strlen(newpassword) + 1));
1480 sprintf(new, "%s%s", newpassword, value);
1481 if (!(cat = ast_category_get(cfg, category))) {
1482 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1485 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1489 /* save the results */
1491 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1492 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1493 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1498 case OPT_PWLOC_USERSCONF:
1499 /* check users.conf and update the password stored for the mailbox */
1500 /* if no vmsecret entry exists create one. */
1501 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1502 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1503 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1504 ast_debug(4, "users.conf: %s\n", category);
1505 if (!strcasecmp(category, vmu->mailbox)) {
1506 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1507 ast_debug(3, "looks like we need to make vmsecret!\n");
1508 var = ast_variable_new("vmsecret", newpassword, "");
1512 new = alloca(strlen(newpassword) + 1);
1513 sprintf(new, "%s", newpassword);
1514 if (!(cat = ast_category_get(cfg, category))) {
1515 ast_debug(4, "failed to get category!\n");
1520 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1522 ast_variable_append(cat, var);
1528 /* save the results and clean things up */
1530 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1531 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1532 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1538 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1541 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1542 if (!ast_safe_system(buf)) {
1543 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1544 /* Reset the password in memory, too */
1545 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1550 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1551 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1552 * \param len The length of the path string that was written out.
1554 * The path is constructed as
1555 * VM_SPOOL_DIRcontext/ext/folder
1557 * \return zero on success, -1 on error.
1559 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1561 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1565 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1566 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1567 * \param len The length of the path string that was written out.
1569 * The path is constructed as
1570 * VM_SPOOL_DIRcontext/ext/folder
1572 * \return zero on success, -1 on error.
1574 static int make_file(char *dest, const int len, const char *dir, const int num)
1576 return snprintf(dest, len, "%s/msg%04d", dir, num);
1579 /* same as mkstemp, but return a FILE * */
1580 static FILE *vm_mkftemp(char *template)
1583 int pfd = mkstemp(template);
1584 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1586 p = fdopen(pfd, "w+");
1595 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1596 * \param dest String. base directory.
1597 * \param len Length of dest.
1598 * \param context String. Ignored if is null or empty string.
1599 * \param ext String. Ignored if is null or empty string.
1600 * \param folder String. Ignored if is null or empty string.
1601 * \return -1 on failure, 0 on success.
1603 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1605 mode_t mode = VOICEMAIL_DIR_MODE;
1608 make_dir(dest, len, context, ext, folder);
1609 if ((res = ast_mkdir(dest, mode))) {
1610 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1616 static const char * const mailbox_folders[] = {
1635 static const char *mbox(struct ast_vm_user *vmu, int id)
1638 if (vmu && id == 0) {
1639 return vmu->imapfolder;
1642 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1645 static int get_folder_by_name(const char *name)
1649 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1650 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1658 static void free_user(struct ast_vm_user *vmu)
1660 if (ast_test_flag(vmu, VM_ALLOCED)) {
1661 if (vmu->emailbody != NULL) {
1662 ast_free(vmu->emailbody);
1663 vmu->emailbody = NULL;
1665 if (vmu->emailsubject != NULL) {
1666 ast_free(vmu->emailsubject);
1667 vmu->emailsubject = NULL;
1673 /* All IMAP-specific functions should go in this block. This
1674 * keeps them from being spread out all over the code */
1676 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1679 struct vm_state *vms;
1680 unsigned long messageNum;
1682 /* If greetings aren't stored in IMAP, just delete the file */
1683 if (msgnum < 0 && !imapgreetings) {
1684 ast_filedelete(file, NULL);
1688 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1689 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);
1693 /* find real message number based on msgnum */
1694 /* this may be an index into vms->msgArray based on the msgnum. */
1695 messageNum = vms->msgArray[msgnum];
1696 if (messageNum == 0) {
1697 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1700 if (option_debug > 2)
1701 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1702 /* delete message */
1703 snprintf (arg, sizeof(arg), "%lu", messageNum);
1704 ast_mutex_lock(&vms->lock);
1705 mail_setflag (vms->mailstream, arg, "\\DELETED");
1706 mail_expunge(vms->mailstream);
1707 ast_mutex_unlock(&vms->lock);
1710 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1712 struct vm_state *vms_p;
1713 char *file, *filename;
1718 /* This function is only used for retrieval of IMAP greetings
1719 * regular messages are not retrieved this way, nor are greetings
1720 * if they are stored locally*/
1721 if (msgnum > -1 || !imapgreetings) {
1724 file = strrchr(ast_strdupa(dir), '/');
1728 ast_debug (1, "Failed to procure file name from directory passed.\n");
1733 /* check if someone is accessing this box right now... */
1734 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
1735 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1736 /* Unlike when retrieving a message, it is reasonable not to be able to find a
1737 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1738 * that's all we need to do.
1740 if (!(vms_p = create_vm_state_from_user(vmu))) {
1741 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1746 /* Greetings will never have a prepended message */
1747 *vms_p->introfn = '\0';
1749 ast_mutex_lock(&vms_p->lock);
1750 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1751 if (!vms_p->mailstream) {
1752 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1753 ast_mutex_unlock(&vms_p->lock);
1757 /*XXX Yuck, this could probably be done a lot better */
1758 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1759 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1760 /* We have the body, now we extract the file name of the first attachment. */
1761 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1762 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1764 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1765 ast_mutex_unlock(&vms_p->lock);
1768 filename = strsep(&attachment, ".");
1769 if (!strcmp(filename, file)) {
1770 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1771 vms_p->msgArray[vms_p->curmsg] = i + 1;
1772 save_body(body, vms_p, "2", attachment, 0);
1773 ast_mutex_unlock(&vms_p->lock);
1777 ast_mutex_unlock(&vms_p->lock);
1782 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1785 char *header_content;
1786 char *attachedfilefmt;
1788 struct vm_state *vms;
1789 char text_file[PATH_MAX];
1790 FILE *text_file_ptr;
1792 struct ast_vm_user *vmu;
1794 if (!(vmu = find_user(NULL, context, mailbox))) {
1795 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1800 if (imapgreetings) {
1801 res = imap_retrieve_greeting(dir, msgnum, vmu);
1809 /* Before anything can happen, we need a vm_state so that we can
1810 * actually access the imap server through the vms->mailstream
1812 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1813 /* This should not happen. If it does, then I guess we'd
1814 * need to create the vm_state, extract which mailbox to
1815 * open, and then set up the msgArray so that the correct
1816 * IMAP message could be accessed. If I have seen correctly
1817 * though, the vms should be obtainable from the vmstates list
1818 * and should have its msgArray properly set up.
1820 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1825 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1826 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1828 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1829 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1834 if (option_debug > 2)
1835 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1836 if (vms->msgArray[msgnum] == 0) {
1837 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1842 /* This will only work for new messages... */
1843 ast_mutex_lock(&vms->lock);
1844 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1845 ast_mutex_unlock(&vms->lock);
1846 /* empty string means no valid header */
1847 if (ast_strlen_zero(header_content)) {
1848 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1853 ast_mutex_lock(&vms->lock);
1854 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1855 ast_mutex_unlock(&vms->lock);
1857 /* We have the body, now we extract the file name of the first attachment. */
1858 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1859 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1861 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1866 /* Find the format of the attached file */
1868 strsep(&attachedfilefmt, ".");
1869 if (!attachedfilefmt) {
1870 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1875 save_body(body, vms, "2", attachedfilefmt, 0);
1876 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1877 *vms->introfn = '\0';
1880 /* Get info from headers!! */
1881 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1883 if (!(text_file_ptr = fopen(text_file, "w"))) {
1884 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1887 fprintf(text_file_ptr, "%s\n", "[message]");
1889 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1890 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1891 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1892 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1893 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1894 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1895 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1896 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1897 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1898 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1899 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1900 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1901 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1902 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1903 fclose(text_file_ptr);
1910 static int folder_int(const char *folder)
1912 /*assume a NULL folder means INBOX*/
1915 if (!strcasecmp(folder, imapfolder))
1917 else if (!strcasecmp(folder, "Old"))
1919 else if (!strcasecmp(folder, "Work"))
1921 else if (!strcasecmp(folder, "Family"))
1923 else if (!strcasecmp(folder, "Friends"))
1925 else if (!strcasecmp(folder, "Cust1"))
1927 else if (!strcasecmp(folder, "Cust2"))
1929 else if (!strcasecmp(folder, "Cust3"))
1931 else if (!strcasecmp(folder, "Cust4"))
1933 else if (!strcasecmp(folder, "Cust5"))
1935 else /*assume they meant INBOX if folder is not found otherwise*/
1939 static int __messagecount(const char *context, const char *mailbox, const char *folder)
1944 struct ast_vm_user *vmu, vmus;
1945 struct vm_state *vms_p;
1947 int fold = folder_int(folder);
1950 /* If URGENT, then look at INBOX */
1956 if (ast_strlen_zero(mailbox))
1959 /* We have to get the user before we can open the stream! */
1960 vmu = find_user(&vmus, context, mailbox);
1962 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1965 /* No IMAP account available */
1966 if (vmu->imapuser[0] == '\0') {
1967 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1972 /* No IMAP account available */
1973 if (vmu->imapuser[0] == '\0') {
1974 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1979 /* check if someone is accessing this box right now... */
1980 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1982 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1985 ast_debug(3, "Returning before search - user is logged in\n");
1986 if (fold == 0) { /* INBOX */
1987 return vms_p->newmessages;
1989 if (fold == 1) { /* Old messages */
1990 return vms_p->oldmessages;
1992 if (fold == 11) {/*Urgent messages*/
1993 return vms_p->urgentmessages;
1997 /* add one if not there... */
1998 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2000 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2004 vms_p = create_vm_state_from_user(vmu);
2006 ret = init_mailstream(vms_p, fold);
2007 if (!vms_p->mailstream) {
2008 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2012 ast_mutex_lock(&vms_p->lock);
2013 pgm = mail_newsearchpgm ();
2014 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2015 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2021 /* In the special case where fold is 1 (old messages) we have to do things a bit
2022 * differently. Old messages are stored in the INBOX but are marked as "seen"
2028 /* look for urgent messages */
2036 vms_p->vmArrayIndex = 0;
2037 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2038 if (fold == 0 && urgent == 0)
2039 vms_p->newmessages = vms_p->vmArrayIndex;
2041 vms_p->oldmessages = vms_p->vmArrayIndex;
2042 if (fold == 0 && urgent == 1)
2043 vms_p->urgentmessages = vms_p->vmArrayIndex;
2044 /*Freeing the searchpgm also frees the searchhdr*/
2045 mail_free_searchpgm(&pgm);
2046 ast_mutex_unlock(&vms_p->lock);
2048 return vms_p->vmArrayIndex;
2050 ast_mutex_lock(&vms_p->lock);
2051 mail_ping(vms_p->mailstream);
2052 ast_mutex_unlock(&vms_p->lock);
2057 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2059 /* Check if mailbox is full */
2060 check_quota(vms, vmu->imapfolder);
2061 if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2062 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2063 ast_play_and_wait(chan, "vm-mailboxfull");
2067 /* Check if we have exceeded maxmsg */
2068 if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, 0)) {
2069 ast_log(AST_LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u > %u)\n", msgnum, vmu->maxmsg);
2070 ast_play_and_wait(chan, "vm-mailboxfull");
2078 * \brief Gets the number of messages that exist in a mailbox folder.
2083 * This method is used when IMAP backend is used.
2084 * \return The number of messages in this mailbox folder (zero or more).
2086 static int messagecount(const char *context, const char *mailbox, const char *folder)
2088 if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2089 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2091 return __messagecount(context, mailbox, folder);
2095 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
2097 char *myserveremail = serveremail;
2099 char introfn[PATH_MAX];
2103 char tmp[80] = "/tmp/astmail-XXXXXX";
2108 int ret; /* for better error checking */
2109 char *imap_flags = NIL;
2110 int msgcount = (messagecount(vmu->context, vmu->mailbox, "INBOX") + messagecount(vmu->context, vmu->mailbox, "Old"));
2112 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2113 if (msgnum < 0 && !imapgreetings) {
2117 if (imap_check_limits(chan, vms, vmu, msgcount)) {
2121 /* Set urgent flag for IMAP message */
2122 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2123 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2124 imap_flags = "\\FLAGGED";
2127 /* Attach only the first format */
2128 fmt = ast_strdupa(fmt);
2130 strsep(&stringp, "|");
2132 if (!ast_strlen_zero(vmu->serveremail))
2133 myserveremail = vmu->serveremail;
2136 make_file(fn, sizeof(fn), dir, msgnum);
2138 ast_copy_string (fn, dir, sizeof(fn));
2140 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2141 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2145 if (ast_strlen_zero(vmu->email)) {
2146 /* We need the vmu->email to be set when we call make_email_file, but
2147 * if we keep it set, a duplicate e-mail will be created. So at the end
2148 * of this function, we will revert back to an empty string if tempcopy
2151 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2155 if (!strcmp(fmt, "wav49"))
2157 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2159 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2161 if (!(p = vm_mkftemp(tmp))) {
2162 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2164 *(vmu->email) = '\0';
2168 if (msgnum < 0 && imapgreetings) {
2169 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2170 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2173 imap_delete_old_greeting(fn, vms);
2176 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);
2177 /* read mail file to memory */
2180 if (!(buf = ast_malloc(len + 1))) {
2181 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2184 *(vmu->email) = '\0';
2187 if (fread(buf, len, 1, p) < len) {
2189 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2193 ((char *) buf)[len] = '\0';
2194 INIT(&str, mail_string, buf, len);
2195 ret = init_mailstream(vms, NEW_FOLDER);
2197 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2198 ast_mutex_lock(&vms->lock);
2199 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2200 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2201 ast_mutex_unlock(&vms->lock);
2206 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2212 ast_debug(3, "%s stored\n", fn);
2215 *(vmu->email) = '\0';
2222 * \brief Gets the number of messages that exist in the inbox folder.
2223 * \param mailbox_context
2224 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2225 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2226 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2228 * This method is used when IMAP backend is used.
2229 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2231 * \return zero on success, -1 on error.
2234 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2236 char tmp[PATH_MAX] = "";
2248 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2249 /* If no mailbox, return immediately */
2250 if (ast_strlen_zero(mailbox_context))
2253 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2254 context = strchr(tmp, '@');
2255 if (strchr(mailbox_context, ',')) {
2256 int tmpnew, tmpold, tmpurgent;
2257 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2259 while ((cur = strsep(&mb, ", "))) {
2260 if (!ast_strlen_zero(cur)) {
2261 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2269 *urgentmsgs += tmpurgent;
2280 context = "default";
2281 mailboxnc = (char *) mailbox_context;
2285 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2287 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2290 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2295 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2300 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2308 * \brief Determines if the given folder has messages.
2309 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2310 * \param folder the folder to look in
2312 * This function is used when the mailbox is stored in an IMAP back end.
2313 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2314 * \return 1 if the folder has one or more messages. zero otherwise.
2317 static int has_voicemail(const char *mailbox, const char *folder)
2319 char tmp[256], *tmp2, *box, *context;
2320 ast_copy_string(tmp, mailbox, sizeof(tmp));
2322 if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2323 while ((box = strsep(&tmp2, ",&"))) {
2324 if (!ast_strlen_zero(box)) {
2325 if (has_voicemail(box, folder)) {
2331 if ((context = strchr(tmp, '@'))) {
2334 context = "default";
2336 return __messagecount(context, tmp, folder) ? 1 : 0;
2340 * \brief Copies a message from one mailbox to another.
2350 * This works with IMAP storage based mailboxes.
2352 * \return zero on success, -1 on error.
2354 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)
2356 struct vm_state *sendvms = NULL, *destvms = NULL;
2357 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2358 if (msgnum >= recip->maxmsg) {
2359 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2362 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2363 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2366 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2367 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2370 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2371 ast_mutex_lock(&sendvms->lock);
2372 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2373 ast_mutex_unlock(&sendvms->lock);
2376 ast_mutex_unlock(&sendvms->lock);
2377 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2381 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2383 char tmp[256], *t = tmp;
2384 size_t left = sizeof(tmp);
2386 if (box == OLD_FOLDER) {
2387 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2389 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2392 if (box == NEW_FOLDER) {
2393 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2395 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2398 /* Build up server information */
2399 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2401 /* Add authentication user if present */
2402 if (!ast_strlen_zero(authuser))
2403 ast_build_string(&t, &left, "/authuser=%s", authuser);
2405 /* Add flags if present */
2406 if (!ast_strlen_zero(imapflags))
2407 ast_build_string(&t, &left, "/%s", imapflags);
2409 /* End with username */
2411 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2413 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2415 if (box == NEW_FOLDER || box == OLD_FOLDER)
2416 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2417 else if (box == GREETINGS_FOLDER)
2418 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2419 else { /* Other folders such as Friends, Family, etc... */
2420 if (!ast_strlen_zero(imapparentfolder)) {
2421 /* imapparentfolder would typically be set to INBOX */
2422 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2424 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2429 static int init_mailstream(struct vm_state *vms, int box)
2431 MAILSTREAM *stream = NIL;
2436 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2439 if (option_debug > 2)
2440 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2441 if (vms->mailstream == NIL || !vms->mailstream) {
2443 ast_log(LOG_DEBUG, "mailstream not set.\n");
2445 stream = vms->mailstream;
2447 /* debug = T; user wants protocol telemetry? */
2448 debug = NIL; /* NO protocol telemetry? */
2450 if (delimiter == '\0') { /* did not probe the server yet */
2452 #ifdef USE_SYSTEM_IMAP
2453 #include <imap/linkage.c>
2454 #elif defined(USE_SYSTEM_CCLIENT)
2455 #include <c-client/linkage.c>
2457 #include "linkage.c"
2459 /* Connect to INBOX first to get folders delimiter */
2460 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2461 ast_mutex_lock(&vms->lock);
2462 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2463 ast_mutex_unlock(&vms->lock);
2464 if (stream == NIL) {
2465 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2468 get_mailbox_delimiter(stream);
2469 /* update delimiter in imapfolder */
2470 for (cp = vms->imapfolder; *cp; cp++)
2474 /* Now connect to the target folder */
2475 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2476 if (option_debug > 2)
2477 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2478 ast_mutex_lock(&vms->lock);
2479 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2480 ast_mutex_unlock(&vms->lock);
2481 if (vms->mailstream == NIL) {
2488 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2492 int ret, urgent = 0;
2494 /* If Urgent, then look at INBOX */
2500 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2501 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2502 vms->imapversion = vmu->imapversion;
2503 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2505 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2506 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2510 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2514 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2515 check_quota(vms, (char *) mbox(vmu, box));
2518 ast_mutex_lock(&vms->lock);
2519 pgm = mail_newsearchpgm();
2521 /* Check IMAP folder for Asterisk messages only... */
2522 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2523 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2528 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2529 if (box == NEW_FOLDER && urgent == 1) {
2534 } else if (box == NEW_FOLDER && urgent == 0) {
2539 } else if (box == OLD_FOLDER) {
2544 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2546 vms->vmArrayIndex = 0;
2547 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2548 vms->lastmsg = vms->vmArrayIndex - 1;
2549 mail_free_searchpgm(&pgm);
2551 ast_mutex_unlock(&vms->lock);
2555 static void write_file(char *filename, char *buffer, unsigned long len)
2559 output = fopen (filename, "w");
2560 if (fwrite(buffer, len, 1, output) != 1) {
2561 if (ferror(output)) {
2562 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2568 static void update_messages_by_imapuser(const char *user, unsigned long number)
2570 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2572 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2576 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2577 vms->msgArray[vms->vmArrayIndex++] = number;
2580 void mm_searched(MAILSTREAM *stream, unsigned long number)
2582 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2584 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2587 update_messages_by_imapuser(user, number);
2590 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2592 struct ast_variable *var;
2593 struct ast_vm_user *vmu;
2595 vmu = ast_calloc(1, sizeof *vmu);
2598 ast_set_flag(vmu, VM_ALLOCED);
2599 populate_defaults(vmu);
2601 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2603 apply_options_full(vmu, var);
2604 ast_variables_destroy(var);
2612 /* Interfaces to C-client */
2614 void mm_exists(MAILSTREAM * stream, unsigned long number)
2616 /* mail_ping will callback here if new mail! */
2617 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2618 if (number == 0) return;
2623 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2625 /* mail_ping will callback here if expunged mail! */
2626 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2627 if (number == 0) return;
2632 void mm_flags(MAILSTREAM * stream, unsigned long number)
2634 /* mail_ping will callback here if read mail! */
2635 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2636 if (number == 0) return;
2641 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2643 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2644 mm_log (string, errflg);
2648 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2650 if (delimiter == '\0') {
2654 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2655 if (attributes & LATT_NOINFERIORS)
2656 ast_debug(5, "no inferiors\n");
2657 if (attributes & LATT_NOSELECT)
2658 ast_debug(5, "no select\n");
2659 if (attributes & LATT_MARKED)
2660 ast_debug(5, "marked\n");
2661 if (attributes & LATT_UNMARKED)
2662 ast_debug(5, "unmarked\n");
2666 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2668 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2669 if (attributes & LATT_NOINFERIORS)
2670 ast_debug(5, "no inferiors\n");
2671 if (attributes & LATT_NOSELECT)
2672 ast_debug(5, "no select\n");
2673 if (attributes & LATT_MARKED)
2674 ast_debug(5, "marked\n");
2675 if (attributes & LATT_UNMARKED)
2676 ast_debug(5, "unmarked\n");
2680 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2682 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2683 if (status->flags & SA_MESSAGES)
2684 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2685 if (status->flags & SA_RECENT)
2686 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2687 if (status->flags & SA_UNSEEN)
2688 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2689 if (status->flags & SA_UIDVALIDITY)
2690 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2691 if (status->flags & SA_UIDNEXT)
2692 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2693 ast_log(AST_LOG_NOTICE, "\n");
2697 void mm_log(char *string, long errflg)
2699 switch ((short) errflg) {
2701 ast_debug(1, "IMAP Info: %s\n", string);
2705 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2708 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2714 void mm_dlog(char *string)
2716 ast_log(AST_LOG_NOTICE, "%s\n", string);
2720 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2722 struct ast_vm_user *vmu;
2724 ast_debug(4, "Entering callback mm_login\n");
2726 ast_copy_string(user, mb->user, MAILTMPLEN);
2728 /* We should only do this when necessary */
2729 if (!ast_strlen_zero(authpassword)) {
2730 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2732 AST_LIST_TRAVERSE(&users, vmu, list) {
2733 if (!strcasecmp(mb->user, vmu->imapuser)) {
2734 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2739 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2740 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2748 void mm_critical(MAILSTREAM * stream)
2753 void mm_nocritical(MAILSTREAM * stream)
2758 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2760 kill (getpid (), SIGSTOP);
2765 void mm_fatal(char *string)
2767 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2770 /* C-client callback to handle quota */
2771 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2773 struct vm_state *vms;
2774 char *mailbox = stream->mailbox, *user;
2775 char buf[1024] = "";
2776 unsigned long usage = 0, limit = 0;
2779 usage = pquota->usage;
2780 limit = pquota->limit;
2781 pquota = pquota->next;
2784 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)))) {
2785 ast_log(AST_LOG_ERROR, "No state found.\n");
2789 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2791 vms->quota_usage = usage;
2792 vms->quota_limit = limit;
2795 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2797 char *start, *eol_pnt;
2800 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2803 taglen = strlen(tag) + 1;
2807 if (!(start = strstr(header, tag)))
2810 /* Since we can be called multiple times we should clear our buffer */
2811 memset(buf, 0, len);
2813 ast_copy_string(buf, start+taglen, len);
2814 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2819 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2821 char *start, *quote, *eol_pnt;
2823 if (ast_strlen_zero(mailbox))
2826 if (!(start = strstr(mailbox, "/user=")))
2829 ast_copy_string(buf, start+6, len);
2831 if (!(quote = strchr(buf, '\"'))) {
2832 if (!(eol_pnt = strchr(buf, '/')))
2833 eol_pnt = strchr(buf,'}');
2837 eol_pnt = strchr(buf+1,'\"');
2843 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2845 struct vm_state *vms_p;
2847 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2848 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2851 if (option_debug > 4)
2852 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2853 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2855 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2856 ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
2857 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2858 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2859 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2860 vms_p->imapversion = vmu->imapversion;
2861 if (option_debug > 4)
2862 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2864 /* set mailbox to INBOX! */
2865 ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
2866 init_vm_state(vms_p);
2867 vmstate_insert(vms_p);
2871 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2873 struct vmstate *vlist = NULL;
2876 struct vm_state *vms;
2877 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2878 vms = pthread_getspecific(ts_vmstate.key);
2882 AST_LIST_LOCK(&vmstates);
2883 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2885 ast_debug(3, "error: vms is NULL for %s\n", user);
2888 if (vlist->vms->imapversion != imapversion) {
2891 if (!vlist->vms->imapuser) {
2892 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2896 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2897 AST_LIST_UNLOCK(&vmstates);
2901 AST_LIST_UNLOCK(&vmstates);
2903 ast_debug(3, "%s not found in vmstates\n", user);
2908 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2911 struct vmstate *vlist = NULL;
2912 const char *local_context = S_OR(context, "default");
2915 struct vm_state *vms;
2916 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2917 vms = pthread_getspecific(ts_vmstate.key);
2921 AST_LIST_LOCK(&vmstates);
2922 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2924 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2927 if (vlist->vms->imapversion != imapversion) {
2930 if (!vlist->vms->username || !vlist->vms->context) {
2931 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2935 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);
2937 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2938 ast_debug(3, "Found it!\n");
2939 AST_LIST_UNLOCK(&vmstates);
2943 AST_LIST_UNLOCK(&vmstates);
2945 ast_debug(3, "%s not found in vmstates\n", mailbox);
2950 static void vmstate_insert(struct vm_state *vms)
2953 struct vm_state *altvms;
2955 /* If interactive, it probably already exists, and we should
2956 use the one we already have since it is more up to date.
2957 We can compare the username to find the duplicate */
2958 if (vms->interactive == 1) {
2959 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2961 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2962 vms->newmessages = altvms->newmessages;
2963 vms->oldmessages = altvms->oldmessages;
2964 vms->vmArrayIndex = altvms->vmArrayIndex;
2965 vms->lastmsg = altvms->lastmsg;
2966 vms->curmsg = altvms->curmsg;
2967 /* get a pointer to the persistent store */
2968 vms->persist_vms = altvms;
2969 /* Reuse the mailstream? */
2970 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2971 vms->mailstream = altvms->mailstream;
2973 vms->mailstream = NIL;
2979 if (!(v = ast_calloc(1, sizeof(*v))))
2984 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2986 AST_LIST_LOCK(&vmstates);
2987 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2988 AST_LIST_UNLOCK(&vmstates);
2991 static void vmstate_delete(struct vm_state *vms)
2993 struct vmstate *vc = NULL;
2994 struct vm_state *altvms = NULL;
2996 /* If interactive, we should copy pertinent info
2997 back to the persistent state (to make update immediate) */
2998 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2999 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3000 altvms->newmessages = vms->newmessages;
3001 altvms->oldmessages = vms->oldmessages;
3002 altvms->updated = 1;
3003 vms->mailstream = mail_close(vms->mailstream);
3005 /* Interactive states are not stored within the persistent list */
3009 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3011 AST_LIST_LOCK(&vmstates);
3012 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
3013 if (vc->vms == vms) {
3014 AST_LIST_REMOVE_CURRENT(list);
3018 AST_LIST_TRAVERSE_SAFE_END
3019 AST_LIST_UNLOCK(&vmstates);
3022 ast_mutex_destroy(&vc->vms->lock);
3026 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3029 static void set_update(MAILSTREAM * stream)
3031 struct vm_state *vms;
3032 char *mailbox = stream->mailbox, *user;
3033 char buf[1024] = "";
3035 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
3036 if (user && option_debug > 2)
3037 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
3041 ast_debug(3, "User %s mailbox set for update.\n", user);
3043 vms->updated = 1; /* Set updated flag since mailbox changed */
3046 static void init_vm_state(struct vm_state *vms)
3049 vms->vmArrayIndex = 0;
3050 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
3051 vms->msgArray[x] = 0;
3053 ast_mutex_init(&vms->lock);
3056 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
3060 char *fn = is_intro ? vms->introfn : vms->fn;
3062 unsigned long newlen;
3065 if (!body || body == NIL)
3068 ast_mutex_lock(&vms->lock);
3069 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
3070 ast_mutex_unlock(&vms->lock);
3071 if (body_content != NIL) {
3072 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
3073 /* ast_debug(1,body_content); */
3074 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
3075 /* If the body of the file is empty, return an error */
3079 write_file(filename, (char *) body_decoded, newlen);
3081 ast_debug(5, "Body of message is NULL.\n");
3088 * \brief Get delimiter via mm_list callback
3091 * Determines the delimiter character that is used by the underlying IMAP based mail store.
3093 /* MUTEX should already be held */
3094 static void get_mailbox_delimiter(MAILSTREAM *stream) {
3096 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
3097 mail_list(stream, tmp, "*");
3101 * \brief Check Quota for user
3102 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3103 * \param mailbox the mailbox to check the quota for.
3105 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3107 static void check_quota(struct vm_state *vms, char *mailbox) {
3108 ast_mutex_lock(&vms->lock);
3109 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3110 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3111 if (vms && vms->mailstream != NULL) {
3112 imap_getquotaroot(vms->mailstream, mailbox);
3114 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3116 ast_mutex_unlock(&vms->lock);
3119 #endif /* IMAP_STORAGE */
3121 /*! \brief Lock file path
3122 only return failure if ast_lock_path returns 'timeout',
3123 not if the path does not exist or any other reason
3125 static int vm_lock_path(const char *path)
3127 switch (ast_lock_path(path)) {
3128 case AST_LOCK_TIMEOUT:
3137 struct generic_prepare_struct {
3143 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
3145 struct generic_prepare_struct *gps = data;
3149 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3150 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3151 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3154 res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
3155 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3156 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
3157 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3160 for (i = 0; i < gps->argc; i++)
3161 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
3167 * \brief Retrieves a file from an ODBC data store.
3168 * \param dir the path to the file to be retreived.
3169 * \param msgnum the message number, such as within a mailbox folder.
3171 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
3172 * 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.
3174 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
3175 * The output is the message information file with the name msgnum and the extension .txt
3176 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
3178 * \return 0 on success, -1 on error.
3180 static int retrieve_file(char *dir, int msgnum)
3186 void *fdm = MAP_FAILED;
3187 SQLSMALLINT colcount = 0;
3194 SQLSMALLINT datatype;
3195 SQLSMALLINT decimaldigits;
3196 SQLSMALLINT nullable;
3202 char full_fn[PATH_MAX];
3204 char *argv[] = { dir, msgnums };
3205 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3207 struct odbc_obj *obj;
3208 obj = ast_odbc_request_obj(odbc_database, 0);
3210 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3211 c = strchr(fmt, '|');
3214 if (!strcasecmp(fmt, "wav49"))
3216 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3218 make_file(fn, sizeof(fn), dir, msgnum);
3220 ast_copy_string(fn, dir, sizeof(fn));
3222 /* Create the information file */
3223 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3225 if (!(f = fopen(full_fn, "w+"))) {
3226 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
3230 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3231 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3232 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3234 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3235 ast_odbc_release_obj(obj);
3238 res = SQLFetch(stmt);
3239 if (res == SQL_NO_DATA) {
3240 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3241 ast_odbc_release_obj(obj);
3243 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3244 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3245 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3246 ast_odbc_release_obj(obj);
3249 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3251 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3252 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3253 ast_odbc_release_obj(obj);
3256 res = SQLNumResultCols(stmt, &colcount);
3257 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3258 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3259 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3260 ast_odbc_release_obj(obj);
3264 fprintf(f, "[message]\n");
3265 for (x = 0; x < colcount; x++) {
3267 collen = sizeof(coltitle);
3268 res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
3269 &datatype, &colsize, &decimaldigits, &nullable);
3270 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3271 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3272 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3273 ast_odbc_release_obj(obj);
3276 if (!strcasecmp(coltitle, "recording")) {
3278 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3282 lseek(fd, fdlen - 1, SEEK_SET);
3283 if (write(fd, tmp, 1) != 1) {
3288 /* Read out in small chunks */
3289 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3290 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3291 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3292 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3293 ast_odbc_release_obj(obj);
3296 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3297 munmap(fdm, CHUNKSIZE);
3298 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3299 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3301 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3302 ast_odbc_release_obj(obj);
3307 if (truncate(full_fn, fdlen) < 0) {
3308 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3312 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3313 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3314 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3315 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3316 ast_odbc_release_obj(obj);
3319 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3320 fprintf(f, "%s=%s\n", coltitle, rowdata);
3323 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3324 ast_odbc_release_obj(obj);
3326 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3336 * \brief Determines the highest message number in use for a given user and mailbox folder.
3338 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3340 * This method is used when mailboxes are stored in an ODBC back end.
3341 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3343 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3345 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3352 char *argv[] = { dir };
3353 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3355 struct odbc_obj *obj;
3356 obj = ast_odbc_request_obj(odbc_database, 0);
3358 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3359 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3361 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3362 ast_odbc_release_obj(obj);
3365 res = SQLFetch(stmt);
3366 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3367 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3368 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3369 ast_odbc_release_obj(obj);
3372 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3373 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3374 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3375 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3376 ast_odbc_release_obj(obj);
3379 if (sscanf(rowdata, "%30d", &x) != 1)
3380 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3381 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3382 ast_odbc_release_obj(obj);
3384 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3390 * \brief Determines if the specified message exists.
3391 * \param dir the folder the mailbox folder to look for messages.
3392 * \param msgnum the message index to query for.
3394 * This method is used when mailboxes are stored in an ODBC back end.
3396 * \return greater than zero if the message exists, zero when the message does not exist or on error.
3398 static int message_exists(char *dir, int msgnum)
3406 char *argv[] = { dir, msgnums };
3407 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3409 struct odbc_obj *obj;
3410 obj = ast_odbc_request_obj(odbc_database, 0);
3412 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3413 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3414 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3416 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3417 ast_odbc_release_obj(obj);
3420 res = SQLFetch(stmt);
3421 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3422 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3423 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3424 ast_odbc_release_obj(obj);
3427 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3428 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3429 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3430 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3431 ast_odbc_release_obj(obj);
3434 if (sscanf(rowdata, "%30d", &x) != 1)
3435 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3436 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3437 ast_odbc_release_obj(obj);
3439 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3445 * \brief returns the one-based count for messages.
3447 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3449 * This method is used when mailboxes are stored in an ODBC back end.
3450 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3451 * one-based messages.
3452 * This method just calls last_message_index and returns +1 of its value.
3454 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3456 static int count_messages(struct ast_vm_user *vmu, char *dir)
3458 return last_message_index(vmu, dir) + 1;
3462 * \brief Deletes a message from the mailbox folder.
3463 * \param sdir The mailbox folder to work in.
3464 * \param smsg The message index to be deleted.
3466 * This method is used when mailboxes are stored in an ODBC back end.
3467 * The specified message is directly deleted from the database 'voicemessages' table.
3469 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3471 static void delete_file(const char *sdir, int smsg)
3476 char *argv[] = { NULL, msgnums };
3477 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3478 struct odbc_obj *obj;
3480 argv[0] = ast_strdupa(sdir);
3482 obj = ast_odbc_request_obj(odbc_database, 0);
3484 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3485 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3486 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3488 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3490 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3491 ast_odbc_release_obj(obj);
3493 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3498 * \brief Copies a voicemail from one mailbox to another.
3499 * \param sdir the folder for which to look for the message to be copied.
3500 * \param smsg the index of the message to be copied.
3501 * \param ddir the destination folder to copy the message into.
3502 * \param dmsg the index to be used for the copied message.
3503 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3504 * \param dmailboxcontext The context for the destination user.
3506 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3508 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3514 struct odbc_obj *obj;
3515 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3516 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3518 delete_file(ddir, dmsg);
3519 obj = ast_odbc_request_obj(odbc_database, 0);
3521 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3522 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3523 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
3524 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3526 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3528 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3529 ast_odbc_release_obj(obj);
3531 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3535 struct insert_data {
3538 const char *msgnums;
3542 const char *context;
3543 const char *macrocontext;
3544 const char *callerid;
3545 const char *origtime;
3546 const char *duration;
3547 const char *mailboxuser;
3548 const char *mailboxcontext;
3549 const char *category;
3553 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3555 struct insert_data *data = vdata;
3559 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3560 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3561 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3562 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3566 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
3567 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);