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