fe6ec717301f0dd0fdcbeed5823d42205b104152
[asterisk/asterisk.git] / apps / app_voicemail.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2006, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*!
20  * \file
21  * \author Mark Spencer <markster@digium.com>
22  * \brief Comedian Mail - Voicemail System
23  *
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/)
27  *
28  * \par See also
29  * \arg \ref Config_vm
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
33  * during compilation.
34  *
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.
38  */
39
40 /*** MODULEINFO
41         <depend>res_smdi</depend>
42  ***/
43
44 /*** MAKEOPTS
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>
50         </member>
51         <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
52                 <depend>generic_odbc</depend>
53                 <depend>ltdl</depend>
54                 <conflict>IMAP_STORAGE</conflict>
55                 <conflict>FILE_STORAGE</conflict>
56                 <defaultenabled>no</defaultenabled>
57         </member>
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>
62                 <use>openssl</use>
63                 <defaultenabled>no</defaultenabled>
64         </member>
65 </category>
66 ***/
67
68 #include "asterisk.h"
69
70 #ifdef IMAP_STORAGE
71 #include <ctype.h>
72 #include <signal.h>
73 #include <pwd.h>
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>
82 #else
83 #include "c-client.h"
84 #include "imap4r1.h"
85 #include "linkage.h"
86 #endif
87 #endif
88
89 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
90
91 #include "asterisk/paths.h"     /* use ast_config_AST_SPOOL_DIR */
92 #include <sys/time.h>
93 #include <sys/stat.h>
94 #include <sys/mman.h>
95 #include <time.h>
96 #include <dirent.h>
97 #if defined(__FreeBSD__) || defined(__OpenBSD__)
98 #include <sys/wait.h>
99 #endif
100
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"
122
123 #ifdef ODBC_STORAGE
124 #include "asterisk/res_odbc.h"
125 #endif
126
127 #ifdef IMAP_STORAGE
128 #include "asterisk/threadstorage.h"
129 #endif
130
131 /*** DOCUMENTATION
132         <application name="VoiceMail" language="en_US">
133                 <synopsis>
134                         Leave a Voicemail message.
135                 </synopsis>
136                 <syntax>
137                         <parameter name="mailboxs" argsep="&amp;" required="true">
138                                 <argument name="mailbox1" argsep="@" required="true">
139                                         <argument name="mailbox" required="true" />
140                                         <argument name="context" />
141                                 </argument>
142                                 <argument name="mailbox2" argsep="@" multiple="true">
143                                         <argument name="mailbox" required="true" />
144                                         <argument name="context" />
145                                 </argument>
146                         </parameter>
147                         <parameter name="options">
148                                 <optionlist>
149                                         <option name="b">
150                                                 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
151                                         </option>
152                                         <option name="d">
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>
156                                         </option>
157                                         <option name="g">
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>
162                                         </option>
163                                         <option name="s">
164                                                 <para>Skip the playback of instructions for leaving a message to the
165                                                 calling party.</para>
166                                         </option>
167                                         <option name="u">
168                                                 <para>Play the <literal>unavailable</literal> greeting.</para>
169                                         </option>
170                                         <option name="U">
171                                                 <para>Mark message as <literal>URGENT</literal>.</para>
172                                         </option>
173                                         <option name="P">
174                                                 <para>Mark message as <literal>PRIORITY</literal>.</para>
175                                         </option>
176                                 </optionlist>
177                         </parameter>
178                 </syntax>
179                 <description>
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
183                         exist.</para>
184                         <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
185                         <enumlist>
186                                 <enum name="0">
187                                         <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
188                                 </enum>
189                                 <enum name="*">
190                                         <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
191                                 </enum>
192                         </enumlist>
193                         <para>This application will set the following channel variable upon completion:</para>
194                         <variablelist>
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" />
200                                 </variable>
201                         </variablelist>
202                 </description>
203         </application>
204         <application name="VoiceMailMain" language="en_US">
205                 <synopsis>
206                         Check Voicemail messages.
207                 </synopsis>
208                 <syntax>
209                         <parameter name="mailbox" required="true" argsep="@">
210                                 <argument name="mailbox" />
211                                 <argument name="context" />
212                         </parameter>
213                         <parameter name="options">
214                                 <optionlist>
215                                         <option name="p">
216                                                 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
217                                                 the mailbox that is entered by the caller.</para>
218                                         </option>
219                                         <option name="g">
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>
223                                         </option>
224                                         <option name="s">
225                                                 <para>Skip checking the passcode for the mailbox.</para>
226                                         </option>
227                                         <option name="a">
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>
231                                                 <enumlist>
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>
242                                                 </enumlist>
243                                         </option>
244                                 </optionlist>
245                         </parameter>
246                 </syntax>
247                 <description>
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>
253                 </description>
254         </application>
255         <application name="MailboxExists" language="en_US">
256                 <synopsis>
257                         Check to see if Voicemail mailbox exists.
258                 </synopsis>
259                 <syntax>
260                         <parameter name="mailbox" required="true" argsep="@">
261                                 <argument name="mailbox" required="true" />
262                                 <argument name="context" />
263                         </parameter>
264                         <parameter name="options">
265                                 <para>None options.</para>
266                         </parameter>
267                 </syntax>
268                 <description>
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
271                         will be used.</para>
272                         <para>This application will set the following channel variable upon completion:</para>
273                         <variablelist>
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" />
279                                 </variable>
280                         </variablelist>
281                 </description>
282         </application>
283         <application name="VMAuthenticate" language="en_US">
284                 <synopsis>
285                         Authenticate with Voicemail passwords.
286                 </synopsis>
287                 <syntax>
288                         <parameter name="mailbox" required="true" argsep="@">
289                                 <argument name="mailbox" />
290                                 <argument name="context" />
291                         </parameter>
292                         <parameter name="options">
293                                 <optionlist>
294                                         <option name="s">
295                                                 <para>Skip playing the initial prompts.</para>
296                                         </option>
297                                 </optionlist>
298                         </parameter>
299                 </syntax>
300                 <description>
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
305                         mailbox.</para>
306                 </description>
307         </application>
308         <application name="VMSayName" language="en_US">
309                 <synopsis>
310                         Play the name of a voicemail user
311                 </synopsis>
312                 <syntax>
313                         <parameter name="mailbox" required="true" argsep="@">
314                                 <argument name="mailbox" />
315                                 <argument name="context" />
316                         </parameter>
317                 </syntax>
318                 <description>
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>
321                 </description>
322         </application>
323         <function name="MAILBOX_EXISTS" language="en_US">
324                 <synopsis>
325                         Tell if a mailbox is configured.
326                 </synopsis>
327                 <syntax argsep="@">
328                         <parameter name="mailbox" required="true" />
329                         <parameter name="context" />
330                 </syntax>
331                 <description>
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>
334                         context.</para>
335                 </description>
336         </function>
337         <manager name="VoicemailUsersList" language="en_US">
338                 <synopsis>
339                         List All Voicemail User Information.
340                 </synopsis>
341                 <syntax>
342                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
343                 </syntax>
344                 <description>
345                 </description>
346         </manager>
347  ***/
348
349 #ifdef IMAP_STORAGE
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;
359
360 static int expungeonhangup = 1;
361 static int imapgreetings = 0;
362 static char delimiter = '\0';
363
364 struct vm_state;
365 struct ast_vm_user;
366
367 AST_THREADSTORAGE(ts_vmstate);
368
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);
389
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);
395 struct vmstate {
396         struct vm_state *vms;
397         AST_LIST_ENTRY(vmstate) list;
398 };
399
400 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
401
402 #endif
403
404 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
405
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
411
412 #define VOICEMAIL_CONFIG "voicemail.conf"
413 #define ASTERISK_USERNAME "asterisk"
414
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? */
424
425 /* Default mail command to mail voicemail. Change it with the
426     mailcmd= command in voicemail.conf */
427 #define SENDMAIL "/usr/sbin/sendmail -t"
428
429 #define INTRO "vm-intro"
430
431 #define MAXMSG 100
432 #define MAXMSGLIMIT 9999
433
434 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
435
436 #define BASELINELEN 72
437 #define BASEMAXINLINE 256
438 #ifdef IMAP_STORAGE
439 #define ENDL "\r\n"
440 #else
441 #define ENDL "\n"
442 #endif
443
444 #define MAX_DATETIME_FORMAT     512
445 #define MAX_NUM_CID_CONTEXTS 10
446
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
468
469 enum vm_box {
470         NEW_FOLDER,
471         OLD_FOLDER,
472         WORK_FOLDER,
473         FAMILY_FOLDER,
474         FRIENDS_FOLDER,
475         GREETINGS_FOLDER
476 };
477
478 enum vm_option_flags {
479         OPT_SILENT =           (1 << 0),
480         OPT_BUSY_GREETING =    (1 << 1),
481         OPT_UNAVAIL_GREETING = (1 << 2),
482         OPT_RECORDGAIN =       (1 << 3),
483         OPT_PREPEND_MAILBOX =  (1 << 4),
484         OPT_AUTOPLAY =         (1 << 6),
485         OPT_DTMFEXIT =         (1 << 7),
486         OPT_MESSAGE_Urgent =   (1 << 8),
487         OPT_MESSAGE_PRIORITY = (1 << 9)
488 };
489
490 enum vm_option_args {
491         OPT_ARG_RECORDGAIN = 0,
492         OPT_ARG_PLAYFOLDER = 1,
493         OPT_ARG_DTMFEXIT   = 2,
494         /* This *must* be the last value in this enum! */
495         OPT_ARG_ARRAY_SIZE = 3,
496 };
497
498 enum vm_passwordlocation {
499         OPT_PWLOC_VOICEMAILCONF = 0,
500         OPT_PWLOC_SPOOLDIR      = 1,
501         OPT_PWLOC_USERSCONF     = 2,
502 };
503
504 AST_APP_OPTIONS(vm_app_options, {
505         AST_APP_OPTION('s', OPT_SILENT),
506         AST_APP_OPTION('b', OPT_BUSY_GREETING),
507         AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
508         AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
509         AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
510         AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
511         AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
512         AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
513         AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
514 });
515
516 static int load_config(int reload);
517
518 /*! \page vmlang Voicemail Language Syntaxes Supported
519
520         \par Syntaxes supported, not really language codes.
521         \arg \b en    - English
522         \arg \b de    - German
523         \arg \b es    - Spanish
524         \arg \b fr    - French
525         \arg \b it    - Italian
526         \arg \b nl    - Dutch
527         \arg \b pt    - Portuguese
528         \arg \b pt_BR - Portuguese (Brazil)
529         \arg \b gr    - Greek
530         \arg \b no    - Norwegian
531         \arg \b se    - Swedish
532         \arg \b tw    - Chinese (Taiwan)
533         \arg \b ua - Ukrainian
534
535 German requires the following additional soundfile:
536 \arg \b 1F      einE (feminine)
537
538 Spanish requires the following additional soundfile:
539 \arg \b 1M      un (masculine)
540
541 Dutch, Portuguese & Spanish require the following additional soundfiles:
542 \arg \b vm-INBOXs       singular of 'new'
543 \arg \b vm-Olds         singular of 'old/heard/read'
544
545 NB these are plural:
546 \arg \b vm-INBOX        nieuwe (nl)
547 \arg \b vm-Old          oude (nl)
548
549 Polish uses:
550 \arg \b vm-new-a        'new', feminine singular accusative
551 \arg \b vm-new-e        'new', feminine plural accusative
552 \arg \b vm-new-ych      'new', feminine plural genitive
553 \arg \b vm-old-a        'old', feminine singular accusative
554 \arg \b vm-old-e        'old', feminine plural accusative
555 \arg \b vm-old-ych      'old', feminine plural genitive
556 \arg \b digits/1-a      'one', not always same as 'digits/1'
557 \arg \b digits/2-ie     'two', not always same as 'digits/2'
558
559 Swedish uses:
560 \arg \b vm-nytt         singular of 'new'
561 \arg \b vm-nya          plural of 'new'
562 \arg \b vm-gammalt      singular of 'old'
563 \arg \b vm-gamla        plural of 'old'
564 \arg \b digits/ett      'one', not always same as 'digits/1'
565
566 Norwegian uses:
567 \arg \b vm-ny           singular of 'new'
568 \arg \b vm-nye          plural of 'new'
569 \arg \b vm-gammel       singular of 'old'
570 \arg \b vm-gamle        plural of 'old'
571
572 Dutch also uses:
573 \arg \b nl-om           'at'?
574
575 Spanish also uses:
576 \arg \b vm-youhaveno
577
578 Italian requires the following additional soundfile:
579
580 For vm_intro_it:
581 \arg \b vm-nuovo        new
582 \arg \b vm-nuovi        new plural
583 \arg \b vm-vecchio      old
584 \arg \b vm-vecchi       old plural
585
586 Chinese (Taiwan) requires the following additional soundfile:
587 \arg \b vm-tong         A class-word for call (tong1)
588 \arg \b vm-ri           A class-word for day (ri4)
589 \arg \b vm-you          You (ni3)
590 \arg \b vm-haveno   Have no (mei2 you3)
591 \arg \b vm-have     Have (you3)
592 \arg \b vm-listen   To listen (yao4 ting1)
593
594
595 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
596 spelled among others when you have to change folder. For the above reasons, vm-INBOX
597 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
598
599 */
600
601 struct baseio {
602         int iocp;
603         int iolen;
604         int linelength;
605         int ateof;
606         unsigned char iobuf[BASEMAXINLINE];
607 };
608
609 /*! Structure for linked list of users 
610  * Use ast_vm_user_destroy() to free one of these structures. */
611 struct ast_vm_user {
612         char context[AST_MAX_CONTEXT];   /*!< Voicemail context */
613         char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
614         char password[80];               /*!< Secret pin code, numbers only */
615         char fullname[80];               /*!< Full name, for directory app */
616         char email[80];                  /*!< E-mail address */
617         char *emailsubject;              /*!< E-mail subject */
618         char *emailbody;                 /*!< E-mail body */
619         char pager[80];                  /*!< E-mail address to pager (no attachment) */
620         char serveremail[80];            /*!< From: Mail address */
621         char mailcmd[160];               /*!< Configurable mail command */
622         char language[MAX_LANGUAGE];     /*!< Config: Language setting */
623         char zonetag[80];                /*!< Time zone */
624         char callback[80];
625         char dialout[80];
626         char uniqueid[80];               /*!< Unique integer identifier */
627         char exit[80];
628         char attachfmt[20];              /*!< Attachment format */
629         unsigned int flags;              /*!< VM_ flags */      
630         int saydurationm;
631         int minsecs;                     /*!< Minimum number of seconds per message for this mailbox */
632         int maxmsg;                      /*!< Maximum number of msgs per folder for this mailbox */
633         int maxdeletedmsg;               /*!< Maximum number of deleted msgs saved for this mailbox */
634         int maxsecs;                     /*!< Maximum number of seconds per message for this mailbox */
635         int passwordlocation;            /*!< Storage location of the password */
636 #ifdef IMAP_STORAGE
637         char imapuser[80];               /*!< IMAP server login */
638         char imappassword[80];           /*!< IMAP server password if authpassword not defined */
639         char imapfolder[64];             /*!< IMAP voicemail folder */
640         char imapvmshareid[80];          /*!< Shared mailbox ID to use rather than the dialed one */
641         int imapversion;                 /*!< If configuration changes, use the new values */
642 #endif
643         double volgain;                  /*!< Volume gain for voicemails sent via email */
644         AST_LIST_ENTRY(ast_vm_user) list;
645 };
646
647 /*! Voicemail time zones */
648 struct vm_zone {
649         AST_LIST_ENTRY(vm_zone) list;
650         char name[80];
651         char timezone[80];
652         char msg_format[512];
653 };
654
655 #define VMSTATE_MAX_MSG_ARRAY 256
656
657 /*! Voicemail mailbox state */
658 struct vm_state {
659         char curbox[80];
660         char username[80];
661         char context[80];
662         char curdir[PATH_MAX];
663         char vmbox[PATH_MAX];
664         char fn[PATH_MAX];
665         char intro[PATH_MAX];
666         int *deleted;
667         int *heard;
668         int curmsg;
669         int lastmsg;
670         int newmessages;
671         int oldmessages;
672         int urgentmessages;
673         int starting;
674         int repeats;
675 #ifdef IMAP_STORAGE
676         ast_mutex_t lock;
677         int updated;                         /*!< decremented on each mail check until 1 -allows delay */
678         long msgArray[VMSTATE_MAX_MSG_ARRAY];
679         MAILSTREAM *mailstream;
680         int vmArrayIndex;
681         char imapuser[80];                   /*!< IMAP server login */
682         char imapfolder[64];                 /*!< IMAP voicemail folder */
683         int imapversion;
684         int interactive;
685         char introfn[PATH_MAX];              /*!< Name of prepended file */
686         unsigned int quota_limit;
687         unsigned int quota_usage;
688         struct vm_state *persist_vms;
689 #endif
690 };
691
692 #ifdef ODBC_STORAGE
693 static char odbc_database[80];
694 static char odbc_table[80];
695 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
696 #define DISPOSE(a,b) remove_file(a,b)
697 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
698 #define EXISTS(a,b,c,d) (message_exists(a,b))
699 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
700 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
701 #define DELETE(a,b,c,d) (delete_file(a,b))
702 #else
703 #ifdef IMAP_STORAGE
704 #define DISPOSE(a,b) (imap_remove_file(a,b))
705 #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))
706 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
707 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
708 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
709 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
710 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
711 #else
712 #define RETRIEVE(a,b,c,d)
713 #define DISPOSE(a,b)
714 #define STORE(a,b,c,d,e,f,g,h,i,j)
715 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
716 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
717 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h)); 
718 #define DELETE(a,b,c,d) (vm_delete(c))
719 #endif
720 #endif
721
722 static char VM_SPOOL_DIR[PATH_MAX];
723
724 static char ext_pass_cmd[128];
725 static char ext_pass_check_cmd[128];
726
727 static int my_umask;
728
729 #define PWDCHANGE_INTERNAL (1 << 1)
730 #define PWDCHANGE_EXTERNAL (1 << 2)
731 static int pwdchange = PWDCHANGE_INTERNAL;
732
733 #ifdef ODBC_STORAGE
734 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
735 #else
736 # ifdef IMAP_STORAGE
737 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
738 # else
739 # define tdesc "Comedian Mail (Voicemail System)"
740 # endif
741 #endif
742
743 static char userscontext[AST_MAX_EXTENSION] = "default";
744
745 static char *addesc = "Comedian Mail";
746
747 /* Leave a message */
748 static char *app = "VoiceMail";
749
750 /* Check mail, control, etc */
751 static char *app2 = "VoiceMailMain";
752
753 static char *app3 = "MailboxExists";
754 static char *app4 = "VMAuthenticate";
755
756 static char *sayname_app = "VMSayName";
757
758 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
759 static AST_LIST_HEAD_STATIC(zones, vm_zone);
760 static char zonetag[80];
761 static int maxsilence;
762 static int maxmsg;
763 static int maxdeletedmsg;
764 static int silencethreshold = 128;
765 static char serveremail[80];
766 static char mailcmd[160];       /* Configurable mail cmd */
767 static char externnotify[160]; 
768 static struct ast_smdi_interface *smdi_iface = NULL;
769 static char vmfmts[80];
770 static double volgain;
771 static int vmminsecs;
772 static int vmmaxsecs;
773 static int maxgreet;
774 static int skipms;
775 static int maxlogins;
776 static int minpassword;
777 static int passwordlocation;
778
779 /*! Poll mailboxes for changes since there is something external to
780  *  app_voicemail that may change them. */
781 static unsigned int poll_mailboxes;
782
783 /*! Polling frequency */
784 static unsigned int poll_freq;
785 /*! By default, poll every 30 seconds */
786 #define DEFAULT_POLL_FREQ 30
787
788 AST_MUTEX_DEFINE_STATIC(poll_lock);
789 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
790 static pthread_t poll_thread = AST_PTHREADT_NULL;
791 static unsigned char poll_thread_run;
792
793 /*! Subscription to ... MWI event subscriptions */
794 static struct ast_event_sub *mwi_sub_sub;
795 /*! Subscription to ... MWI event un-subscriptions */
796 static struct ast_event_sub *mwi_unsub_sub;
797
798 /*!
799  * \brief An MWI subscription
800  *
801  * This is so we can keep track of which mailboxes are subscribed to.
802  * This way, we know which mailboxes to poll when the pollmailboxes
803  * option is being used.
804  */
805 struct mwi_sub {
806         AST_RWLIST_ENTRY(mwi_sub) entry;
807         int old_urgent;
808         int old_new;
809         int old_old;
810         uint32_t uniqueid;
811         char mailbox[1];
812 };
813
814 struct mwi_sub_task {
815         const char *mailbox;
816         const char *context;
817         uint32_t uniqueid;
818 };
819
820 static struct ast_taskprocessor *mwi_subscription_tps;
821
822 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
823
824 /* custom audio control prompts for voicemail playback */
825 static char listen_control_forward_key[12];
826 static char listen_control_reverse_key[12];
827 static char listen_control_pause_key[12];
828 static char listen_control_restart_key[12];
829 static char listen_control_stop_key[12];
830
831 /* custom password sounds */
832 static char vm_password[80] = "vm-password";
833 static char vm_newpassword[80] = "vm-newpassword";
834 static char vm_passchanged[80] = "vm-passchanged";
835 static char vm_reenterpassword[80] = "vm-reenterpassword";
836 static char vm_mismatch[80] = "vm-mismatch";
837 static char vm_invalid_password[80] = "vm-invalid-password";
838 static char vm_pls_try_again[80] = "vm-pls-try-again";
839
840 static struct ast_flags globalflags = {0};
841
842 static int saydurationminfo;
843
844 static char dialcontext[AST_MAX_CONTEXT] = "";
845 static char callcontext[AST_MAX_CONTEXT] = "";
846 static char exitcontext[AST_MAX_CONTEXT] = "";
847
848 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
849
850
851 static char *emailbody = NULL;
852 static char *emailsubject = NULL;
853 static char *pagerbody = NULL;
854 static char *pagersubject = NULL;
855 static char fromstring[100];
856 static char pagerfromstring[100];
857 static char charset[32] = "ISO-8859-1";
858
859 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
860 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
861 static int adsiver = 1;
862 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
863 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
864
865 /* Forward declarations - generic */
866 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
867 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);
868 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
869 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
870                         char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
871                         signed char record_gain, struct vm_state *vms, char *flag);
872 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
873 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
874 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);
875 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);
876 static void apply_options(struct ast_vm_user *vmu, const char *options);
877 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);
878 static int is_valid_dtmf(const char *key);
879 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
880 static int write_password_to_file(const char *secretfn, const char *password);
881
882 struct ao2_container *inprocess_container;
883
884 struct inprocess {
885         int count;
886         char *context;
887         char mailbox[0];
888 };
889
890 static int inprocess_hash_fn(const void *obj, const int flags)
891 {
892         const struct inprocess *i = obj;
893         return atoi(i->mailbox);
894 }
895
896 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
897 {
898         struct inprocess *i = obj, *j = arg;
899         if (!strcmp(i->mailbox, j->mailbox)) {
900                 return 0;
901         }
902         return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
903 }
904
905 static int inprocess_count(const char *context, const char *mailbox, int delta)
906 {
907         struct inprocess *i, *arg = alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
908         arg->context = arg->mailbox + strlen(mailbox) + 1;
909         strcpy(arg->mailbox, mailbox); /* SAFE */
910         strcpy(arg->context, context); /* SAFE */
911         ao2_lock(inprocess_container);
912         if ((i = ao2_find(inprocess_container, arg, 0))) {
913                 int ret = ast_atomic_fetchadd_int(&i->count, delta);
914                 ao2_unlock(inprocess_container);
915                 ao2_ref(i, -1);
916                 return ret;
917         }
918         if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
919                 ao2_unlock(inprocess_container);
920                 return 0;
921         }
922         i->context = i->mailbox + strlen(mailbox) + 1;
923         strcpy(i->mailbox, mailbox); /* SAFE */
924         strcpy(i->context, context); /* SAFE */
925         i->count = delta;
926         ao2_link(inprocess_container, i);
927         ao2_unlock(inprocess_container);
928         ao2_ref(i, -1);
929         return 0;
930 }
931
932 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
933 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
934 #endif
935
936 /*!
937  * \brief Strips control and non 7-bit clean characters from input string.
938  *
939  * \note To map control and none 7-bit characters to a 7-bit clean characters
940  *  please use ast_str_encode_mine().
941  */
942 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
943 {
944         char *bufptr = buf;
945         for (; *input; input++) {
946                 if (*input < 32) {
947                         continue;
948                 }
949                 *bufptr++ = *input;
950                 if (bufptr == buf + buflen - 1) {
951                         break;
952                 }
953         }
954         *bufptr = '\0';
955         return buf;
956 }
957
958
959 /*!
960  * \brief Sets default voicemail system options to a voicemail user.
961  *
962  * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
963  * - all the globalflags
964  * - the saydurationminfo
965  * - the callcontext
966  * - the dialcontext
967  * - the exitcontext
968  * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
969  * - volume gain.
970  */
971 static void populate_defaults(struct ast_vm_user *vmu)
972 {
973         ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);     
974         vmu->passwordlocation = passwordlocation;
975         if (saydurationminfo) {
976                 vmu->saydurationm = saydurationminfo;
977         }
978         ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
979         ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
980         ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
981         ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
982         if (vmminsecs) {
983                 vmu->minsecs = vmminsecs;
984         }
985         if (vmmaxsecs) {
986                 vmu->maxsecs = vmmaxsecs;
987         }
988         if (maxmsg) {
989                 vmu->maxmsg = maxmsg;
990         }
991         if (maxdeletedmsg) {
992                 vmu->maxdeletedmsg = maxdeletedmsg;
993         }
994         vmu->volgain = volgain;
995         vmu->emailsubject = NULL;
996         vmu->emailbody = NULL;
997 #ifdef IMAP_STORAGE
998         ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
999 #endif
1000 }
1001
1002 /*!
1003  * \brief Sets a a specific property value.
1004  * \param vmu The voicemail user object to work with.
1005  * \param var The name of the property to be set.
1006  * \param value The value to be set to the property.
1007  * 
1008  * The property name must be one of the understood properties. See the source for details.
1009  */
1010 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1011 {
1012         int x;
1013         if (!strcasecmp(var, "attach")) {
1014                 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1015         } else if (!strcasecmp(var, "attachfmt")) {
1016                 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1017         } else if (!strcasecmp(var, "serveremail")) {
1018                 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1019         } else if (!strcasecmp(var, "language")) {
1020                 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1021         } else if (!strcasecmp(var, "tz")) {
1022                 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1023 #ifdef IMAP_STORAGE
1024         } else if (!strcasecmp(var, "imapuser")) {
1025                 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1026                 vmu->imapversion = imapversion;
1027         } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1028                 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1029                 vmu->imapversion = imapversion;
1030         } else if (!strcasecmp(var, "imapfolder")) {
1031                 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1032         } else if (!strcasecmp(var, "imapvmshareid")) {
1033                 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1034                 vmu->imapversion = imapversion;
1035 #endif
1036         } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1037                 ast_set2_flag(vmu, ast_true(value), VM_DELETE); 
1038         } else if (!strcasecmp(var, "saycid")){
1039                 ast_set2_flag(vmu, ast_true(value), VM_SAYCID); 
1040         } else if (!strcasecmp(var, "sendvoicemail")){
1041                 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL); 
1042         } else if (!strcasecmp(var, "review")){
1043                 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1044         } else if (!strcasecmp(var, "tempgreetwarn")){
1045                 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);  
1046         } else if (!strcasecmp(var, "messagewrap")){
1047                 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);    
1048         } else if (!strcasecmp(var, "operator")) {
1049                 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);       
1050         } else if (!strcasecmp(var, "envelope")){
1051                 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);       
1052         } else if (!strcasecmp(var, "moveheard")){
1053                 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1054         } else if (!strcasecmp(var, "sayduration")){
1055                 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);    
1056         } else if (!strcasecmp(var, "saydurationm")){
1057                 if (sscanf(value, "%30d", &x) == 1) {
1058                         vmu->saydurationm = x;
1059                 } else {
1060                         ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1061                 }
1062         } else if (!strcasecmp(var, "forcename")){
1063                 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);      
1064         } else if (!strcasecmp(var, "forcegreetings")){
1065                 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);     
1066         } else if (!strcasecmp(var, "callback")) {
1067                 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1068         } else if (!strcasecmp(var, "dialout")) {
1069                 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1070         } else if (!strcasecmp(var, "exitcontext")) {
1071                 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1072         } else if (!strcasecmp(var, "minsecs")) {
1073                 if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1074                         vmu->minsecs = x;
1075                 } else {
1076                         ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1077                         vmu->minsecs = vmminsecs;
1078                 }
1079         } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1080                 vmu->maxsecs = atoi(value);
1081                 if (vmu->maxsecs <= 0) {
1082                         ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1083                         vmu->maxsecs = vmmaxsecs;
1084                 } else {
1085                         vmu->maxsecs = atoi(value);
1086                 }
1087                 if (!strcasecmp(var, "maxmessage"))
1088                         ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'.  Please make that change in your voicemail config.\n");
1089         } else if (!strcasecmp(var, "maxmsg")) {
1090                 vmu->maxmsg = atoi(value);
1091                 /* Accept maxmsg=0 (Greetings only voicemail) */
1092                 if (vmu->maxmsg < 0) {
1093                         ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1094                         vmu->maxmsg = MAXMSG;
1095                 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1096                         ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1097                         vmu->maxmsg = MAXMSGLIMIT;
1098                 }
1099         } else if (!strcasecmp(var, "nextaftercmd")) {
1100                 ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
1101         } else if (!strcasecmp(var, "backupdeleted")) {
1102                 if (sscanf(value, "%30d", &x) == 1)
1103                         vmu->maxdeletedmsg = x;
1104                 else if (ast_true(value))
1105                         vmu->maxdeletedmsg = MAXMSG;
1106                 else
1107                         vmu->maxdeletedmsg = 0;
1108
1109                 if (vmu->maxdeletedmsg < 0) {
1110                         ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1111                         vmu->maxdeletedmsg = MAXMSG;
1112                 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1113                         ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1114                         vmu->maxdeletedmsg = MAXMSGLIMIT;
1115                 }
1116         } else if (!strcasecmp(var, "volgain")) {
1117                 sscanf(value, "%30lf", &vmu->volgain);
1118         } else if (!strcasecmp(var, "passwordlocation")) {
1119                 if (!strcasecmp(value, "spooldir")) {
1120                         vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1121                 } else {
1122                         vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1123                 }
1124         } else if (!strcasecmp(var, "options")) {
1125                 apply_options(vmu, value);
1126         }
1127 }
1128
1129 static char *vm_check_password_shell(char *command, char *buf, size_t len) 
1130 {
1131         int fds[2], pid = 0;
1132
1133         memset(buf, 0, len);
1134
1135         if (pipe(fds)) {
1136                 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1137         } else {
1138                 /* good to go*/
1139                 pid = ast_safe_fork(0);
1140
1141                 if (pid < 0) {
1142                         /* ok maybe not */
1143                         close(fds[0]);
1144                         close(fds[1]);
1145                         snprintf(buf, len, "FAILURE: Fork failed");
1146                 } else if (pid) {
1147                         /* parent */
1148                         close(fds[1]);
1149                         if (read(fds[0], buf, len) < 0) {
1150                                 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1151                         }
1152                         close(fds[0]);
1153                 } else {
1154                         /*  child */
1155                         AST_DECLARE_APP_ARGS(arg,
1156                                 AST_APP_ARG(v)[20];
1157                         );
1158                         char *mycmd = ast_strdupa(command);
1159
1160                         close(fds[0]);
1161                         dup2(fds[1], STDOUT_FILENO);
1162                         close(fds[1]);
1163                         ast_close_fds_above_n(STDOUT_FILENO);
1164
1165                         AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1166
1167                         execv(arg.v[0], arg.v); 
1168                         printf("FAILURE: %s", strerror(errno));
1169                         _exit(0);
1170                 }
1171         }
1172         return buf;
1173 }
1174
1175 /*!
1176  * \brief Check that password meets minimum required length
1177  * \param vmu The voicemail user to change the password for.
1178  * \param password The password string to check
1179  *
1180  * \return zero on ok, 1 on not ok.
1181  */
1182 static int check_password(struct ast_vm_user *vmu, char *password)
1183 {
1184         /* check minimum length */
1185         if (strlen(password) < minpassword)
1186                 return 1;
1187         if (!ast_strlen_zero(ext_pass_check_cmd)) {
1188                 char cmd[255], buf[255];
1189
1190                 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1191
1192                 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1193                 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1194                         ast_debug(5, "Result: %s\n", buf);
1195                         if (!strncasecmp(buf, "VALID", 5)) {
1196                                 ast_debug(3, "Passed password check: '%s'\n", buf);
1197                                 return 0;
1198                         } else if (!strncasecmp(buf, "FAILURE", 7)) {
1199                                 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1200                                 return 0;
1201                         } else {
1202                                 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1203                                 return 1;
1204                         }
1205                 }
1206         }
1207         return 0;
1208 }
1209
1210 /*! 
1211  * \brief Performs a change of the voicemail passowrd in the realtime engine.
1212  * \param vmu The voicemail user to change the password for.
1213  * \param password The new value to be set to the password for this user.
1214  * 
1215  * This only works if there is a realtime engine configured.
1216  * This is called from the (top level) vm_change_password.
1217  *
1218  * \return zero on success, -1 on error.
1219  */
1220 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1221 {
1222         int res = -1;
1223         if (!strcmp(vmu->password, password)) {
1224                 /* No change (but an update would return 0 rows updated, so we opt out here) */
1225                 return 0;
1226         }
1227
1228         if (strlen(password) > 10) {
1229                 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1230         }
1231         if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1232                 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1233                 res = 0;
1234         }
1235         return res;
1236 }
1237
1238 /*!
1239  * \brief Destructively Parse options and apply.
1240  */
1241 static void apply_options(struct ast_vm_user *vmu, const char *options)
1242 {       
1243         char *stringp;
1244         char *s;
1245         char *var, *value;
1246         stringp = ast_strdupa(options);
1247         while ((s = strsep(&stringp, "|"))) {
1248                 value = s;
1249                 if ((var = strsep(&value, "=")) && value) {
1250                         apply_option(vmu, var, value);
1251                 }
1252         }       
1253 }
1254
1255 /*!
1256  * \brief Loads the options specific to a voicemail user.
1257  * 
1258  * This is called when a vm_user structure is being set up, such as from load_options.
1259  */
1260 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1261 {
1262         for (; var; var = var->next) {
1263                 if (!strcasecmp(var->name, "vmsecret")) {
1264                         ast_copy_string(retval->password, var->value, sizeof(retval->password));
1265                 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1266                         if (ast_strlen_zero(retval->password))
1267                                 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1268                 } else if (!strcasecmp(var->name, "uniqueid")) {
1269                         ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1270                 } else if (!strcasecmp(var->name, "pager")) {
1271                         ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1272                 } else if (!strcasecmp(var->name, "email")) {
1273                         ast_copy_string(retval->email, var->value, sizeof(retval->email));
1274                 } else if (!strcasecmp(var->name, "fullname")) {
1275                         ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1276                 } else if (!strcasecmp(var->name, "context")) {
1277                         ast_copy_string(retval->context, var->value, sizeof(retval->context));
1278                 } else if (!strcasecmp(var->name, "emailsubject")) {
1279                         retval->emailsubject = ast_strdup(var->value);
1280                 } else if (!strcasecmp(var->name, "emailbody")) {
1281                         retval->emailbody = ast_strdup(var->value);
1282 #ifdef IMAP_STORAGE
1283                 } else if (!strcasecmp(var->name, "imapuser")) {
1284                         ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1285                         retval->imapversion = imapversion;
1286                 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1287                         ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1288                         retval->imapversion = imapversion;
1289                 } else if (!strcasecmp(var->name, "imapfolder")) {
1290                         ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1291                 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1292                         ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1293                         retval->imapversion = imapversion;
1294 #endif
1295                 } else
1296                         apply_option(retval, var->name, var->value);
1297         }
1298 }
1299
1300 /*!
1301  * \brief Determines if a DTMF key entered is valid.
1302  * \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.
1303  *
1304  * Tests the character entered against the set of valid DTMF characters. 
1305  * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1306  */
1307 static int is_valid_dtmf(const char *key)
1308 {
1309         int i;
1310         char *local_key = ast_strdupa(key);
1311
1312         for (i = 0; i < strlen(key); ++i) {
1313                 if (!strchr(VALID_DTMF, *local_key)) {
1314                         ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1315                         return 0;
1316                 }
1317                 local_key++;
1318         }
1319         return 1;
1320 }
1321
1322 /*!
1323  * \brief Finds a voicemail user from the realtime engine.
1324  * \param ivm
1325  * \param context
1326  * \param mailbox
1327  *
1328  * 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.
1329  *
1330  * \return The ast_vm_user structure for the user that was found.
1331  */
1332 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1333 {
1334         struct ast_variable *var;
1335         struct ast_vm_user *retval;
1336
1337         if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1338                 if (!ivm)
1339                         ast_set_flag(retval, VM_ALLOCED);       
1340                 else
1341                         memset(retval, 0, sizeof(*retval));
1342                 if (mailbox) 
1343                         ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1344                 populate_defaults(retval);
1345                 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1346                         var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1347                 else
1348                         var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1349                 if (var) {
1350                         apply_options_full(retval, var);
1351                         ast_variables_destroy(var);
1352                 } else { 
1353                         if (!ivm) 
1354                                 ast_free(retval);
1355                         retval = NULL;
1356                 }       
1357         } 
1358         return retval;
1359 }
1360
1361 /*!
1362  * \brief Finds a voicemail user from the users file or the realtime engine.
1363  * \param ivm
1364  * \param context
1365  * \param mailbox
1366  * 
1367  * \return The ast_vm_user structure for the user that was found.
1368  */
1369 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1370 {
1371         /* This function could be made to generate one from a database, too */
1372         struct ast_vm_user *vmu = NULL, *cur;
1373         AST_LIST_LOCK(&users);
1374
1375         if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1376                 context = "default";
1377
1378         AST_LIST_TRAVERSE(&users, cur, list) {
1379 #ifdef IMAP_STORAGE
1380                 if (cur->imapversion != imapversion) {
1381                         continue;
1382                 }
1383 #endif
1384                 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1385                         break;
1386                 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1387                         break;
1388         }
1389         if (cur) {
1390                 /* Make a copy, so that on a reload, we have no race */
1391                 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1392                         memcpy(vmu, cur, sizeof(*vmu));
1393                         ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1394                         AST_LIST_NEXT(vmu, list) = NULL;
1395                 }
1396         } else
1397                 vmu = find_user_realtime(ivm, context, mailbox);
1398         AST_LIST_UNLOCK(&users);
1399         return vmu;
1400 }
1401
1402 /*!
1403  * \brief Resets a user password to a specified password.
1404  * \param context
1405  * \param mailbox
1406  * \param newpass
1407  *
1408  * This does the actual change password work, called by the vm_change_password() function.
1409  *
1410  * \return zero on success, -1 on error.
1411  */
1412 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1413 {
1414         /* This function could be made to generate one from a database, too */
1415         struct ast_vm_user *cur;
1416         int res = -1;
1417         AST_LIST_LOCK(&users);
1418         AST_LIST_TRAVERSE(&users, cur, list) {
1419                 if ((!context || !strcasecmp(context, cur->context)) &&
1420                         (!strcasecmp(mailbox, cur->mailbox)))
1421                                 break;
1422         }
1423         if (cur) {
1424                 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1425                 res = 0;
1426         }
1427         AST_LIST_UNLOCK(&users);
1428         return res;
1429 }
1430
1431 /*! 
1432  * \brief The handler for the change password option.
1433  * \param vmu The voicemail user to work with.
1434  * \param newpassword The new password (that has been gathered from the appropriate prompting).
1435  * 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.
1436  * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1437  */
1438 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1439 {
1440         struct ast_config   *cfg = NULL;
1441         struct ast_variable *var = NULL;
1442         struct ast_category *cat = NULL;
1443         char *category = NULL, *value = NULL, *new = NULL;
1444         const char *tmp = NULL;
1445         struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1446         char secretfn[PATH_MAX] = "";
1447         int found = 0;
1448
1449         if (!change_password_realtime(vmu, newpassword))
1450                 return;
1451
1452         /* check if we should store the secret in the spool directory next to the messages */
1453         switch (vmu->passwordlocation) {
1454         case OPT_PWLOC_SPOOLDIR:
1455                 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1456                 if (write_password_to_file(secretfn, newpassword) == 0) {
1457                         ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1458                         reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1459                         ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1460                         break;
1461                 } else {
1462                         ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1463                 }
1464                 /* Fall-through */
1465         case OPT_PWLOC_VOICEMAILCONF:
1466                 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1467                         while ((category = ast_category_browse(cfg, category))) {
1468                                 if (!strcasecmp(category, vmu->context)) {
1469                                         if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1470                                                 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1471                                                 break;
1472                                         }
1473                                         value = strstr(tmp, ",");
1474                                         if (!value) {
1475                                                 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1476                                                 break;
1477                                         }
1478                                         new = alloca((strlen(value) + strlen(newpassword) + 1));
1479                                         sprintf(new, "%s%s", newpassword, value);
1480                                         if (!(cat = ast_category_get(cfg, category))) {
1481                                                 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1482                                                 break;
1483                                         }
1484                                         ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1485                                         found = 1;
1486                                 }
1487                         }
1488                         /* save the results */
1489                         if (found) {
1490                                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1491                                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1492                                 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1493                                 break;
1494                         }
1495                 }
1496                 /* Fall-through */
1497         case OPT_PWLOC_USERSCONF:
1498                 /* check users.conf and update the password stored for the mailbox */
1499                 /* if no vmsecret entry exists create one. */
1500                 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1501                         ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1502                         for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1503                                 ast_debug(4, "users.conf: %s\n", category);
1504                                 if (!strcasecmp(category, vmu->mailbox)) {
1505                                         if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1506                                                 ast_debug(3, "looks like we need to make vmsecret!\n");
1507                                                 var = ast_variable_new("vmsecret", newpassword, "");
1508                                         } else {
1509                                                 var = NULL;
1510                                         }
1511                                         new = alloca(strlen(newpassword) + 1);
1512                                         sprintf(new, "%s", newpassword);
1513                                         if (!(cat = ast_category_get(cfg, category))) {
1514                                                 ast_debug(4, "failed to get category!\n");
1515                                                 ast_free(var);
1516                                                 break;
1517                                         }
1518                                         if (!var) {
1519                                                 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1520                                         } else {
1521                                                 ast_variable_append(cat, var);
1522                                         }
1523                                         found = 1;
1524                                         break;
1525                                 }
1526                         }
1527                         /* save the results and clean things up */
1528                         if (found) {
1529                                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1530                                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1531                                 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1532                         }
1533                 }
1534         }
1535 }
1536
1537 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1538 {
1539         char buf[255];
1540         snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1541         if (!ast_safe_system(buf)) {
1542                 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1543                 /* Reset the password in memory, too */
1544                 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1545         }
1546 }
1547
1548 /*! 
1549  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1550  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1551  * \param len The length of the path string that was written out.
1552  * 
1553  * The path is constructed as 
1554  *      VM_SPOOL_DIRcontext/ext/folder
1555  *
1556  * \return zero on success, -1 on error.
1557  */
1558 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1559 {
1560         return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1561 }
1562
1563 /*! 
1564  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1565  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1566  * \param len The length of the path string that was written out.
1567  * 
1568  * The path is constructed as 
1569  *      VM_SPOOL_DIRcontext/ext/folder
1570  *
1571  * \return zero on success, -1 on error.
1572  */
1573 static int make_file(char *dest, const int len, const char *dir, const int num)
1574 {
1575         return snprintf(dest, len, "%s/msg%04d", dir, num);
1576 }
1577
1578 /* same as mkstemp, but return a FILE * */
1579 static FILE *vm_mkftemp(char *template)
1580 {
1581         FILE *p = NULL;
1582         int pfd = mkstemp(template);
1583         chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1584         if (pfd > -1) {
1585                 p = fdopen(pfd, "w+");
1586                 if (!p) {
1587                         close(pfd);
1588                         pfd = -1;
1589                 }
1590         }
1591         return p;
1592 }
1593
1594 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1595  * \param dest    String. base directory.
1596  * \param len     Length of dest.
1597  * \param context String. Ignored if is null or empty string.
1598  * \param ext     String. Ignored if is null or empty string.
1599  * \param folder  String. Ignored if is null or empty string. 
1600  * \return -1 on failure, 0 on success.
1601  */
1602 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1603 {
1604         mode_t  mode = VOICEMAIL_DIR_MODE;
1605         int res;
1606
1607         make_dir(dest, len, context, ext, folder);
1608         if ((res = ast_mkdir(dest, mode))) {
1609                 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1610                 return -1;
1611         }
1612         return 0;
1613 }
1614
1615 static const char * const mailbox_folders[] = {
1616 #ifdef IMAP_STORAGE
1617         imapfolder,
1618 #else
1619         "INBOX",
1620 #endif
1621         "Old",
1622         "Work",
1623         "Family",
1624         "Friends",
1625         "Cust1",
1626         "Cust2",
1627         "Cust3",
1628         "Cust4",
1629         "Cust5",
1630         "Deleted",
1631         "Urgent",
1632 };
1633
1634 static const char *mbox(struct ast_vm_user *vmu, int id)
1635 {
1636 #ifdef IMAP_STORAGE
1637         if (vmu && id == 0) {
1638                 return vmu->imapfolder;
1639         }
1640 #endif
1641         return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1642 }
1643
1644 static int get_folder_by_name(const char *name)
1645 {
1646         size_t i;
1647
1648         for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1649                 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1650                         return i;
1651                 }
1652         }
1653
1654         return -1;
1655 }
1656
1657 static void free_user(struct ast_vm_user *vmu)
1658 {
1659         if (ast_test_flag(vmu, VM_ALLOCED)) {
1660                 if (vmu->emailbody != NULL) {
1661                         ast_free(vmu->emailbody);
1662                         vmu->emailbody = NULL;
1663                 }
1664                 if (vmu->emailsubject != NULL) {
1665                         ast_free(vmu->emailsubject);
1666                         vmu->emailsubject = NULL;
1667                 }
1668                 ast_free(vmu);
1669         }
1670 }
1671
1672 /* All IMAP-specific functions should go in this block. This
1673  * keeps them from being spread out all over the code */
1674 #ifdef IMAP_STORAGE
1675 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1676 {
1677         char arg[10];
1678         struct vm_state *vms;
1679         unsigned long messageNum;
1680
1681         /* If greetings aren't stored in IMAP, just delete the file */
1682         if (msgnum < 0 && !imapgreetings) {
1683                 ast_filedelete(file, NULL);
1684                 return;
1685         }
1686
1687         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1688                 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);
1689                 return;
1690         }
1691
1692         /* find real message number based on msgnum */
1693         /* this may be an index into vms->msgArray based on the msgnum. */
1694         messageNum = vms->msgArray[msgnum];
1695         if (messageNum == 0) {
1696                 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1697                 return;
1698         }
1699         if (option_debug > 2)
1700                 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1701         /* delete message */
1702         snprintf (arg, sizeof(arg), "%lu", messageNum);
1703         ast_mutex_lock(&vms->lock);
1704         mail_setflag (vms->mailstream, arg, "\\DELETED");
1705         mail_expunge(vms->mailstream);
1706         ast_mutex_unlock(&vms->lock);
1707 }
1708
1709 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1710 {
1711         struct vm_state *vms_p;
1712         char *file, *filename;
1713         char *attachment;
1714         int ret = 0, i;
1715         BODY *body;
1716
1717         /* This function is only used for retrieval of IMAP greetings
1718          * regular messages are not retrieved this way, nor are greetings
1719          * if they are stored locally*/
1720         if (msgnum > -1 || !imapgreetings) {
1721                 return 0;
1722         } else {
1723                 file = strrchr(ast_strdupa(dir), '/');
1724                 if (file)
1725                         *file++ = '\0';
1726                 else {
1727                         ast_debug (1, "Failed to procure file name from directory passed.\n");
1728                         return -1;
1729                 }
1730         }
1731
1732         /* check if someone is accessing this box right now... */
1733         if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && 
1734                 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1735                 /* Unlike when retrieving a message, it is reasonable not to be able to find a 
1736                 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1737                 * that's all we need to do.
1738                 */
1739                 if (!(vms_p = create_vm_state_from_user(vmu))) {
1740                         ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1741                         return -1;
1742                 }
1743         }
1744         
1745         /* Greetings will never have a prepended message */
1746         *vms_p->introfn = '\0';
1747
1748         ast_mutex_lock(&vms_p->lock);
1749         ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1750         if (!vms_p->mailstream) {
1751                 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1752                 ast_mutex_unlock(&vms_p->lock);
1753                 return -1;
1754         }
1755
1756         /*XXX Yuck, this could probably be done a lot better */
1757         for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1758                 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1759                 /* We have the body, now we extract the file name of the first attachment. */
1760                 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1761                         attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1762                 } else {
1763                         ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1764                         ast_mutex_unlock(&vms_p->lock);
1765                         return -1;
1766                 }
1767                 filename = strsep(&attachment, ".");
1768                 if (!strcmp(filename, file)) {
1769                         ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1770                         vms_p->msgArray[vms_p->curmsg] = i + 1;
1771                         save_body(body, vms_p, "2", attachment, 0);
1772                         ast_mutex_unlock(&vms_p->lock);
1773                         return 0;
1774                 }
1775         }
1776         ast_mutex_unlock(&vms_p->lock);
1777
1778         return -1;
1779 }
1780
1781 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1782 {
1783         BODY *body;
1784         char *header_content;
1785         char *attachedfilefmt;
1786         char buf[80];
1787         struct vm_state *vms;
1788         char text_file[PATH_MAX];
1789         FILE *text_file_ptr;
1790         int res = 0;
1791         struct ast_vm_user *vmu;
1792
1793         if (!(vmu = find_user(NULL, context, mailbox))) {
1794                 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1795                 return -1;
1796         }
1797         
1798         if (msgnum < 0) {
1799                 if (imapgreetings) {
1800                         res = imap_retrieve_greeting(dir, msgnum, vmu);
1801                         goto exit;
1802                 } else {
1803                         res = 0;
1804                         goto exit;
1805                 }
1806         }
1807
1808         /* Before anything can happen, we need a vm_state so that we can
1809          * actually access the imap server through the vms->mailstream
1810          */
1811         if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1812                 /* This should not happen. If it does, then I guess we'd
1813                  * need to create the vm_state, extract which mailbox to
1814                  * open, and then set up the msgArray so that the correct
1815                  * IMAP message could be accessed. If I have seen correctly
1816                  * though, the vms should be obtainable from the vmstates list
1817                  * and should have its msgArray properly set up.
1818                  */
1819                 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1820                 res = -1;
1821                 goto exit;
1822         }
1823         
1824         make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1825         snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1826
1827         /* Don't try to retrieve a message from IMAP if it already is on the file system */
1828         if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1829                 res = 0;
1830                 goto exit;
1831         }
1832
1833         if (option_debug > 2)
1834                 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1835         if (vms->msgArray[msgnum] == 0) {
1836                 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1837                 res = -1;
1838                 goto exit;
1839         }
1840
1841         /* This will only work for new messages... */
1842         ast_mutex_lock(&vms->lock);
1843         header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1844         ast_mutex_unlock(&vms->lock);
1845         /* empty string means no valid header */
1846         if (ast_strlen_zero(header_content)) {
1847                 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1848                 res = -1;
1849                 goto exit;
1850         }
1851
1852         ast_mutex_lock(&vms->lock);
1853         mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1854         ast_mutex_unlock(&vms->lock);
1855
1856         /* We have the body, now we extract the file name of the first attachment. */
1857         if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1858                 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1859         } else {
1860                 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1861                 res = -1;
1862                 goto exit;
1863         }
1864         
1865         /* Find the format of the attached file */
1866
1867         strsep(&attachedfilefmt, ".");
1868         if (!attachedfilefmt) {
1869                 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1870                 res = -1;
1871                 goto exit;
1872         }
1873         
1874         save_body(body, vms, "2", attachedfilefmt, 0);
1875         if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1876                 *vms->introfn = '\0';
1877         }
1878
1879         /* Get info from headers!! */
1880         snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1881
1882         if (!(text_file_ptr = fopen(text_file, "w"))) {
1883                 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1884         }
1885
1886         fprintf(text_file_ptr, "%s\n", "[message]");
1887
1888         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1889         fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1890         get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1891         fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1892         get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1893         fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1894         get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1895         fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1896         get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1897         fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1898         get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1899         fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1900         get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1901         fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1902         fclose(text_file_ptr);
1903
1904 exit:
1905         free_user(vmu);
1906         return res;
1907 }
1908
1909 static int folder_int(const char *folder)
1910 {
1911         /*assume a NULL folder means INBOX*/
1912         if (!folder)
1913                 return 0;
1914         if (!strcasecmp(folder, imapfolder))
1915                 return 0;
1916         else if (!strcasecmp(folder, "Old"))
1917                 return 1;
1918         else if (!strcasecmp(folder, "Work"))
1919                 return 2;
1920         else if (!strcasecmp(folder, "Family"))
1921                 return 3;
1922         else if (!strcasecmp(folder, "Friends"))
1923                 return 4;
1924         else if (!strcasecmp(folder, "Cust1"))
1925                 return 5;
1926         else if (!strcasecmp(folder, "Cust2"))
1927                 return 6;
1928         else if (!strcasecmp(folder, "Cust3"))
1929                 return 7;
1930         else if (!strcasecmp(folder, "Cust4"))
1931                 return 8;
1932         else if (!strcasecmp(folder, "Cust5"))
1933                 return 9;
1934         else /*assume they meant INBOX if folder is not found otherwise*/
1935                 return 0;
1936 }
1937
1938 static int __messagecount(const char *context, const char *mailbox, const char *folder)
1939 {
1940         SEARCHPGM *pgm;
1941         SEARCHHEADER *hdr;
1942
1943         struct ast_vm_user *vmu, vmus;
1944         struct vm_state *vms_p;
1945         int ret = 0;
1946         int fold = folder_int(folder);
1947         int urgent = 0;
1948         
1949         /* If URGENT, then look at INBOX */
1950         if (fold == 11) {
1951                 fold = NEW_FOLDER;
1952                 urgent = 1;
1953         }
1954
1955         if (ast_strlen_zero(mailbox))
1956                 return 0;
1957
1958         /* We have to get the user before we can open the stream! */
1959         vmu = find_user(&vmus, context, mailbox);
1960         if (!vmu) {
1961                 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1962                 return -1;
1963         } else {
1964                 /* No IMAP account available */
1965                 if (vmu->imapuser[0] == '\0') {
1966                         ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1967                         return -1;
1968                 }
1969         }
1970         
1971         /* No IMAP account available */
1972         if (vmu->imapuser[0] == '\0') {
1973                 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1974                 free_user(vmu);
1975                 return -1;
1976         }
1977
1978         /* check if someone is accessing this box right now... */
1979         vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
1980         if (!vms_p) {
1981                 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1982         }
1983         if (vms_p) {
1984                 ast_debug(3, "Returning before search - user is logged in\n");
1985                 if (fold == 0) { /* INBOX */
1986                         return vms_p->newmessages;
1987                 }
1988                 if (fold == 1) { /* Old messages */
1989                         return vms_p->oldmessages;
1990                 }
1991                 if (fold == 11) {/*Urgent messages*/
1992                         return vms_p->urgentmessages;
1993                 }
1994         }
1995
1996         /* add one if not there... */
1997         vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
1998         if (!vms_p) {
1999                 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2000         }
2001
2002         if (!vms_p) {
2003                 vms_p = create_vm_state_from_user(vmu);
2004         }
2005         ret = init_mailstream(vms_p, fold);
2006         if (!vms_p->mailstream) {
2007                 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2008                 return -1;
2009         }
2010         if (ret == 0) {
2011                 ast_mutex_lock(&vms_p->lock);
2012                 pgm = mail_newsearchpgm ();
2013                 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2014                 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2015                 pgm->header = hdr;
2016                 if (fold != 1) {
2017                         pgm->unseen = 1;
2018                         pgm->seen = 0;
2019                 }
2020                 /* In the special case where fold is 1 (old messages) we have to do things a bit
2021                  * differently. Old messages are stored in the INBOX but are marked as "seen"
2022                  */
2023                 else {
2024                         pgm->unseen = 0;
2025                         pgm->seen = 1;
2026                 }
2027                 /* look for urgent messages */
2028                 if (urgent) {
2029                         pgm->flagged = 1;
2030                         pgm->unflagged = 0;
2031                 }
2032                 pgm->undeleted = 1;
2033                 pgm->deleted = 0;
2034
2035                 vms_p->vmArrayIndex = 0;
2036                 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2037                 if (fold == 0 && urgent == 0)
2038                         vms_p->newmessages = vms_p->vmArrayIndex;
2039                 if (fold == 1)
2040                         vms_p->oldmessages = vms_p->vmArrayIndex;
2041                 if (fold == 0 && urgent == 1)
2042                         vms_p->urgentmessages = vms_p->vmArrayIndex;
2043                 /*Freeing the searchpgm also frees the searchhdr*/
2044                 mail_free_searchpgm(&pgm);
2045                 ast_mutex_unlock(&vms_p->lock);
2046                 vms_p->updated = 0;
2047                 return vms_p->vmArrayIndex;
2048         } else {
2049                 ast_mutex_lock(&vms_p->lock);
2050                 mail_ping(vms_p->mailstream);
2051                 ast_mutex_unlock(&vms_p->lock);
2052         }
2053         return 0;
2054 }
2055
2056 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2057 {
2058         /* Check if mailbox is full */
2059         check_quota(vms, vmu->imapfolder);
2060         if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2061                 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2062                 ast_play_and_wait(chan, "vm-mailboxfull");
2063                 return -1;
2064         }
2065         
2066         /* Check if we have exceeded maxmsg */
2067         if (msgnum >= vmu->maxmsg  - inprocess_count(vmu->mailbox, vmu->context, 0)) {
2068                 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);
2069                 ast_play_and_wait(chan, "vm-mailboxfull");
2070                 return -1;
2071         }
2072
2073         return 0;
2074 }
2075
2076 /*!
2077  * \brief Gets the number of messages that exist in a mailbox folder.
2078  * \param context
2079  * \param mailbox
2080  * \param folder
2081  * 
2082  * This method is used when IMAP backend is used.
2083  * \return The number of messages in this mailbox folder (zero or more).
2084  */
2085 static int messagecount(const char *context, const char *mailbox, const char *folder)
2086 {
2087         if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2088                 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2089         } else {
2090                 return __messagecount(context, mailbox, folder);
2091         }
2092 }
2093
2094 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)
2095 {
2096         char *myserveremail = serveremail;
2097         char fn[PATH_MAX];
2098         char introfn[PATH_MAX];
2099         char mailbox[256];
2100         char *stringp;
2101         FILE *p = NULL;
2102         char tmp[80] = "/tmp/astmail-XXXXXX";
2103         long len;
2104         void *buf;
2105         int tempcopy = 0;
2106         STRING str;
2107         int ret; /* for better error checking */
2108         char *imap_flags = NIL;
2109         int msgcount = (messagecount(vmu->context, vmu->mailbox, "INBOX") + messagecount(vmu->context, vmu->mailbox, "Old"));
2110
2111     /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2112     if (msgnum < 0 && !imapgreetings) {
2113         return 0;
2114     }
2115         
2116         if (imap_check_limits(chan, vms, vmu, msgcount)) {
2117                 return -1;
2118         }
2119
2120         /* Set urgent flag for IMAP message */
2121         if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2122                 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2123                 imap_flags = "\\FLAGGED";
2124         }
2125         
2126         /* Attach only the first format */
2127         fmt = ast_strdupa(fmt);
2128         stringp = fmt;
2129         strsep(&stringp, "|");
2130
2131         if (!ast_strlen_zero(vmu->serveremail))
2132                 myserveremail = vmu->serveremail;
2133
2134         if (msgnum > -1)
2135                 make_file(fn, sizeof(fn), dir, msgnum);
2136         else
2137                 ast_copy_string (fn, dir, sizeof(fn));
2138
2139         snprintf(introfn, sizeof(introfn), "%sintro", fn);
2140         if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2141                 *introfn = '\0';
2142         }
2143         
2144         if (ast_strlen_zero(vmu->email)) {
2145                 /* We need the vmu->email to be set when we call make_email_file, but
2146                  * if we keep it set, a duplicate e-mail will be created. So at the end
2147                  * of this function, we will revert back to an empty string if tempcopy
2148                  * is 1.
2149                  */
2150                 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2151                 tempcopy = 1;
2152         }
2153
2154         if (!strcmp(fmt, "wav49"))
2155                 fmt = "WAV";
2156         ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2157
2158         /* Make a temporary file instead of piping directly to sendmail, in case the mail
2159            command hangs. */
2160         if (!(p = vm_mkftemp(tmp))) {
2161                 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2162                 if (tempcopy)
2163                         *(vmu->email) = '\0';
2164                 return -1;
2165         }
2166
2167         if (msgnum < 0 && imapgreetings) {
2168                 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2169                         ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2170                         return -1;
2171                 }
2172                 imap_delete_old_greeting(fn, vms);
2173         }
2174
2175         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);
2176         /* read mail file to memory */
2177         len = ftell(p);
2178         rewind(p);
2179         if (!(buf = ast_malloc(len + 1))) {
2180                 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2181                 fclose(p);
2182                 if (tempcopy)
2183                         *(vmu->email) = '\0';
2184                 return -1;
2185         }
2186         if (fread(buf, len, 1, p) < len) {
2187                 if (ferror(p)) {
2188                         ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2189                         return -1;
2190                 }
2191         }
2192         ((char *) buf)[len] = '\0';
2193         INIT(&str, mail_string, buf, len);
2194         ret = init_mailstream(vms, NEW_FOLDER);
2195         if (ret == 0) {
2196                 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2197                 ast_mutex_lock(&vms->lock);
2198                 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2199                         ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2200                 ast_mutex_unlock(&vms->lock);
2201                 fclose(p);
2202                 unlink(tmp);
2203                 ast_free(buf);
2204         } else {
2205                 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2206                 fclose(p);
2207                 unlink(tmp);
2208                 ast_free(buf);
2209                 return -1;
2210         }
2211         ast_debug(3, "%s stored\n", fn);
2212         
2213         if (tempcopy)
2214                 *(vmu->email) = '\0';
2215         
2216         return 0;
2217
2218 }
2219
2220 /*!
2221  * \brief Gets the number of messages that exist in the inbox folder.
2222  * \param mailbox_context
2223  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2224  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2225  * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2226  * 
2227  * This method is used when IMAP backend is used.
2228  * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2229  *
2230  * \return zero on success, -1 on error.
2231  */
2232
2233 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2234 {
2235         char tmp[PATH_MAX] = "";
2236         char *mailboxnc;
2237         char *context;
2238         char *mb;
2239         char *cur;
2240         if (newmsgs)
2241                 *newmsgs = 0;
2242         if (oldmsgs)
2243                 *oldmsgs = 0;
2244         if (urgentmsgs)
2245                 *urgentmsgs = 0;
2246
2247         ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2248         /* If no mailbox, return immediately */
2249         if (ast_strlen_zero(mailbox_context))
2250                 return 0;
2251         
2252         ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2253         context = strchr(tmp, '@');
2254         if (strchr(mailbox_context, ',')) {
2255                 int tmpnew, tmpold, tmpurgent;
2256                 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2257                 mb = tmp;
2258                 while ((cur = strsep(&mb, ", "))) {
2259                         if (!ast_strlen_zero(cur)) {
2260                                 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2261                                         return -1;
2262                                 else {
2263                                         if (newmsgs)
2264                                                 *newmsgs += tmpnew; 
2265                                         if (oldmsgs)
2266                                                 *oldmsgs += tmpold;
2267                                         if (urgentmsgs)
2268                                                 *urgentmsgs += tmpurgent;
2269                                 }
2270                         }
2271                 }
2272                 return 0;
2273         }
2274         if (context) {
2275                 *context = '\0';
2276                 mailboxnc = tmp;
2277                 context++;
2278         } else {
2279                 context = "default";
2280                 mailboxnc = (char *) mailbox_context;
2281         }
2282
2283         if (newmsgs) {
2284                 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2285                 if (!vmu) {
2286                         ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2287                         return -1;
2288                 }
2289                 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2290                         return -1;
2291                 }
2292         }
2293         if (oldmsgs) {
2294                 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2295                         return -1;
2296                 }
2297         }
2298         if (urgentmsgs) {
2299                 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2300                         return -1;
2301                 }
2302         }
2303         return 0;
2304 }
2305
2306 /** 
2307  * \brief Determines if the given folder has messages.
2308  * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2309  * \param folder the folder to look in
2310  *
2311  * This function is used when the mailbox is stored in an IMAP back end.
2312  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2313  * \return 1 if the folder has one or more messages. zero otherwise.
2314  */
2315
2316 static int has_voicemail(const char *mailbox, const char *folder)
2317 {
2318         char tmp[256], *tmp2, *box, *context;
2319         ast_copy_string(tmp, mailbox, sizeof(tmp));
2320         tmp2 = tmp;
2321         if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2322                 while ((box = strsep(&tmp2, ",&"))) {
2323                         if (!ast_strlen_zero(box)) {
2324                                 if (has_voicemail(box, folder)) {
2325                                         return 1;
2326                                 }
2327                         }
2328                 }
2329         }
2330         if ((context = strchr(tmp, '@'))) {
2331                 *context++ = '\0';
2332         } else {
2333                 context = "default";
2334         }
2335         return __messagecount(context, tmp, folder) ? 1 : 0;
2336 }
2337
2338 /*!
2339  * \brief Copies a message from one mailbox to another.
2340  * \param chan
2341  * \param vmu
2342  * \param imbox
2343  * \param msgnum
2344  * \param duration
2345  * \param recip
2346  * \param fmt
2347  * \param dir
2348  *
2349  * This works with IMAP storage based mailboxes.
2350  *
2351  * \return zero on success, -1 on error.
2352  */
2353 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)
2354 {
2355         struct vm_state *sendvms = NULL, *destvms = NULL;
2356         char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2357         if (msgnum >= recip->maxmsg) {
2358                 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2359                 return -1;
2360         }
2361         if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2362                 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2363                 return -1;
2364         }
2365         if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2366                 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2367                 return -1;
2368         }
2369         snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2370         ast_mutex_lock(&sendvms->lock);
2371         if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2372                 ast_mutex_unlock(&sendvms->lock);
2373                 return 0;
2374         }
2375         ast_mutex_unlock(&sendvms->lock);
2376         ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2377         return -1;
2378 }
2379
2380 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2381 {
2382         char tmp[256], *t = tmp;
2383         size_t left = sizeof(tmp);
2384         
2385         if (box == OLD_FOLDER) {
2386                 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2387         } else {
2388                 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2389         }
2390
2391         if (box == NEW_FOLDER) {
2392                 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2393         } else {
2394                 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2395         }
2396
2397         /* Build up server information */
2398         ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2399
2400         /* Add authentication user if present */
2401         if (!ast_strlen_zero(authuser))
2402                 ast_build_string(&t, &left, "/authuser=%s", authuser);
2403
2404         /* Add flags if present */
2405         if (!ast_strlen_zero(imapflags))
2406                 ast_build_string(&t, &left, "/%s", imapflags);
2407
2408         /* End with username */
2409 #if 1
2410         ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2411 #else
2412         ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2413 #endif
2414         if (box == NEW_FOLDER || box == OLD_FOLDER)
2415                 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2416         else if (box == GREETINGS_FOLDER)
2417                 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2418         else {  /* Other folders such as Friends, Family, etc... */
2419                 if (!ast_strlen_zero(imapparentfolder)) {
2420                         /* imapparentfolder would typically be set to INBOX */
2421                         snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2422                 } else {
2423                         snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2424                 }
2425         }
2426 }
2427
2428 static int init_mailstream(struct vm_state *vms, int box)
2429 {
2430         MAILSTREAM *stream = NIL;
2431         long debug;
2432         char tmp[256];
2433         
2434         if (!vms) {
2435                 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2436                 return -1;
2437         }
2438         if (option_debug > 2)
2439                 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2440         if (vms->mailstream == NIL || !vms->mailstream) {
2441                 if (option_debug)
2442                         ast_log(LOG_DEBUG, "mailstream not set.\n");
2443         } else {
2444                 stream = vms->mailstream;
2445         }
2446         /* debug = T;  user wants protocol telemetry? */
2447         debug = NIL;  /* NO protocol telemetry? */
2448
2449         if (delimiter == '\0') {                /* did not probe the server yet */
2450                 char *cp;
2451 #ifdef USE_SYSTEM_IMAP
2452 #include <imap/linkage.c>
2453 #elif defined(USE_SYSTEM_CCLIENT)
2454 #include <c-client/linkage.c>
2455 #else
2456 #include "linkage.c"
2457 #endif
2458                 /* Connect to INBOX first to get folders delimiter */
2459                 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2460                 ast_mutex_lock(&vms->lock);
2461                 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2462                 ast_mutex_unlock(&vms->lock);
2463                 if (stream == NIL) {
2464                         ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2465                         return -1;
2466                 }
2467                 get_mailbox_delimiter(stream);
2468                 /* update delimiter in imapfolder */
2469                 for (cp = vms->imapfolder; *cp; cp++)
2470                         if (*cp == '/')
2471                                 *cp = delimiter;
2472         }
2473         /* Now connect to the target folder */
2474         imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2475         if (option_debug > 2)
2476                 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2477         ast_mutex_lock(&vms->lock);
2478         vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2479         ast_mutex_unlock(&vms->lock);
2480         if (vms->mailstream == NIL) {
2481                 return -1;
2482         } else {
2483                 return 0;
2484         }
2485 }
2486
2487 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2488 {
2489         SEARCHPGM *pgm;
2490         SEARCHHEADER *hdr;
2491         int ret, urgent = 0;
2492
2493         /* If Urgent, then look at INBOX */
2494         if (box == 11) {
2495                 box = NEW_FOLDER;
2496                 urgent = 1;
2497         }
2498
2499         ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2500         ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2501         vms->imapversion = vmu->imapversion;
2502         ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2503
2504         if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2505                 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2506                 return -1;
2507         }
2508         
2509         create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2510         
2511         /* Check Quota */
2512         if  (box == 0)  {
2513                 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2514                 check_quota(vms, (char *) mbox(vmu, box));
2515         }
2516
2517         ast_mutex_lock(&vms->lock);
2518         pgm = mail_newsearchpgm();
2519
2520         /* Check IMAP folder for Asterisk messages only... */
2521         hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2522         hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2523         pgm->header = hdr;
2524         pgm->deleted = 0;
2525         pgm->undeleted = 1;
2526
2527         /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2528         if (box == NEW_FOLDER && urgent == 1) {
2529                 pgm->unseen = 1;
2530                 pgm->seen = 0;
2531                 pgm->flagged = 1;
2532                 pgm->unflagged = 0;
2533         } else if (box == NEW_FOLDER && urgent == 0) {
2534                 pgm->unseen = 1;
2535                 pgm->seen = 0;
2536                 pgm->flagged = 0;
2537                 pgm->unflagged = 1;
2538         } else if (box == OLD_FOLDER) {
2539                 pgm->seen = 1;
2540                 pgm->unseen = 0;
2541         }
2542
2543         ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2544
2545         vms->vmArrayIndex = 0;
2546         mail_search_full (vms->mailstream, NULL, pgm, NIL);
2547         vms->lastmsg = vms->vmArrayIndex - 1;
2548         mail_free_searchpgm(&pgm);
2549
2550         ast_mutex_unlock(&vms->lock);
2551         return 0;
2552 }
2553
2554 static void write_file(char *filename, char *buffer, unsigned long len)
2555 {
2556         FILE *output;
2557
2558         output = fopen (filename, "w");
2559         if (fwrite(buffer, len, 1, output) != 1) {
2560                 if (ferror(output)) {
2561                         ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2562                 }
2563         }
2564         fclose (output);
2565 }
2566
2567 static void update_messages_by_imapuser(const char *user, unsigned long number)
2568 {
2569         struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2570
2571         if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2572                 return;
2573         }
2574
2575         ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2576         vms->msgArray[vms->vmArrayIndex++] = number;
2577 }
2578
2579 void mm_searched(MAILSTREAM *stream, unsigned long number)
2580 {
2581         char *mailbox = stream->mailbox, buf[1024] = "", *user;
2582
2583         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2584                 return;
2585
2586         update_messages_by_imapuser(user, number);
2587 }
2588
2589 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2590 {
2591         struct ast_variable *var;
2592         struct ast_vm_user *vmu;
2593
2594         vmu = ast_calloc(1, sizeof *vmu);
2595         if (!vmu)
2596                 return NULL;
2597         ast_set_flag(vmu, VM_ALLOCED);
2598         populate_defaults(vmu);
2599
2600         var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2601         if (var) {
2602                 apply_options_full(vmu, var);
2603                 ast_variables_destroy(var);
2604                 return vmu;
2605         } else {
2606                 ast_free(vmu);
2607                 return NULL;
2608         }
2609 }
2610
2611 /* Interfaces to C-client */
2612
2613 void mm_exists(MAILSTREAM * stream, unsigned long number)
2614 {
2615         /* mail_ping will callback here if new mail! */
2616         ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2617         if (number == 0) return;
2618         set_update(stream);
2619 }
2620
2621
2622 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2623 {
2624         /* mail_ping will callback here if expunged mail! */
2625         ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2626         if (number == 0) return;
2627         set_update(stream);
2628 }
2629
2630
2631 void mm_flags(MAILSTREAM * stream, unsigned long number)
2632 {
2633         /* mail_ping will callback here if read mail! */
2634         ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2635         if (number == 0) return;
2636         set_update(stream);
2637 }
2638
2639
2640 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2641 {
2642         ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2643         mm_log (string, errflg);
2644 }
2645
2646
2647 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2648 {
2649         if (delimiter == '\0') {
2650                 delimiter = delim;
2651         }
2652
2653         ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2654         if (attributes & LATT_NOINFERIORS)
2655                 ast_debug(5, "no inferiors\n");
2656         if (attributes & LATT_NOSELECT)
2657                 ast_debug(5, "no select\n");
2658         if (attributes & LATT_MARKED)
2659                 ast_debug(5, "marked\n");
2660         if (attributes & LATT_UNMARKED)
2661                 ast_debug(5, "unmarked\n");
2662 }
2663
2664
2665 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2666 {
2667         ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2668         if (attributes & LATT_NOINFERIORS)
2669                 ast_debug(5, "no inferiors\n");
2670         if (attributes & LATT_NOSELECT)
2671                 ast_debug(5, "no select\n");
2672         if (attributes & LATT_MARKED)
2673                 ast_debug(5, "marked\n");
2674         if (attributes & LATT_UNMARKED)
2675                 ast_debug(5, "unmarked\n");
2676 }
2677
2678
2679 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2680 {
2681         ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2682         if (status->flags & SA_MESSAGES)
2683                 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2684         if (status->flags & SA_RECENT)
2685                 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2686         if (status->flags & SA_UNSEEN)
2687                 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2688         if (status->flags & SA_UIDVALIDITY)
2689                 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2690         if (status->flags & SA_UIDNEXT)
2691                 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2692         ast_log(AST_LOG_NOTICE, "\n");
2693 }
2694
2695
2696 void mm_log(char *string, long errflg)
2697 {
2698         switch ((short) errflg) {
2699                 case NIL:
2700                         ast_debug(1, "IMAP Info: %s\n", string);
2701                         break;
2702                 case PARSE:
2703                 case WARN:
2704                         ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2705                         break;
2706                 case ERROR:
2707                         ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2708                         break;
2709         }
2710 }
2711
2712
2713 void mm_dlog(char *string)
2714 {
2715         ast_log(AST_LOG_NOTICE, "%s\n", string);
2716 }
2717
2718
2719 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2720 {
2721         struct ast_vm_user *vmu;
2722
2723         ast_debug(4, "Entering callback mm_login\n");
2724
2725         ast_copy_string(user, mb->user, MAILTMPLEN);
2726
2727         /* We should only do this when necessary */
2728         if (!ast_strlen_zero(authpassword)) {
2729                 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2730         } else {
2731                 AST_LIST_TRAVERSE(&users, vmu, list) {
2732                         if (!strcasecmp(mb->user, vmu->imapuser)) {
2733                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2734                                 break;
2735                         }
2736                 }
2737                 if (!vmu) {
2738                         if ((vmu = find_user_realtime_imapuser(mb->user))) {
2739                                 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2740                                 free_user(vmu);
2741                         }
2742                 }
2743         }
2744 }
2745
2746
2747 void mm_critical(MAILSTREAM * stream)
2748 {
2749 }
2750
2751
2752 void mm_nocritical(MAILSTREAM * stream)
2753 {
2754 }
2755
2756
2757 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2758 {
2759         kill (getpid (), SIGSTOP);
2760         return NIL;
2761 }
2762
2763
2764 void mm_fatal(char *string)
2765 {
2766         ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2767 }
2768
2769 /* C-client callback to handle quota */
2770 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2771 {
2772         struct vm_state *vms;
2773         char *mailbox = stream->mailbox, *user;
2774         char buf[1024] = "";
2775         unsigned long usage = 0, limit = 0;
2776         
2777         while (pquota) {
2778                 usage = pquota->usage;
2779                 limit = pquota->limit;
2780                 pquota = pquota->next;
2781         }
2782         
2783         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)))) {
2784                 ast_log(AST_LOG_ERROR, "No state found.\n");
2785                 return;
2786         }
2787
2788         ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2789
2790         vms->quota_usage = usage;
2791         vms->quota_limit = limit;
2792 }
2793
2794 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2795 {
2796         char *start, *eol_pnt;
2797         int taglen;
2798
2799         if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2800                 return NULL;
2801
2802         taglen = strlen(tag) + 1;
2803         if (taglen < 1)
2804                 return NULL;
2805
2806         if (!(start = strstr(header, tag)))
2807                 return NULL;
2808
2809         /* Since we can be called multiple times we should clear our buffer */
2810         memset(buf, 0, len);
2811
2812         ast_copy_string(buf, start+taglen, len);
2813         if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2814                 *eol_pnt = '\0';
2815         return buf;
2816 }
2817
2818 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2819 {
2820         char *start, *quote, *eol_pnt;
2821
2822         if (ast_strlen_zero(mailbox))
2823                 return NULL;
2824
2825         if (!(start = strstr(mailbox, "/user=")))
2826                 return NULL;
2827
2828         ast_copy_string(buf, start+6, len);
2829
2830         if (!(quote = strchr(buf, '\"'))) {
2831                 if (!(eol_pnt = strchr(buf, '/')))
2832                         eol_pnt = strchr(buf,'}');
2833                 *eol_pnt = '\0';
2834                 return buf;
2835         } else {
2836                 eol_pnt = strchr(buf+1,'\"');
2837                 *eol_pnt = '\0';
2838                 return buf+1;
2839         }
2840 }
2841
2842 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2843 {
2844         struct vm_state *vms_p;
2845
2846         pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2847         if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2848                 return vms_p;
2849         }
2850         if (option_debug > 4)
2851                 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2852         if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2853                 return NULL;
2854         ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2855         ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
2856         ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2857         ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2858         vms_p->mailstream = NIL; /* save for access from interactive entry point */
2859         vms_p->imapversion = vmu->imapversion;
2860         if (option_debug > 4)
2861                 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2862         vms_p->updated = 1;
2863         /* set mailbox to INBOX! */
2864         ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
2865         init_vm_state(vms_p);
2866         vmstate_insert(vms_p);
2867         return vms_p;
2868 }
2869
2870 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2871 {
2872         struct vmstate *vlist = NULL;
2873
2874         if (interactive) {
2875                 struct vm_state *vms;
2876                 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2877                 vms = pthread_getspecific(ts_vmstate.key);
2878                 return vms;
2879         }
2880
2881         AST_LIST_LOCK(&vmstates);
2882         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2883                 if (!vlist->vms) {
2884                         ast_debug(3, "error: vms is NULL for %s\n", user);
2885                         continue;
2886                 }
2887                 if (vlist->vms->imapversion != imapversion) {
2888                         continue;
2889                 }
2890                 if (!vlist->vms->imapuser) {
2891                         ast_debug(3, "error: imapuser is NULL for %s\n", user);
2892                         continue;
2893                 }
2894
2895                 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2896                         AST_LIST_UNLOCK(&vmstates);
2897                         return vlist->vms;
2898                 }
2899         }
2900         AST_LIST_UNLOCK(&vmstates);
2901
2902         ast_debug(3, "%s not found in vmstates\n", user);
2903
2904         return NULL;
2905 }
2906
2907 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2908 {
2909
2910         struct vmstate *vlist = NULL;
2911         const char *local_context = S_OR(context, "default");
2912
2913         if (interactive) {
2914                 struct vm_state *vms;
2915                 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2916                 vms = pthread_getspecific(ts_vmstate.key);
2917                 return vms;
2918         }
2919
2920         AST_LIST_LOCK(&vmstates);
2921         AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2922                 if (!vlist->vms) {
2923                         ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2924                         continue;
2925                 }
2926                 if (vlist->vms->imapversion != imapversion) {
2927                         continue;
2928                 }
2929                 if (!vlist->vms->username || !vlist->vms->context) {
2930                         ast_debug(3, "error: username is NULL for %s\n", mailbox);
2931                         continue;
2932                 }
2933
2934                 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);
2935                 
2936                 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2937                         ast_debug(3, "Found it!\n");
2938                         AST_LIST_UNLOCK(&vmstates);
2939                         return vlist->vms;
2940                 }
2941         }
2942         AST_LIST_UNLOCK(&vmstates);
2943
2944         ast_debug(3, "%s not found in vmstates\n", mailbox);
2945
2946         return NULL;
2947 }
2948
2949 static void vmstate_insert(struct vm_state *vms) 
2950 {
2951         struct vmstate *v;
2952         struct vm_state *altvms;
2953
2954         /* If interactive, it probably already exists, and we should
2955            use the one we already have since it is more up to date.
2956            We can compare the username to find the duplicate */
2957         if (vms->interactive == 1) {
2958                 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2959                 if (altvms) {   
2960                         ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2961                         vms->newmessages = altvms->newmessages;
2962                         vms->oldmessages = altvms->oldmessages;
2963                         vms->vmArrayIndex = altvms->vmArrayIndex;
2964                         vms->lastmsg = altvms->lastmsg;
2965                         vms->curmsg = altvms->curmsg;
2966                         /* get a pointer to the persistent store */
2967                         vms->persist_vms = altvms;
2968                         /* Reuse the mailstream? */
2969 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
2970                         vms->mailstream = altvms->mailstream;
2971 #else
2972                         vms->mailstream = NIL;
2973 #endif
2974                 }
2975                 return;
2976         }
2977
2978         if (!(v = ast_calloc(1, sizeof(*v))))
2979                 return;
2980         
2981         v->vms = vms;
2982
2983         ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2984
2985         AST_LIST_LOCK(&vmstates);
2986         AST_LIST_INSERT_TAIL(&vmstates, v, list);
2987         AST_LIST_UNLOCK(&vmstates);
2988 }
2989
2990 static void vmstate_delete(struct vm_state *vms) 
2991 {
2992         struct vmstate *vc = NULL;
2993         struct vm_state *altvms = NULL;
2994
2995         /* If interactive, we should copy pertinent info
2996            back to the persistent state (to make update immediate) */
2997         if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2998                 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2999                 altvms->newmessages = vms->newmessages;
3000                 altvms->oldmessages = vms->oldmessages;
3001                 altvms->updated = 1;
3002                 vms->mailstream = mail_close(vms->mailstream);
3003
3004                 /* Interactive states are not stored within the persistent list */
3005                 return;
3006         }
3007         
3008         ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3009         
3010         AST_LIST_LOCK(&vmstates);
3011         AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
3012                 if (vc->vms == vms) {
3013                         AST_LIST_REMOVE_CURRENT(list);
3014                         break;
3015                 }
3016         }
3017         AST_LIST_TRAVERSE_SAFE_END
3018         AST_LIST_UNLOCK(&vmstates);
3019         
3020         if (vc) {
3021                 ast_mutex_destroy(&vc->vms->lock);
3022                 ast_free(vc);
3023         }
3024         else
3025                 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3026 }
3027
3028 static void set_update(MAILSTREAM * stream) 
3029 {
3030         struct vm_state *vms;
3031         char *mailbox = stream->mailbox, *user;
3032         char buf[1024] = "";
3033
3034         if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
3035                 if (user && option_debug > 2)
3036                         ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
3037                 return;
3038         }
3039
3040         ast_debug(3, "User %s mailbox set for update.\n", user);
3041
3042         vms->updated = 1; /* Set updated flag since mailbox changed */
3043 }
3044
3045 static void init_vm_state(struct vm_state *vms) 
3046 {
3047         int x;
3048         vms->vmArrayIndex = 0;
3049         for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
3050                 vms->msgArray[x] = 0;
3051         }
3052         ast_mutex_init(&vms->lock);
3053 }
3054
3055 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro) 
3056 {
3057         char *body_content;
3058         char *body_decoded;
3059         char *fn = is_intro ? vms->introfn : vms->fn;
3060         unsigned long len;
3061         unsigned long newlen;
3062         char filename[256];
3063         
3064         if (!body || body == NIL)
3065                 return -1;
3066
3067         ast_mutex_lock(&vms->lock);
3068         body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
3069         ast_mutex_unlock(&vms->lock);
3070         if (body_content != NIL) {
3071                 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
3072                 /* ast_debug(1,body_content); */
3073                 body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
3074                 /* If the body of the file is empty, return an error */
3075                 if (!newlen) {
3076                         return -1;
3077                 }
3078                 write_file(filename, (char *) body_decoded, newlen);
3079         } else {
3080                 ast_debug(5, "Body of message is NULL.\n");
3081                 return -1;
3082         }
3083         return 0;
3084 }
3085
3086 /*! 
3087  * \brief Get delimiter via mm_list callback 
3088  * \param stream
3089  *
3090  * Determines the delimiter character that is used by the underlying IMAP based mail store.
3091  */
3092 /* MUTEX should already be held */
3093 static void get_mailbox_delimiter(MAILSTREAM *stream) {
3094         char tmp[50];
3095         snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
3096         mail_list(stream, tmp, "*");
3097 }
3098
3099 /*! 
3100  * \brief Check Quota for user 
3101  * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3102  * \param mailbox the mailbox to check the quota for.
3103  *
3104  * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3105  */
3106 static void check_quota(struct vm_state *vms, char *mailbox) {
3107         ast_mutex_lock(&vms->lock);
3108         mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3109         ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3110         if (vms && vms->mailstream != NULL) {
3111                 imap_getquotaroot(vms->mailstream, mailbox);
3112         } else {
3113                 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3114         }
3115         ast_mutex_unlock(&vms->lock);
3116 }
3117
3118 #endif /* IMAP_STORAGE */
3119
3120 /*! \brief Lock file path
3121     only return failure if ast_lock_path returns 'timeout',
3122    not if the path does not exist or any other reason
3123 */
3124 static int vm_lock_path(const char *path)
3125 {
3126         switch (ast_lock_path(path)) {
3127         case AST_LOCK_TIMEOUT:
3128                 return -1;
3129         default:
3130                 return 0;
3131         }
3132 }
3133
3134
3135 #ifdef ODBC_STORAGE
3136 struct generic_prepare_struct {
3137         char *sql;
3138         int argc;
3139         char **argv;
3140 };
3141
3142 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
3143 {
3144         struct generic_prepare_struct *gps = data;
3145         int res, i;
3146         SQLHSTMT stmt;
3147
3148         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3149         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3150                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3151                 return NULL;
3152         }
3153         res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
3154         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3155                 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
3156                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3157                 return NULL;
3158         }
3159         for (i = 0; i < gps->argc; i++)
3160                 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
3161
3162         return stmt;
3163 }
3164
3165 /*!
3166  * \brief Retrieves a file from an ODBC data store.
3167  * \param dir the path to the file to be retreived.
3168  * \param msgnum the message number, such as within a mailbox folder.
3169  * 
3170  * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
3171  * 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.
3172  *
3173  * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
3174  * The output is the message information file with the name msgnum and the extension .txt
3175  * and the message file with the extension of its format, in the directory with base file name of the msgnum.
3176  * 
3177  * \return 0 on success, -1 on error.
3178  */
3179 static int retrieve_file(char *dir, int msgnum)
3180 {
3181         int x = 0;
3182         int res;
3183         int fd = -1;
3184         size_t fdlen = 0;
3185         void *fdm = MAP_FAILED;
3186         SQLSMALLINT colcount = 0;
3187         SQLHSTMT stmt;
3188         char sql[PATH_MAX];
3189         char fmt[80]="";
3190         char *c;
3191         char coltitle[256];
3192         SQLSMALLINT collen;
3193         SQLSMALLINT datatype;
3194         SQLSMALLINT decimaldigits;
3195         SQLSMALLINT nullable;
3196         SQLULEN colsize;
3197         SQLLEN colsize2;
3198         FILE *f = NULL;
3199         char rowdata[80];
3200         char fn[PATH_MAX];
3201         char full_fn[PATH_MAX];
3202         char msgnums[80];
3203         char *argv[] = { dir, msgnums };
3204         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3205
3206         struct odbc_obj *obj;
3207         obj = ast_odbc_request_obj(odbc_database, 0);
3208         if (obj) {
3209                 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3210                 c = strchr(fmt, '|');
3211                 if (c)
3212                         *c = '\0';
3213                 if (!strcasecmp(fmt, "wav49"))
3214                         strcpy(fmt, "WAV");
3215                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3216                 if (msgnum > -1)
3217                         make_file(fn, sizeof(fn), dir, msgnum);
3218                 else
3219                         ast_copy_string(fn, dir, sizeof(fn));
3220
3221                 /* Create the information file */
3222                 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3223                 
3224                 if (!(f = fopen(full_fn, "w+"))) {
3225                         ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
3226                         goto yuck;
3227                 }
3228                 
3229                 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3230                 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3231                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3232                 if (!stmt) {
3233                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3234                         ast_odbc_release_obj(obj);
3235                         goto yuck;
3236                 }
3237                 res = SQLFetch(stmt);
3238                 if (res == SQL_NO_DATA) {
3239                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3240                         ast_odbc_release_obj(obj);
3241                         goto yuck;
3242                 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3243                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3244                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3245                         ast_odbc_release_obj(obj);
3246                         goto yuck;
3247                 }
3248                 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3249                 if (fd < 0) {
3250                         ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3251                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3252                         ast_odbc_release_obj(obj);
3253                         goto yuck;
3254                 }
3255                 res = SQLNumResultCols(stmt, &colcount);
3256                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {   
3257                         ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3258                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3259                         ast_odbc_release_obj(obj);
3260                         goto yuck;
3261                 }
3262                 if (f) 
3263                         fprintf(f, "[message]\n");
3264                 for (x = 0; x < colcount; x++) {
3265                         rowdata[0] = '\0';
3266                         collen = sizeof(coltitle);
3267                         res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen, 
3268                                                 &datatype, &colsize, &decimaldigits, &nullable);
3269                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3270                                 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3271                                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3272                                 ast_odbc_release_obj(obj);
3273                                 goto yuck;
3274                         }
3275                         if (!strcasecmp(coltitle, "recording")) {
3276                                 off_t offset;
3277                                 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3278                                 fdlen = colsize2;
3279                                 if (fd > -1) {
3280                                         char tmp[1]="";
3281                                         lseek(fd, fdlen - 1, SEEK_SET);
3282                                         if (write(fd, tmp, 1) != 1) {
3283                                                 close(fd);
3284                                                 fd = -1;
3285                                                 continue;
3286                                         }
3287                                         /* Read out in small chunks */
3288                                         for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3289                                                 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3290                                                         ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3291                                                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3292                                                         ast_odbc_release_obj(obj);
3293                                                         goto yuck;
3294                                                 } else {
3295                                                         res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3296                                                         munmap(fdm, CHUNKSIZE);
3297                                                         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3298                                                                 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3299                                                                 unlink(full_fn);
3300                                                                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3301                                                                 ast_odbc_release_obj(obj);
3302                                                                 goto yuck;
3303                                                         }
3304                                                 }
3305                                         }
3306                                         if (truncate(full_fn, fdlen) < 0) {
3307                                                 ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3308                                         }
3309                                 }
3310                         } else {
3311                                 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3312                                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3313                                         ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3314                                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3315                                         ast_odbc_release_obj(obj);
3316                                         goto yuck;
3317                                 }
3318                                 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
3319                                         fprintf(f, "%s=%s\n", coltitle, rowdata);
3320                         }
3321                 }
3322                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3323                 ast_odbc_release_obj(obj);
3324         } else
3325                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3326 yuck:   
3327         if (f)
3328                 fclose(f);
3329         if (fd > -1)
3330                 close(fd);
3331         return x - 1;
3332 }
3333
3334 /*!
3335  * \brief Determines the highest message number in use for a given user and mailbox folder.
3336  * \param vmu 
3337  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3338  *
3339  * This method is used when mailboxes are stored in an ODBC back end.
3340  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3341  *
3342  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3343  */
3344 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3345 {
3346         int x = 0;
3347         int res;
3348         SQLHSTMT stmt;
3349         char sql[PATH_MAX];
3350         char rowdata[20];
3351         char *argv[] = { dir };
3352         struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3353
3354         struct odbc_obj *obj;
3355         obj = ast_odbc_request_obj(odbc_database, 0);
3356         if (obj) {
3357                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
3358                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3359                 if (!stmt) {
3360                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3361                         ast_odbc_release_obj(obj);
3362                         goto yuck;
3363                 }
3364                 res = SQLFetch(stmt);
3365                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3366                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3367                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3368                         ast_odbc_release_obj(obj);
3369                         goto yuck;
3370                 }
3371                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3372                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3373                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3374                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3375                         ast_odbc_release_obj(obj);
3376                         goto yuck;
3377                 }
3378                 if (sscanf(rowdata, "%30d", &x) != 1)
3379                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3380                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3381                 ast_odbc_release_obj(obj);
3382         } else
3383                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3384 yuck:   
3385         return x - 1;
3386 }
3387
3388 /*!
3389  * \brief Determines if the specified message exists.
3390  * \param dir the folder the mailbox folder to look for messages. 
3391  * \param msgnum the message index to query for.
3392  *
3393  * This method is used when mailboxes are stored in an ODBC back end.
3394  *
3395  * \return greater than zero if the message exists, zero when the message does not exist or on error.
3396  */
3397 static int message_exists(char *dir, int msgnum)
3398 {
3399         int x = 0;
3400         int res;
3401         SQLHSTMT stmt;
3402         char sql[PATH_MAX];
3403         char rowdata[20];
3404         char msgnums[20];
3405         char *argv[] = { dir, msgnums };
3406         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3407
3408         struct odbc_obj *obj;
3409         obj = ast_odbc_request_obj(odbc_database, 0);
3410         if (obj) {
3411                 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3412                 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3413                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3414                 if (!stmt) {
3415                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3416                         ast_odbc_release_obj(obj);
3417                         goto yuck;
3418                 }
3419                 res = SQLFetch(stmt);
3420                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3421                         ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3422                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3423                         ast_odbc_release_obj(obj);
3424                         goto yuck;
3425                 }
3426                 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3427                 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3428                         ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3429                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3430                         ast_odbc_release_obj(obj);
3431                         goto yuck;
3432                 }
3433                 if (sscanf(rowdata, "%30d", &x) != 1)
3434                         ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
3435                 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3436                 ast_odbc_release_obj(obj);
3437         } else
3438                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3439 yuck:   
3440         return x;
3441 }
3442
3443 /*!
3444  * \brief returns the one-based count for messages.
3445  * \param vmu
3446  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3447  *
3448  * This method is used when mailboxes are stored in an ODBC back end.
3449  * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
3450  * one-based messages.
3451  * This method just calls last_message_index and returns +1 of its value.
3452  *
3453  * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
3454  */
3455 static int count_messages(struct ast_vm_user *vmu, char *dir)
3456 {
3457         return last_message_index(vmu, dir) + 1;
3458 }
3459
3460 /*!
3461  * \brief Deletes a message from the mailbox folder.
3462  * \param sdir The mailbox folder to work in.
3463  * \param smsg The message index to be deleted.
3464  *
3465  * This method is used when mailboxes are stored in an ODBC back end.
3466  * The specified message is directly deleted from the database 'voicemessages' table.
3467  * 
3468  * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3469  */
3470 static void delete_file(const char *sdir, int smsg)
3471 {
3472         SQLHSTMT stmt;
3473         char sql[PATH_MAX];
3474         char msgnums[20];
3475         char *argv[] = { NULL, msgnums };
3476         struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3477         struct odbc_obj *obj;
3478
3479         argv[0] = ast_strdupa(sdir);
3480
3481         obj = ast_odbc_request_obj(odbc_database, 0);
3482         if (obj) {
3483                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3484                 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3485                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3486                 if (!stmt)
3487                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3488                 else
3489                         SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3490                 ast_odbc_release_obj(obj);
3491         } else
3492                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3493         return; 
3494 }
3495
3496 /*!
3497  * \brief Copies a voicemail from one mailbox to another.
3498  * \param sdir the folder for which to look for the message to be copied.
3499  * \param smsg the index of the message to be copied.
3500  * \param ddir the destination folder to copy the message into.
3501  * \param dmsg the index to be used for the copied message.
3502  * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3503  * \param dmailboxcontext The context for the destination user.
3504  *
3505  * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3506  */
3507 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3508 {
3509         SQLHSTMT stmt;
3510         char sql[512];
3511         char msgnums[20];
3512         char msgnumd[20];
3513         struct odbc_obj *obj;
3514         char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3515         struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3516
3517         delete_file(ddir, dmsg);
3518         obj = ast_odbc_request_obj(odbc_database, 0);
3519         if (obj) {
3520                 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3521                 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3522                 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);
3523                 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3524                 if (!stmt)
3525                         ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3526                 else
3527                         SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3528                 ast_odbc_release_obj(obj);
3529         } else
3530                 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3531         return; 
3532 }
3533
3534 struct insert_data {
3535         char *sql;
3536         const char *dir;
3537         const char *msgnums;
3538         void *data;
3539         SQLLEN datalen;
3540         SQLLEN indlen;
3541         const char *context;
3542         const char *macrocontext;
3543         const char *callerid;
3544         const char *origtime;
3545         const char *duration;
3546         const char *mailboxuser;
3547         const char *mailboxcontext;
3548         const char *category;
3549         const char *flag;
3550 };
3551
3552 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3553 {
3554         struct insert_data *data = vdata;
3555         int res;
3556         SQLHSTMT stmt;
3557
3558         res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3559         if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3560                 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3561                 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3562                 return NULL;
3563         }
3564
3565         SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
3566         SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
3567         SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
3568         SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
3569         SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
3570         SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
3571         SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
3572         SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
3573         SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
3574         SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
3575         SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
3576         if (!ast_strlen_zero(data->category)) {
3577                 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
3578         }
3579         res = SQLExecDirect(stmt,&nbs