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