Asterisk - The Open Source Telephony Project  GIT-master-a24979a
app_voicemail.c
Go to the documentation of this file.
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  * unixODBC (http://www.unixodbc.org/)
25  * 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 voicemail.conf "Config_voicemail"
30  * \note For information about voicemail IMAP storage, https://wiki.asterisk.org/wiki/display/AST/IMAP+Voicemail+Storage
31  * \ingroup applications
32  * \todo This module requires res_adsi to load. This needs to be optional
33  * during compilation.
34  *
35  * \todo 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 /*! \li \ref app_voicemail.c uses configuration file \ref voicemail.conf
41  * \addtogroup configuration_file Configuration Files
42  */
43 
44 /*!
45  * \page voicemail.conf voicemail.conf
46  * \verbinclude voicemail.conf.sample
47  */
48 
49 #include "asterisk.h"
50 
51 #ifdef IMAP_STORAGE
52 #include <ctype.h>
53 #include <signal.h>
54 #include <pwd.h>
55 #ifdef USE_SYSTEM_IMAP
56 #include <imap/c-client.h>
57 #include <imap/imap4r1.h>
58 #include <imap/linkage.h>
59 #elif defined (USE_SYSTEM_CCLIENT)
60 #include <c-client/c-client.h>
61 #include <c-client/imap4r1.h>
62 #include <c-client/linkage.h>
63 #else
64 #include "c-client.h"
65 #include "imap4r1.h"
66 #include "linkage.h"
67 #endif
68 #endif
69 
70 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
71 #include <sys/time.h>
72 #include <sys/stat.h>
73 #include <sys/mman.h>
74 #include <time.h>
75 #include <dirent.h>
76 #if defined(__FreeBSD__) || defined(__OpenBSD__)
77 #include <sys/wait.h>
78 #endif
79 
80 #include "asterisk/logger.h"
81 #include "asterisk/lock.h"
82 #include "asterisk/file.h"
83 #include "asterisk/channel.h"
84 #include "asterisk/pbx.h"
85 #include "asterisk/config.h"
86 #include "asterisk/say.h"
87 #include "asterisk/module.h"
88 #include "asterisk/adsi.h"
89 #include "asterisk/app.h"
90 #include "asterisk/mwi.h"
91 #include "asterisk/manager.h"
92 #include "asterisk/dsp.h"
93 #include "asterisk/localtime.h"
94 #include "asterisk/cli.h"
95 #include "asterisk/utils.h"
96 #include "asterisk/stringfields.h"
97 #include "asterisk/strings.h"
98 #include "asterisk/smdi.h"
99 #include "asterisk/astobj2.h"
100 #include "asterisk/taskprocessor.h"
101 #include "asterisk/test.h"
102 #include "asterisk/format_cache.h"
103 
104 #ifdef ODBC_STORAGE
105 #include "asterisk/res_odbc.h"
106 #endif
107 
108 #ifdef IMAP_STORAGE
109 #include "asterisk/threadstorage.h"
110 #endif
111 
112 /*** DOCUMENTATION
113  <application name="VoiceMail" language="en_US">
114  <synopsis>
115  Leave a Voicemail message.
116  </synopsis>
117  <syntax>
118  <parameter name="mailboxs" argsep="&amp;" required="true">
119  <argument name="mailbox1" argsep="@" required="true">
120  <argument name="mailbox" required="true" />
121  <argument name="context" />
122  </argument>
123  <argument name="mailbox2" argsep="@" multiple="true">
124  <argument name="mailbox" required="true" />
125  <argument name="context" />
126  </argument>
127  </parameter>
128  <parameter name="options">
129  <optionlist>
130  <option name="b">
131  <para>Play the <literal>busy</literal> greeting to the calling party.</para>
132  </option>
133  <option name="d">
134  <argument name="c" />
135  <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
136  if played during the greeting. Context defaults to the current context.</para>
137  </option>
138  <option name="e">
139  <para>Play greetings as early media -- only answer the channel just
140  before accepting the voice message.</para>
141  </option>
142  <option name="g">
143  <argument name="#" required="true" />
144  <para>Use the specified amount of gain when recording the voicemail
145  message. The units are whole-number decibels (dB). Only works on supported
146  technologies, which is DAHDI only.</para>
147  </option>
148  <option name="s">
149  <para>Skip the playback of instructions for leaving a message to the
150  calling party.</para>
151  </option>
152  <option name="S">
153  <para>Skip the playback of instructions for leaving a message to the
154  calling party, but only if a greeting has been recorded by the
155  mailbox user.</para>
156  </option>
157  <option name="t">
158  <argument name="x" required="false" />
159  <para>Play a custom beep tone to the caller instead of the default one.
160  If this option is used but no file is specified, the beep is suppressed.</para>
161  </option>
162  <option name="u">
163  <para>Play the <literal>unavailable</literal> greeting.</para>
164  </option>
165  <option name="U">
166  <para>Mark message as <literal>URGENT</literal>.</para>
167  </option>
168  <option name="P">
169  <para>Mark message as <literal>PRIORITY</literal>.</para>
170  </option>
171  </optionlist>
172  </parameter>
173  </syntax>
174  <description>
175  <para>This application allows the calling party to leave a message for the specified
176  list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
177  the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
178  exist.</para>
179  <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
180  <enumlist>
181  <enum name="0">
182  <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
183  </enum>
184  <enum name="*">
185  <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
186  </enum>
187  </enumlist>
188  <para>This application will set the following channel variable upon completion:</para>
189  <variablelist>
190  <variable name="VMSTATUS">
191  <para>This indicates the status of the execution of the VoiceMail application.</para>
192  <value name="SUCCESS" />
193  <value name="USEREXIT" />
194  <value name="FAILED" />
195  </variable>
196  </variablelist>
197  </description>
198  <see-also>
199  <ref type="application">VoiceMailMain</ref>
200  </see-also>
201  </application>
202  <application name="VoiceMailMain" language="en_US">
203  <synopsis>
204  Check Voicemail messages.
205  </synopsis>
206  <syntax>
207  <parameter name="mailbox" required="true" argsep="@">
208  <argument name="mailbox" />
209  <argument name="context" />
210  </parameter>
211  <parameter name="options">
212  <optionlist>
213  <option name="p">
214  <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
215  the mailbox that is entered by the caller.</para>
216  </option>
217  <option name="g">
218  <argument name="#" required="true" />
219  <para>Use the specified amount of gain when recording a voicemail message.
220  The units are whole-number decibels (dB).</para>
221  </option>
222  <option name="s">
223  <para>Skip checking the passcode for the mailbox.</para>
224  </option>
225  <option name="a">
226  <argument name="folder" required="true" />
227  <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
228  Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
229  <enumlist>
230  <enum name="0"><para>INBOX</para></enum>
231  <enum name="1"><para>Old</para></enum>
232  <enum name="2"><para>Work</para></enum>
233  <enum name="3"><para>Family</para></enum>
234  <enum name="4"><para>Friends</para></enum>
235  <enum name="5"><para>Cust1</para></enum>
236  <enum name="6"><para>Cust2</para></enum>
237  <enum name="7"><para>Cust3</para></enum>
238  <enum name="8"><para>Cust4</para></enum>
239  <enum name="9"><para>Cust5</para></enum>
240  </enumlist>
241  </option>
242  </optionlist>
243  </parameter>
244  </syntax>
245  <description>
246  <para>This application allows the calling party to check voicemail messages. A specific
247  <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
248  may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
249  be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
250  <literal>default</literal> context will be used.</para>
251  <para>The VoiceMailMain application will exit if the following DTMF digit is entered as Mailbox
252  or Password, and the extension exists:</para>
253  <enumlist>
254  <enum name="*">
255  <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
256  </enum>
257  </enumlist>
258  </description>
259  <see-also>
260  <ref type="application">VoiceMail</ref>
261  </see-also>
262  </application>
263  <application name="VMAuthenticate" language="en_US">
264  <synopsis>
265  Authenticate with Voicemail passwords.
266  </synopsis>
267  <syntax>
268  <parameter name="mailbox" required="true" argsep="@">
269  <argument name="mailbox" />
270  <argument name="context" />
271  </parameter>
272  <parameter name="options">
273  <optionlist>
274  <option name="s">
275  <para>Skip playing the initial prompts.</para>
276  </option>
277  </optionlist>
278  </parameter>
279  </syntax>
280  <description>
281  <para>This application behaves the same way as the Authenticate application, but the passwords
282  are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
283  specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
284  is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
285  mailbox.</para>
286  <para>The VMAuthenticate application will exit if the following DTMF digit is entered as Mailbox
287  or Password, and the extension exists:</para>
288  <enumlist>
289  <enum name="*">
290  <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
291  </enum>
292  </enumlist>
293  </description>
294  </application>
295  <application name="VoiceMailPlayMsg" language="en_US">
296  <synopsis>
297  Play a single voice mail msg from a mailbox by msg id.
298  </synopsis>
299  <syntax>
300  <parameter name="mailbox" required="true" argsep="@">
301  <argument name="mailbox" />
302  <argument name="context" />
303  </parameter>
304  <parameter name="msg_id" required="true">
305  <para>The msg id of the msg to play back. </para>
306  </parameter>
307  </syntax>
308  <description>
309  <para>This application sets the following channel variable upon completion:</para>
310  <variablelist>
311  <variable name="VOICEMAIL_PLAYBACKSTATUS">
312  <para>The status of the playback attempt as a text string.</para>
313  <value name="SUCCESS"/>
314  <value name="FAILED"/>
315  </variable>
316  </variablelist>
317  </description>
318  </application>
319  <application name="VMSayName" language="en_US">
320  <synopsis>
321  Play the name of a voicemail user
322  </synopsis>
323  <syntax>
324  <parameter name="mailbox" required="true" argsep="@">
325  <argument name="mailbox" />
326  <argument name="context" />
327  </parameter>
328  </syntax>
329  <description>
330  <para>This application will say the recorded name of the voicemail user specified as the
331  argument to this application. If no context is provided, <literal>default</literal> is assumed.</para>
332  <para>Similar to the Background() application, playback of the recorded
333  name can be interrupted by entering an extension, which will be searched
334  for in the current context.</para>
335  </description>
336  </application>
337  <function name="VM_INFO" language="en_US">
338  <synopsis>
339  Returns the selected attribute from a mailbox.
340  </synopsis>
341  <syntax argsep=",">
342  <parameter name="mailbox" argsep="@" required="true">
343  <argument name="mailbox" required="true" />
344  <argument name="context" />
345  </parameter>
346  <parameter name="attribute" required="true">
347  <optionlist>
348  <option name="count">
349  <para>Count of messages in specified <replaceable>folder</replaceable>.
350  If <replaceable>folder</replaceable> is not specified, defaults to <literal>INBOX</literal>.</para>
351  </option>
352  <option name="email">
353  <para>E-mail address associated with the mailbox.</para>
354  </option>
355  <option name="exists">
356  <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.</para>
357  </option>
358  <option name="fullname">
359  <para>Full name associated with the mailbox.</para>
360  </option>
361  <option name="language">
362  <para>Mailbox language if overridden, otherwise the language of the channel.</para>
363  </option>
364  <option name="locale">
365  <para>Mailbox locale if overridden, otherwise global locale.</para>
366  </option>
367  <option name="pager">
368  <para>Pager e-mail address associated with the mailbox.</para>
369  </option>
370  <option name="password">
371  <para>Mailbox access password.</para>
372  </option>
373  <option name="tz">
374  <para>Mailbox timezone if overridden, otherwise global timezone</para>
375  </option>
376  </optionlist>
377  </parameter>
378  <parameter name="folder" required="false">
379  <para>If not specified, <literal>INBOX</literal> is assumed.</para>
380  </parameter>
381  </syntax>
382  <description>
383  <para>Returns the selected attribute from the specified <replaceable>mailbox</replaceable>.
384  If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
385  context. Where the <replaceable>folder</replaceable> can be specified, common folders
386  include <literal>INBOX</literal>, <literal>Old</literal>, <literal>Work</literal>,
387  <literal>Family</literal> and <literal>Friends</literal>.</para>
388  </description>
389  </function>
390  <manager name="VoicemailUsersList" language="en_US">
391  <synopsis>
392  List All Voicemail User Information.
393  </synopsis>
394  <syntax>
395  <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
396  </syntax>
397  <description>
398  </description>
399  </manager>
400  <manager name="VoicemailUserStatus" language="en_US">
401  <synopsis>
402  Show the status of given voicemail user's info.
403  </synopsis>
404  <syntax>
405  <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
406  <parameter name="Context" required="true">
407  <para>The context you want to check.</para>
408  </parameter>
409  <parameter name="Mailbox" required="true">
410  <para>The mailbox you want to check.</para>
411  </parameter>
412  </syntax>
413  <description>
414  <para>Retrieves the status of the given voicemail user.</para>
415  </description>
416  </manager>
417  <manager name="VoicemailRefresh" language="en_US">
418  <synopsis>
419  Tell Asterisk to poll mailboxes for a change
420  </synopsis>
421  <syntax>
422  <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
423  <parameter name="Context" />
424  <parameter name="Mailbox" />
425  </syntax>
426  <description>
427  <para>Normally, MWI indicators are only sent when Asterisk itself
428  changes a mailbox. With external programs that modify the content
429  of a mailbox from outside the application, an option exists called
430  <literal>pollmailboxes</literal> that will cause voicemail to
431  continually scan all mailboxes on a system for changes. This can
432  cause a large amount of load on a system. This command allows
433  external applications to signal when a particular mailbox has
434  changed, thus permitting external applications to modify mailboxes
435  and MWI to work without introducing considerable CPU load.</para>
436  <para>If <replaceable>Context</replaceable> is not specified, all
437  mailboxes on the system will be polled for changes. If
438  <replaceable>Context</replaceable> is specified, but
439  <replaceable>Mailbox</replaceable> is omitted, then all mailboxes
440  within <replaceable>Context</replaceable> will be polled.
441  Otherwise, only a single mailbox will be polled for changes.</para>
442  </description>
443  </manager>
444  ***/
445 
446 #ifdef IMAP_STORAGE
447 static char imapserver[48] = "localhost";
448 static char imapport[8] = "143";
449 static char imapflags[128];
450 static char imapfolder[64] = "INBOX";
451 static char imapparentfolder[64];
452 static char greetingfolder[64] = "INBOX";
453 static char authuser[32];
454 static char authpassword[42];
455 static int imapversion = 1;
456 
457 static int expungeonhangup = 1;
458 static int imapgreetings;
459 static int imap_poll_logout;
460 static char delimiter;
461 
462 /* mail_open cannot be protected on a stream basis */
463 ast_mutex_t mail_open_lock;
464 
465 struct vm_state;
466 struct ast_vm_user;
467 
468 AST_THREADSTORAGE(ts_vmstate);
469 
470 /* Forward declarations for IMAP */
471 static int init_mailstream(struct vm_state *vms, int box);
472 static void write_file(char *filename, char *buffer, unsigned long len);
473 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
474 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
475 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
476 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
477 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
478 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
479 static void vmstate_insert(struct vm_state *vms);
480 static void vmstate_delete(struct vm_state *vms);
481 static void set_update(MAILSTREAM * stream);
482 static void init_vm_state(struct vm_state *vms);
483 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
484 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream);
485 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
486 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
487 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, const char *msg_id);
488 static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder);
489 static void update_messages_by_imapuser(const char *user, unsigned long number);
490 static int vm_delete(char *file);
491 
492 static int imap_remove_file (char *dir, int msgnum);
493 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
494 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
495 static void check_quota(struct vm_state *vms, char *mailbox);
496 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
497 static void imap_logout(const char *mailbox_id);
498 
499 struct vmstate {
500  struct vm_state *vms;
501  AST_LIST_ENTRY(vmstate) list;
502 };
503 
504 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
505 
506 #endif
507 
508 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
509 
510 #define COMMAND_TIMEOUT 5000
511 /* Don't modify these here; set your umask at runtime instead */
512 #define VOICEMAIL_DIR_MODE 0777
513 #define VOICEMAIL_FILE_MODE 0666
514 #define CHUNKSIZE 65536
515 
516 #define VOICEMAIL_CONFIG "voicemail.conf"
517 #define ASTERISK_USERNAME "asterisk"
518 
519 /* Define fast-forward, pause, restart, and reverse keys
520  * while listening to a voicemail message - these are
521  * strings, not characters */
522 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
523 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
524 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
525 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
526 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
527 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
528 
529 /* Default mail command to mail voicemail. Change it with the
530  * mailcmd= command in voicemail.conf */
531 #define SENDMAIL "/usr/sbin/sendmail -t"
532 #define INTRO "vm-intro"
533 
534 #define MAX_MAIL_BODY_CONTENT_SIZE 134217728L // 128 Mbyte
535 
536 #define MAXMSG 100
537 #define MAXMSGLIMIT 9999
538 
539 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
540 
541 #ifdef IMAP_STORAGE
542 #define ENDL "\r\n"
543 #else
544 #define ENDL "\n"
545 #endif
546 
547 #define MAX_DATETIME_FORMAT 512
548 #define MAX_NUM_CID_CONTEXTS 10
549 
550 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
551 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
552 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
553 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
554 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
555 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
556 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
557 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
558 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
559 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
560 #define VM_DIRECTFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
561 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
562 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
563 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
564 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
565 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
566 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
567 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
568 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
569 #define ERROR_LOCK_PATH -100
570 #define ERROR_MAX_MSGS -101
571 #define OPERATOR_EXIT 300
572 
573 enum vm_box {
579  GREETINGS_FOLDER = -1
580 };
581 
583  OPT_SILENT = (1 << 0),
584  OPT_BUSY_GREETING = (1 << 1),
586  OPT_RECORDGAIN = (1 << 3),
588  OPT_AUTOPLAY = (1 << 6),
589  OPT_DTMFEXIT = (1 << 7),
590  OPT_MESSAGE_Urgent = (1 << 8),
592  OPT_EARLYM_GREETING = (1 << 10),
593  OPT_BEEP = (1 << 11),
594  OPT_SILENT_IF_GREET = (1 << 12),
595 };
596 
602  /* This *must* be the last value in this enum! */
604 };
605 
610 };
611 
625 });
626 
627 static const char * const mailbox_folders[] = {
628 #ifdef IMAP_STORAGE
629  imapfolder,
630 #else
631  "INBOX",
632 #endif
633  "Old",
634  "Work",
635  "Family",
636  "Friends",
637  "Cust1",
638  "Cust2",
639  "Cust3",
640  "Cust4",
641  "Cust5",
642  "Deleted",
643  "Urgent",
644 };
645 
646 static int load_config(int reload);
647 #ifdef TEST_FRAMEWORK
648 static int load_config_from_memory(int reload, struct ast_config *cfg, struct ast_config *ucfg);
649 #endif
650 static int actual_load_config(int reload, struct ast_config *cfg, struct ast_config *ucfg);
651 
652 /*! \page vmlang Voicemail Language Syntaxes Supported
653 
654  \par Syntaxes supported, not really language codes.
655  \arg \b en - English
656  \arg \b de - German
657  \arg \b es - Spanish
658  \arg \b fr - French
659  \arg \b it - Italian
660  \arg \b nl - Dutch
661  \arg \b pt - Portuguese
662  \arg \b pt_BR - Portuguese (Brazil)
663  \arg \b gr - Greek
664  \arg \b no - Norwegian
665  \arg \b se - Swedish
666  \arg \b tw - Chinese (Taiwan)
667  \arg \b ua - Ukrainian
668 
669 German requires the following additional soundfile:
670 \arg \b 1F einE (feminine)
671 
672 Spanish requires the following additional soundfile:
673 \arg \b 1M un (masculine)
674 
675 Dutch, Portuguese & Spanish require the following additional soundfiles:
676 \arg \b vm-INBOXs singular of 'new'
677 \arg \b vm-Olds singular of 'old/heard/read'
678 
679 NB these are plural:
680 \arg \b vm-INBOX nieuwe (nl)
681 \arg \b vm-Old oude (nl)
682 
683 Polish uses:
684 \arg \b vm-new-a 'new', feminine singular accusative
685 \arg \b vm-new-e 'new', feminine plural accusative
686 \arg \b vm-new-ych 'new', feminine plural genitive
687 \arg \b vm-old-a 'old', feminine singular accusative
688 \arg \b vm-old-e 'old', feminine plural accusative
689 \arg \b vm-old-ych 'old', feminine plural genitive
690 \arg \b digits/1-a 'one', not always same as 'digits/1'
691 \arg \b digits/2-ie 'two', not always same as 'digits/2'
692 
693 Swedish uses:
694 \arg \b vm-nytt singular of 'new'
695 \arg \b vm-nya plural of 'new'
696 \arg \b vm-gammalt singular of 'old'
697 \arg \b vm-gamla plural of 'old'
698 \arg \b digits/ett 'one', not always same as 'digits/1'
699 
700 Norwegian uses:
701 \arg \b vm-ny singular of 'new'
702 \arg \b vm-nye plural of 'new'
703 \arg \b vm-gammel singular of 'old'
704 \arg \b vm-gamle plural of 'old'
705 
706 Dutch also uses:
707 \arg \b nl-om 'at'?
708 
709 Spanish also uses:
710 \arg \b vm-youhaveno
711 
712 Italian requires the following additional soundfile:
713 
714 For vm_intro_it:
715 \arg \b vm-nuovo new
716 \arg \b vm-nuovi new plural
717 \arg \b vm-vecchio old
718 \arg \b vm-vecchi old plural
719 
720 Japanese requires the following additional soundfile:
721 \arg \b jp-arimasu there is
722 \arg \b jp-arimasen there is not
723 \arg \b jp-oshitekudasai please press
724 \arg \b jp-ni article ni
725 \arg \b jp-ga article ga
726 \arg \b jp-wa article wa
727 \arg \b jp-wo article wo
728 
729 Chinese (Taiwan) requires the following additional soundfile:
730 \arg \b vm-tong A class-word for call (tong1)
731 \arg \b vm-ri A class-word for day (ri4)
732 \arg \b vm-you You (ni3)
733 \arg \b vm-haveno Have no (mei2 you3)
734 \arg \b vm-have Have (you3)
735 \arg \b vm-listen To listen (yao4 ting1)
736 
737 
738 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
739 spelled among others when you have to change folder. For the above reasons, vm-INBOX
740 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
741 
742 */
743 
744 #define MAX_VM_MBOX_ID_LEN (AST_MAX_EXTENSION)
745 #define MAX_VM_CONTEXT_LEN (AST_MAX_CONTEXT)
746 /* MAX_VM_MAILBOX_LEN allows enough room for the '@' and NULL terminator */
747 #define MAX_VM_MAILBOX_LEN (MAX_VM_MBOX_ID_LEN + MAX_VM_CONTEXT_LEN)
748 
749 /*! Structure for linked list of users
750  * Use ast_vm_user_destroy() to free one of these structures. */
751 struct ast_vm_user {
752  char context[MAX_VM_CONTEXT_LEN];/*!< Voicemail context */
753  char mailbox[MAX_VM_MBOX_ID_LEN];/*!< Mailbox id, unique within vm context */
754  char password[80]; /*!< Secret pin code, numbers only */
755  char fullname[80]; /*!< Full name, for directory app */
756  char *email; /*!< E-mail address */
757  char *emailsubject; /*!< E-mail subject */
758  char *emailbody; /*!< E-mail body */
759  char pager[80]; /*!< E-mail address to pager (no attachment) */
760  char serveremail[80]; /*!< From: Mail address */
761  char fromstring[100]; /*!< From: Username */
762  char language[MAX_LANGUAGE]; /*!< Config: Language setting */
763  char zonetag[80]; /*!< Time zone */
764  char locale[20]; /*!< The locale (for presentation of date/time) */
765  char callback[80];
766  char dialout[80];
767  char uniqueid[80]; /*!< Unique integer identifier */
768  char exit[80];
769  char attachfmt[20]; /*!< Attachment format */
770  unsigned int flags; /*!< VM_ flags */
772  int minsecs; /*!< Minimum number of seconds per message for this mailbox */
773  int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
774  int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
775  int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
776  int passwordlocation; /*!< Storage location of the password */
777 #ifdef IMAP_STORAGE
778  char imapserver[48]; /*!< IMAP server address */
779  char imapport[8]; /*!< IMAP server port */
780  char imapflags[128]; /*!< IMAP optional flags */
781  char imapuser[80]; /*!< IMAP server login */
782  char imappassword[80]; /*!< IMAP server password if authpassword not defined */
783  char imapfolder[64]; /*!< IMAP voicemail folder */
784  char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
785  int imapversion; /*!< If configuration changes, use the new values */
786 #endif
787  double volgain; /*!< Volume gain for voicemails sent via email */
789 };
790 
791 /*! Voicemail time zones */
792 struct vm_zone {
794  char name[80];
795  char timezone[80];
796  char msg_format[512];
797 };
798 
799 #define VMSTATE_MAX_MSG_ARRAY 256
800 
801 /*! Voicemail mailbox state */
802 struct vm_state {
803  char curbox[80];
804  char username[80];
805  char context[80];
808  char fn[PATH_MAX];
810  int *deleted;
811  int *heard;
812  int dh_arraysize; /* used for deleted / heard allocation */
813  int curmsg;
814  int lastmsg;
818  int starting;
819  int repeats;
820 #ifdef IMAP_STORAGE
822  int updated; /*!< decremented on each mail check until 1 -allows delay */
823  long *msgArray;
824  unsigned msg_array_max;
825  MAILSTREAM *mailstream;
826  int vmArrayIndex;
827  char imapuser[80]; /*!< IMAP server login */
828  char imapfolder[64]; /*!< IMAP voicemail folder */
829  char imapserver[48]; /*!< IMAP server address */
830  char imapport[8]; /*!< IMAP server port */
831  char imapflags[128]; /*!< IMAP optional flags */
832  int imapversion;
833  int interactive;
834  char introfn[PATH_MAX]; /*!< Name of prepended file */
835  unsigned int quota_limit;
836  unsigned int quota_usage;
837  struct vm_state *persist_vms;
838 #endif
839 };
840 
841 #ifdef ODBC_STORAGE
842 static char odbc_database[80] = "asterisk";
843 static char odbc_table[80] = "voicemessages";
844 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
845 #define DISPOSE(a,b) remove_file(a,b)
846 #define STORE(a,b,c,d,e,f,g,h,i,j,k) store_file(a,b,c,d)
847 #define EXISTS(a,b,c,d) (message_exists(a,b))
848 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
849 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
850 #define DELETE(a,b,c,d) (delete_file(a,b))
851 #define UPDATE_MSG_ID(a, b, c, d, e, f) (odbc_update_msg_id((a), (b), (c)))
852 #else
853 #ifdef IMAP_STORAGE
854 #define DISPOSE(a,b) (imap_remove_file(a,b))
855 #define STORE(a,b,c,d,e,f,g,h,i,j,k) (imap_store_file(a,b,c,d,e,f,g,h,i,j,k))
856 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
857 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
858 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
859 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
860 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
861 #define UPDATE_MSG_ID(a, b, c, d, e, f) (vm_imap_update_msg_id((a), (b), (c), (d), (e), (f)))
862 #else
863 #define RETRIEVE(a,b,c,d)
864 #define DISPOSE(a,b)
865 #define STORE(a,b,c,d,e,f,g,h,i,j,k)
866 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
867 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
868 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
869 #define DELETE(a,b,c,d) (vm_delete(c))
870 #define UPDATE_MSG_ID(a, b, c, d, e, f)
871 #endif
872 #endif
873 
874 static char VM_SPOOL_DIR[PATH_MAX];
875 
876 static char ext_pass_cmd[128];
877 static char ext_pass_check_cmd[128];
878 
879 static int my_umask;
880 
881 #define PWDCHANGE_INTERNAL (1 << 1)
882 #define PWDCHANGE_EXTERNAL (1 << 2)
884 
885 #ifdef ODBC_STORAGE
886 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
887 #else
888 # ifdef IMAP_STORAGE
889 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
890 # else
891 # define tdesc "Comedian Mail (Voicemail System)"
892 # endif
893 #endif
894 
895 static char userscontext[AST_MAX_EXTENSION] = "default";
896 
897 static char *addesc = "Comedian Mail";
898 
899 /* Leave a message */
900 static char *voicemail_app = "VoiceMail";
901 
902 /* Check mail, control, etc */
903 static char *voicemailmain_app = "VoiceMailMain";
904 
905 static char *vmauthenticate_app = "VMAuthenticate";
906 
907 static char *playmsg_app = "VoiceMailPlayMsg";
908 
909 static char *sayname_app = "VMSayName";
910 
913 static char zonetag[80];
914 static char locale[20];
915 static int maxsilence;
916 static int maxmsg = MAXMSG;
917 static int maxdeletedmsg;
918 static int silencethreshold = 128;
919 static char serveremail[80] = ASTERISK_USERNAME;
920 static char mailcmd[160] = SENDMAIL; /* Configurable mail cmd */
921 static char externnotify[160];
923 static char vmfmts[80] = "wav";
924 static double volgain;
925 static int vmminsecs;
926 static int vmmaxsecs;
927 static int maxgreet;
928 static int skipms = 3000;
929 static int maxlogins = 3;
931 static int passwordlocation;
933 
934 /*! Poll mailboxes for changes since there is something external to
935  * app_voicemail that may change them. */
936 static unsigned int poll_mailboxes;
937 
938 /*! By default, poll every 30 seconds */
939 #define DEFAULT_POLL_FREQ 30
940 /*! Polling frequency */
941 static unsigned int poll_freq = DEFAULT_POLL_FREQ;
942 
944 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
945 static pthread_t poll_thread = AST_PTHREADT_NULL;
946 static unsigned char poll_thread_run;
947 
949 
951  char *alias;
952  char *mailbox;
953  char buf[0];
954 };
955 
957  char *alias;
958  char *mailbox;
959  char buf[0];
960 };
961 
962 #define MAPPING_BUCKETS 511
966 
970 
971 /* custom audio control prompts for voicemail playback */
974 static char listen_control_pause_key[12];
976 static char listen_control_stop_key[12];
977 
978 /* custom password sounds */
979 static char vm_login[80] = "vm-login";
980 static char vm_newuser[80] = "vm-newuser";
981 static char vm_password[80] = "vm-password";
982 static char vm_newpassword[80] = "vm-newpassword";
983 static char vm_passchanged[80] = "vm-passchanged";
984 static char vm_reenterpassword[80] = "vm-reenterpassword";
985 static char vm_mismatch[80] = "vm-mismatch";
986 static char vm_invalid_password[80] = "vm-invalid-password";
987 static char vm_pls_try_again[80] = "vm-pls-try-again";
988 
989 /*
990  * XXX If we have the time, motivation, etc. to fix up this prompt, one of the following would be appropriate:
991  * 1. create a sound along the lines of "Please try again. When done, press the pound key" which could be spliced
992  * from existing sound clips. This would require some programming changes in the area of vm_forward options and also
993  * app.c's __ast_play_and_record function
994  * 2. create a sound prompt saying "Please try again. When done recording, press any key to stop and send the prepended
995  * message." At the time of this comment, I think this would require new voice work to be commissioned.
996  * 3. Something way different like providing instructions before a time out or a post-recording menu. This would require
997  * more effort than either of the other two.
998  */
999 static char vm_prepend_timeout[80] = "vm-then-pound";
1000 
1001 static struct ast_flags globalflags = {0};
1002 
1003 static int saydurationminfo = 2;
1004 
1005 static char dialcontext[AST_MAX_CONTEXT] = "";
1006 static char callcontext[AST_MAX_CONTEXT] = "";
1007 static char exitcontext[AST_MAX_CONTEXT] = "";
1008 
1010 
1011 
1012 static char *emailbody;
1013 static char *emailsubject;
1014 static char *pagerbody;
1015 static char *pagersubject;
1016 static char fromstring[100];
1017 static char pagerfromstring[100];
1018 static char charset[32] = "ISO-8859-1";
1019 
1020 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
1021 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
1022 static int adsiver = 1;
1023 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
1024 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
1025 
1026 /* Forward declarations - generic */
1027 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
1028 static int close_mailbox(struct vm_state *vms, struct ast_vm_user *vmu);
1029 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);
1030 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
1031 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
1032  char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, int *sound_duration, const char *unlockdir,
1033  signed char record_gain, struct vm_state *vms, char *flag, const char *msg_id, int forwardintro);
1034 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
1035 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
1036 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);
1037 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, const char *msg_id);
1038 static void apply_options(struct ast_vm_user *vmu, const char *options);
1039 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);
1040 static int is_valid_dtmf(const char *key);
1041 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
1042 static int write_password_to_file(const char *secretfn, const char *password);
1043 static const char *substitute_escapes(const char *value);
1044 static int message_range_and_existence_check(struct vm_state *vms, const char *msg_ids [], size_t num_msgs, int *msg_nums, struct ast_vm_user *vmu);
1045 static void notify_new_state(struct ast_vm_user *vmu);
1046 static int append_vmu_info_astman(struct mansession *s, struct ast_vm_user *vmu, const char* event_name, const char* actionid);
1047 
1048 
1049 /*!
1050  * Place a message in the indicated folder
1051  *
1052  * \param vmu Voicemail user
1053  * \param vms Current voicemail state for the user
1054  * \param msg The message number to save
1055  * \param box The folder into which the message should be saved
1056  * \param[out] newmsg The new message number of the saved message
1057  * \param move Tells whether to copy or to move the message
1058  *
1059  * \note the "move" parameter is only honored for IMAP voicemail presently
1060  * \retval 0 Success
1061  * \retval other Failure
1062  */
1063 static int save_to_folder(struct ast_vm_user *vmu, struct vm_state *vms, int msg, int box, int *newmsg, int move);
1064 
1065 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_create(const char *mailbox, const char *context, const char *folder, int descending, enum ast_vm_snapshot_sort_val sort_val, int combine_INBOX_and_OLD);
1066 static struct ast_vm_mailbox_snapshot *vm_mailbox_snapshot_destroy(struct ast_vm_mailbox_snapshot *mailbox_snapshot);
1067 
1068 static int vm_msg_forward(const char *from_mailbox, const char *from_context, const char *from_folder, const char *to_mailbox, const char *to_context, const char *to_folder, size_t num_msgs, const char *msg_ids[], int delete_old);
1069 static int vm_msg_move(const char *mailbox, const char *context, size_t num_msgs, const char *oldfolder, const char *old_msg_ids[], const char *newfolder);
1070 static int vm_msg_remove(const char *mailbox, const char *context, size_t num_msgs, const char *folder, const char *msgs[]);
1071 static int vm_msg_play(struct ast_channel *chan, const char *mailbox, const char *context, const char *folder, const char *msg_num, ast_vm_msg_play_cb cb);
1072 
1073 #ifdef TEST_FRAMEWORK
1074 static int vm_test_destroy_user(const char *context, const char *mailbox);
1075 static int vm_test_create_user(const char *context, const char *mailbox);
1076 #endif
1077 
1078 /*!
1079  * \internal
1080  * \brief Parse the given mailbox_id into mailbox and context.
1081  * \since 12.0.0
1082  *
1083  * \param mailbox_id The mailbox\@context string to separate.
1084  * \param mailbox Where the mailbox part will start.
1085  * \param context Where the context part will start. ("default" if not present)
1086  *
1087  * \retval 0 on success.
1088  * \retval -1 on error.
1089  */
1090 static int separate_mailbox(char *mailbox_id, char **mailbox, char **context)
1091 {
1092  if (ast_strlen_zero(mailbox_id) || !mailbox || !context) {
1093  return -1;
1094  }
1095  *context = mailbox_id;
1096  *mailbox = strsep(context, "@");
1097  if (ast_strlen_zero(*mailbox)) {
1098  return -1;
1099  }
1100  if (ast_strlen_zero(*context)) {
1101  *context = "default";
1102  }
1103  return 0;
1104 }
1105 
1107 
1108 struct inprocess {
1109  int count;
1110  char *context;
1111  char mailbox[0];
1112 };
1113 
1114 static int inprocess_hash_fn(const void *obj, const int flags)
1115 {
1116  const struct inprocess *i = obj;
1117  return atoi(i->mailbox);
1118 }
1119 
1120 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
1121 {
1122  struct inprocess *i = obj, *j = arg;
1123  if (strcmp(i->mailbox, j->mailbox)) {
1124  return 0;
1125  }
1126  return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
1127 }
1128 
1129 static int inprocess_count(const char *context, const char *mailbox, int delta)
1130 {
1131  int context_len = strlen(context) + 1;
1132  int mailbox_len = strlen(mailbox) + 1;
1133  struct inprocess *i, *arg = ast_alloca(sizeof(*arg) + context_len + mailbox_len);
1134  arg->context = arg->mailbox + mailbox_len;
1135  ast_copy_string(arg->mailbox, mailbox, mailbox_len); /* SAFE */
1136  ast_copy_string(arg->context, context, context_len); /* SAFE */
1138  if ((i = ao2_find(inprocess_container, arg, 0))) {
1139  int ret = ast_atomic_fetchadd_int(&i->count, delta);
1141  ao2_ref(i, -1);
1142  return ret;
1143  }
1144  if (delta < 0) {
1145  ast_log(LOG_WARNING, "BUG: ref count decrement on non-existing object???\n");
1146  }
1147  if (!(i = ao2_alloc(sizeof(*i) + context_len + mailbox_len, NULL))) {
1149  return 0;
1150  }
1151  i->context = i->mailbox + mailbox_len;
1152  ast_copy_string(i->mailbox, mailbox, mailbox_len); /* SAFE */
1153  ast_copy_string(i->context, context, context_len); /* SAFE */
1154  i->count = delta;
1157  ao2_ref(i, -1);
1158  return 0;
1159 }
1160 
1161 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
1162 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
1163 #endif
1164 
1165 /*!
1166  * \brief Strips control and non 7-bit clean characters from input string.
1167  *
1168  * \note To map control and none 7-bit characters to a 7-bit clean characters
1169  * please use ast_str_encode_mine().
1170  */
1171 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
1172 {
1173  char *bufptr = buf;
1174  for (; *input; input++) {
1175  if (*input < 32) {
1176  continue;
1177  }
1178  *bufptr++ = *input;
1179  if (bufptr == buf + buflen - 1) {
1180  break;
1181  }
1182  }
1183  *bufptr = '\0';
1184  return buf;
1185 }
1186 
1187 
1188 /*!
1189  * \brief Sets default voicemail system options to a voicemail user.
1190  *
1191  * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
1192  * - all the globalflags
1193  * - the saydurationminfo
1194  * - the callcontext
1195  * - the dialcontext
1196  * - the exitcontext
1197  * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
1198  * - volume gain.
1199  * - emailsubject, emailbody set to NULL
1200  */
1201 static void populate_defaults(struct ast_vm_user *vmu)
1202 {
1205  if (saydurationminfo) {
1207  }
1208  ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
1209  ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
1210  ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
1211  ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
1212  ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
1213  if (vmminsecs) {
1214  vmu->minsecs = vmminsecs;
1215  }
1216  if (vmmaxsecs) {
1217  vmu->maxsecs = vmmaxsecs;
1218  }
1219  if (maxmsg) {
1220  vmu->maxmsg = maxmsg;
1221  }
1222  if (maxdeletedmsg) {
1224  }
1225  vmu->volgain = volgain;
1226  ast_free(vmu->email);
1227  vmu->email = NULL;
1228  ast_free(vmu->emailsubject);
1229  vmu->emailsubject = NULL;
1230  ast_free(vmu->emailbody);
1231  vmu->emailbody = NULL;
1232 #ifdef IMAP_STORAGE
1233  ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
1234  ast_copy_string(vmu->imapserver, imapserver, sizeof(vmu->imapserver));
1235  ast_copy_string(vmu->imapport, imapport, sizeof(vmu->imapport));
1236  ast_copy_string(vmu->imapflags, imapflags, sizeof(vmu->imapflags));
1237 #endif
1238 }
1239 
1240 /*!
1241  * \brief Sets a specific property value.
1242  * \param vmu The voicemail user object to work with.
1243  * \param var The name of the property to be set.
1244  * \param value The value to be set to the property.
1245  *
1246  * The property name must be one of the understood properties. See the source for details.
1247  */
1248 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1249 {
1250  int x;
1251  if (!strcasecmp(var, "attach")) {
1253  } else if (!strcasecmp(var, "attachfmt")) {
1254  ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1255  } else if (!strcasecmp(var, "serveremail")) {
1256  ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1257  } else if (!strcasecmp(var, "fromstring")) {
1258  ast_copy_string(vmu->fromstring, value, sizeof(vmu->fromstring));
1259  } else if (!strcasecmp(var, "emailbody")) {
1260  ast_free(vmu->emailbody);
1262  } else if (!strcasecmp(var, "emailsubject")) {
1263  ast_free(vmu->emailsubject);
1265  } else if (!strcasecmp(var, "language")) {
1266  ast_copy_string(vmu->language, value, sizeof(vmu->language));
1267  } else if (!strcasecmp(var, "tz")) {
1268  ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1269  } else if (!strcasecmp(var, "locale")) {
1270  ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
1271 #ifdef IMAP_STORAGE
1272  } else if (!strcasecmp(var, "imapuser")) {
1273  ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1274  vmu->imapversion = imapversion;
1275  } else if (!strcasecmp(var, "imapserver")) {
1276  ast_copy_string(vmu->imapserver, value, sizeof(vmu->imapserver));
1277  vmu->imapversion = imapversion;
1278  } else if (!strcasecmp(var, "imapport")) {
1279  ast_copy_string(vmu->imapport, value, sizeof(vmu->imapport));
1280  vmu->imapversion = imapversion;
1281  } else if (!strcasecmp(var, "imapflags")) {
1282  ast_copy_string(vmu->imapflags, value, sizeof(vmu->imapflags));
1283  vmu->imapversion = imapversion;
1284  } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1285  ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1286  vmu->imapversion = imapversion;
1287  } else if (!strcasecmp(var, "imapfolder")) {
1288  ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1289  vmu->imapversion = imapversion;
1290  } else if (!strcasecmp(var, "imapvmshareid")) {
1291  ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1292  vmu->imapversion = imapversion;
1293 #endif
1294  } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1296  } else if (!strcasecmp(var, "saycid")){
1298  } else if (!strcasecmp(var, "sendvoicemail")){
1300  } else if (!strcasecmp(var, "review")){
1302  } else if (!strcasecmp(var, "tempgreetwarn")){
1304  } else if (!strcasecmp(var, "messagewrap")){
1306  } else if (!strcasecmp(var, "operator")) {
1308  } else if (!strcasecmp(var, "envelope")){
1310  } else if (!strcasecmp(var, "moveheard")){
1312  } else if (!strcasecmp(var, "sayduration")){
1314  } else if (!strcasecmp(var, "saydurationm")){
1315  if (sscanf(value, "%30d", &x) == 1) {
1316  vmu->saydurationm = x;
1317  } else {
1318  ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1319  }
1320  } else if (!strcasecmp(var, "forcename")){
1322  } else if (!strcasecmp(var, "forcegreetings")){
1324  } else if (!strcasecmp(var, "callback")) {
1325  ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1326  } else if (!strcasecmp(var, "dialout")) {
1327  ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1328  } else if (!strcasecmp(var, "exitcontext")) {
1329  ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1330  } else if (!strcasecmp(var, "minsecs")) {
1331  if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1332  vmu->minsecs = x;
1333  } else {
1334  ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1335  vmu->minsecs = vmminsecs;
1336  }
1337  } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1338  vmu->maxsecs = atoi(value);
1339  if (vmu->maxsecs <= 0) {
1340  ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1341  vmu->maxsecs = vmmaxsecs;
1342  } else {
1343  vmu->maxsecs = atoi(value);
1344  }
1345  if (!strcasecmp(var, "maxmessage"))
1346  ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1347  } else if (!strcasecmp(var, "maxmsg")) {
1348  vmu->maxmsg = atoi(value);
1349  /* Accept maxmsg=0 (Greetings only voicemail) */
1350  if (vmu->maxmsg < 0) {
1351  ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1352  vmu->maxmsg = MAXMSG;
1353  } else if (vmu->maxmsg > MAXMSGLIMIT) {
1354  ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1355  vmu->maxmsg = MAXMSGLIMIT;
1356  }
1357  } else if (!strcasecmp(var, "nextaftercmd")) {
1359  } else if (!strcasecmp(var, "backupdeleted")) {
1360  if (sscanf(value, "%30d", &x) == 1)
1361  vmu->maxdeletedmsg = x;
1362  else if (ast_true(value))
1363  vmu->maxdeletedmsg = MAXMSG;
1364  else
1365  vmu->maxdeletedmsg = 0;
1366 
1367  if (vmu->maxdeletedmsg < 0) {
1368  ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1369  vmu->maxdeletedmsg = MAXMSG;
1370  } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1371  ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1372  vmu->maxdeletedmsg = MAXMSGLIMIT;
1373  }
1374  } else if (!strcasecmp(var, "volgain")) {
1375  sscanf(value, "%30lf", &vmu->volgain);
1376  } else if (!strcasecmp(var, "passwordlocation")) {
1377  if (!strcasecmp(value, "spooldir")) {
1379  } else {
1381  }
1382  } else if (!strcasecmp(var, "options")) {
1383  apply_options(vmu, value);
1384  }
1385 }
1386 
1387 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1388 {
1389  int fds[2], pid = 0;
1390 
1391  memset(buf, 0, len);
1392 
1393  if (pipe(fds)) {
1394  snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1395  } else {
1396  /* good to go*/
1397  pid = ast_safe_fork(0);
1398 
1399  if (pid < 0) {
1400  /* ok maybe not */
1401  close(fds[0]);
1402  close(fds[1]);
1403  snprintf(buf, len, "FAILURE: Fork failed");
1404  } else if (pid) {
1405  /* parent */
1406  close(fds[1]);
1407  if (read(fds[0], buf, len) < 0) {
1408  ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1409  }
1410  close(fds[0]);
1411  } else {
1412  /* child */
1414  AST_APP_ARG(v)[20];
1415  );
1416  char *mycmd = ast_strdupa(command);
1417 
1418  close(fds[0]);
1419  dup2(fds[1], STDOUT_FILENO);
1420  close(fds[1]);
1421  ast_close_fds_above_n(STDOUT_FILENO);
1422 
1423  AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1424 
1425  execv(arg.v[0], arg.v);
1426  printf("FAILURE: %s", strerror(errno));
1427  _exit(0);
1428  }
1429  }
1430  return buf;
1431 }
1432 
1433 /*!
1434  * \brief Check that password meets minimum required length
1435  * \param vmu The voicemail user to change the password for.
1436  * \param password The password string to check
1437  *
1438  * \return zero on ok, 1 on not ok.
1439  */
1440 static int check_password(struct ast_vm_user *vmu, char *password)
1441 {
1442  /* check minimum length */
1443  if (strlen(password) < minpassword)
1444  return 1;
1445  /* check that password does not contain '*' character */
1446  if (!ast_strlen_zero(password) && password[0] == '*')
1447  return 1;
1449  char cmd[255], buf[255];
1450 
1451  ast_debug(1, "Verify password policies for %s\n", password);
1452 
1453  snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1454  if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1455  ast_debug(5, "Result: %s\n", buf);
1456  if (!strncasecmp(buf, "VALID", 5)) {
1457  ast_debug(3, "Passed password check: '%s'\n", buf);
1458  return 0;
1459  } else if (!strncasecmp(buf, "FAILURE", 7)) {
1460  ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1461  return 0;
1462  } else {
1463  ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1464  return 1;
1465  }
1466  }
1467  }
1468  return 0;
1469 }
1470 
1471 /*!
1472  * \brief Performs a change of the voicemail passowrd in the realtime engine.
1473  * \param vmu The voicemail user to change the password for.
1474  * \param password The new value to be set to the password for this user.
1475  *
1476  * This only works if there is a realtime engine configured.
1477  * This is called from the (top level) vm_change_password.
1478  *
1479  * \return zero on success, -1 on error.
1480  */
1481 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1482 {
1483  int res = -1;
1484  if (!strcmp(vmu->password, password)) {
1485  /* No change (but an update would return 0 rows updated, so we opt out here) */
1486  return 0;
1487  }
1488 
1489  if (strlen(password) > 10) {
1490  ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1491  }
1492  if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1493  ast_test_suite_event_notify("PASSWORDCHANGED", "Message: realtime engine updated with new password\r\nPasswordSource: realtime");
1494  ast_copy_string(vmu->password, password, sizeof(vmu->password));
1495  res = 0;
1496  }
1497  return res;
1498 }
1499 
1500 /*!
1501  * \brief Destructively Parse options and apply.
1502  */
1503 static void apply_options(struct ast_vm_user *vmu, const char *options)
1504 {
1505  char *stringp;
1506  char *s;
1507  char *var, *value;
1508  stringp = ast_strdupa(options);
1509  while ((s = strsep(&stringp, "|"))) {
1510  value = s;
1511  if ((var = strsep(&value, "=")) && value) {
1512  apply_option(vmu, var, value);
1513  }
1514  }
1515 }
1516 
1517 /*!
1518  * \brief Loads the options specific to a voicemail user.
1519  *
1520  * This is called when a vm_user structure is being set up, such as from load_options.
1521  */
1523 {
1524  for (; var; var = var->next) {
1525  if (!strcasecmp(var->name, "vmsecret")) {
1526  ast_copy_string(retval->password, var->value, sizeof(retval->password));
1527  } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1528  if (ast_strlen_zero(retval->password)) {
1529  if (!ast_strlen_zero(var->value) && var->value[0] == '*') {
1530  ast_log(LOG_WARNING, "Invalid password detected for mailbox %s. The password"
1531  "\n\tmust be reset in voicemail.conf.\n", retval->mailbox);
1532  } else {
1533  ast_copy_string(retval->password, var->value, sizeof(retval->password));
1534  }
1535  }
1536  } else if (!strcasecmp(var->name, "uniqueid")) {
1537  ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1538  } else if (!strcasecmp(var->name, "pager")) {
1539  ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1540  } else if (!strcasecmp(var->name, "email")) {
1541  ast_free(retval->email);
1542  retval->email = ast_strdup(var->value);
1543  } else if (!strcasecmp(var->name, "fullname")) {
1544  ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1545  } else if (!strcasecmp(var->name, "context")) {
1546  ast_copy_string(retval->context, var->value, sizeof(retval->context));
1547  } else if (!strcasecmp(var->name, "emailsubject")) {
1548  ast_free(retval->emailsubject);
1549  retval->emailsubject = ast_strdup(substitute_escapes(var->value));
1550  } else if (!strcasecmp(var->name, "emailbody")) {
1551  ast_free(retval->emailbody);
1552  retval->emailbody = ast_strdup(substitute_escapes(var->value));
1553 #ifdef IMAP_STORAGE
1554  } else if (!strcasecmp(var->name, "imapuser")) {
1555  ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1556  retval->imapversion = imapversion;
1557  } else if (!strcasecmp(var->name, "imapserver")) {
1558  ast_copy_string(retval->imapserver, var->value, sizeof(retval->imapserver));
1559  retval->imapversion = imapversion;
1560  } else if (!strcasecmp(var->name, "imapport")) {
1561  ast_copy_string(retval->imapport, var->value, sizeof(retval->imapport));
1562  retval->imapversion = imapversion;
1563  } else if (!strcasecmp(var->name, "imapflags")) {
1564  ast_copy_string(retval->imapflags, var->value, sizeof(retval->imapflags));
1565  retval->imapversion = imapversion;
1566  } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1567  ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1568  retval->imapversion = imapversion;
1569  } else if (!strcasecmp(var->name, "imapfolder")) {
1570  ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1571  retval->imapversion = imapversion;
1572  } else if (!strcasecmp(var->name, "imapvmshareid")) {
1573  ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1574  retval->imapversion = imapversion;
1575 #endif
1576  } else
1577  apply_option(retval, var->name, var->value);
1578  }
1579 }
1580 
1581 /*!
1582  * \brief Determines if a DTMF key entered is valid.
1583  * \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.
1584  *
1585  * Tests the character entered against the set of valid DTMF characters.
1586  * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1587  */
1588 static int is_valid_dtmf(const char *key)
1589 {
1590  int i;
1591  char *local_key = ast_strdupa(key);
1592 
1593  for (i = 0; i < strlen(key); ++i) {
1594  if (!strchr(VALID_DTMF, *local_key)) {
1595  ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1596  return 0;
1597  }
1598  local_key++;
1599  }
1600  return 1;
1601 }
1602 
1603 /*!
1604  * \brief Finds a voicemail user from the realtime engine.
1605  * \param ivm
1606  * \param context
1607  * \param mailbox
1608  *
1609  * 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.
1610  *
1611  * \return The ast_vm_user structure for the user that was found.
1612  */
1613 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1614 {
1615  struct ast_variable *var;
1616  struct ast_vm_user *retval;
1617 
1618  if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1619  if (ivm) {
1620  memset(retval, 0, sizeof(*retval));
1621  }
1623  if (!ivm) {
1625  }
1626  if (mailbox) {
1627  ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1628  }
1629  if (!context && ast_test_flag((&globalflags), VM_SEARCH)) {
1630  var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1631  } else {
1632  var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1633  }
1634  if (var) {
1637  } else {
1638  if (!ivm)
1639  ast_free(retval);
1640  retval = NULL;
1641  }
1642  }
1643  return retval;
1644 }
1645 
1646 /*!
1647  * \brief Finds a voicemail user from the users file or the realtime engine.
1648  * \param ivm
1649  * \param context
1650  * \param mailbox
1651  *
1652  * \return The ast_vm_user structure for the user that was found.
1653  */
1654 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1655 {
1656  /* This function could be made to generate one from a database, too */
1657  struct ast_vm_user *vmu = NULL, *cur;
1658  AST_LIST_LOCK(&users);
1659 
1661  context = "default";
1662 
1663  AST_LIST_TRAVERSE(&users, cur, list) {
1664 #ifdef IMAP_STORAGE
1665  if (cur->imapversion != imapversion) {
1666  continue;
1667  }
1668 #endif
1669  if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1670  break;
1671  if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1672  break;
1673  }
1674  if (cur) {
1675  /* Make a copy, so that on a reload, we have no race */
1676  if ((vmu = (ivm ? ivm : ast_calloc(1, sizeof(*vmu))))) {
1677  ast_free(vmu->email);
1678  ast_free(vmu->emailbody);
1679  ast_free(vmu->emailsubject);
1680  *vmu = *cur;
1681  vmu->email = ast_strdup(cur->email);
1682  vmu->emailbody = ast_strdup(cur->emailbody);
1683  vmu->emailsubject = ast_strdup(cur->emailsubject);
1684  ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1685  AST_LIST_NEXT(vmu, list) = NULL;
1686  }
1687  }
1689  if (!vmu) {
1690  vmu = find_user_realtime(ivm, context, mailbox);
1691  }
1692  if (!vmu && !ast_strlen_zero(aliasescontext)) {
1693  struct alias_mailbox_mapping *mapping;
1694  char *search_string = ast_alloca(MAX_VM_MAILBOX_LEN);
1695 
1696  snprintf(search_string, MAX_VM_MAILBOX_LEN, "%s%s%s",
1697  mailbox,
1698  ast_strlen_zero(context) ? "" : "@",
1699  S_OR(context, ""));
1700 
1701  mapping = ao2_find(alias_mailbox_mappings, search_string, OBJ_SEARCH_KEY);
1702  if (mapping) {
1703  char *search_mailbox = NULL;
1704  char *search_context = NULL;
1705 
1706  separate_mailbox(ast_strdupa(mapping->mailbox), &search_mailbox, &search_context);
1707  ao2_ref(mapping, -1);
1708  vmu = find_user(ivm, search_mailbox, search_context);
1709  }
1710  }
1711 
1712  return vmu;
1713 }
1714 
1715 /*!
1716  * \brief Resets a user password to a specified password.
1717  * \param context
1718  * \param mailbox
1719  * \param newpass
1720  *
1721  * This does the actual change password work, called by the vm_change_password() function.
1722  *
1723  * \return zero on success, -1 on error.
1724  */
1725 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1726 {
1727  /* This function could be made to generate one from a database, too */
1728  struct ast_vm_user *cur;
1729  int res = -1;
1730  AST_LIST_LOCK(&users);
1731  AST_LIST_TRAVERSE(&users, cur, list) {
1732  if ((!context || !strcasecmp(context, cur->context)) &&
1733  (!strcasecmp(mailbox, cur->mailbox)))
1734  break;
1735  }
1736  if (cur) {
1737  ast_copy_string(cur->password, newpass, sizeof(cur->password));
1738  res = 0;
1739  }
1741  return res;
1742 }
1743 
1744 /*!
1745  * \brief Check if configuration file is valid
1746  */
1747 static inline int valid_config(const struct ast_config *cfg)
1748 {
1749  return cfg && cfg != CONFIG_STATUS_FILEINVALID;
1750 }
1751 
1752 /*!
1753  * \brief The handler for the change password option.
1754  * \param vmu The voicemail user to work with.
1755  * \param newpassword The new password (that has been gathered from the appropriate prompting).
1756  * 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.
1757  * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1758  */
1759 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1760 {
1761  struct ast_config *cfg = NULL;
1762  struct ast_variable *var = NULL;
1763  struct ast_category *cat = NULL;
1764  char *category = NULL;
1765  const char *tmp = NULL;
1766  struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1767  char secretfn[PATH_MAX] = "";
1768  int found = 0;
1769 
1770  if (!change_password_realtime(vmu, newpassword))
1771  return;
1772 
1773  /* check if we should store the secret in the spool directory next to the messages */
1774  switch (vmu->passwordlocation) {
1775  case OPT_PWLOC_SPOOLDIR:
1776  snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1777  if (write_password_to_file(secretfn, newpassword) == 0) {
1778  ast_test_suite_event_notify("PASSWORDCHANGED", "Message: secret.conf updated with new password\r\nPasswordSource: secret.conf");
1779  ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1780  reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1781  ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1782  break;
1783  } else {
1784  ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1785  }
1786  /* Fall-through */
1788  if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && valid_config(cfg)) {
1789  while ((category = ast_category_browse(cfg, category))) {
1790  if (!strcasecmp(category, vmu->context)) {
1791  char *value = NULL;
1792  char *new = NULL;
1793  if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1794  ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1795  break;
1796  }
1797  value = strstr(tmp, ",");
1798  if (!value) {
1799  new = ast_malloc(strlen(newpassword) + 1);
1800  sprintf(new, "%s", newpassword);
1801  } else {
1802  new = ast_malloc((strlen(value) + strlen(newpassword) + 1));
1803  sprintf(new, "%s%s", newpassword, value);
1804  }
1805  if (!(cat = ast_category_get(cfg, category, NULL))) {
1806  ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1807  ast_free(new);
1808  break;
1809  }
1810  ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1811  found = 1;
1812  ast_free(new);
1813  }
1814  }
1815  /* save the results */
1816  if (found) {
1817  ast_test_suite_event_notify("PASSWORDCHANGED", "Message: voicemail.conf updated with new password\r\nPasswordSource: voicemail.conf");
1818  reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1819  ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1820  ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "app_voicemail");
1821  ast_config_destroy(cfg);
1822  break;
1823  }
1824 
1825  ast_config_destroy(cfg);
1826  }
1827  /* Fall-through */
1828  case OPT_PWLOC_USERSCONF:
1829  /* check users.conf and update the password stored for the mailbox */
1830  /* if no vmsecret entry exists create one. */
1831  if ((cfg = ast_config_load("users.conf", config_flags)) && valid_config(cfg)) {
1832  ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1833  for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1834  ast_debug(4, "users.conf: %s\n", category);
1835  if (!strcasecmp(category, vmu->mailbox)) {
1836  char new[strlen(newpassword) + 1];
1837  if (!ast_variable_retrieve(cfg, category, "vmsecret")) {
1838  ast_debug(3, "looks like we need to make vmsecret!\n");
1839  var = ast_variable_new("vmsecret", newpassword, "");
1840  } else {
1841  var = NULL;
1842  }
1843 
1844  sprintf(new, "%s", newpassword);
1845  if (!(cat = ast_category_get(cfg, category, NULL))) {
1846  ast_debug(4, "failed to get category!\n");
1847  ast_free(var);
1848  break;
1849  }
1850  if (!var) {
1851  ast_variable_update(cat, "vmsecret", new, NULL, 0);
1852  } else {
1853  ast_variable_append(cat, var);
1854  }
1855  found = 1;
1856  break;
1857  }
1858  }
1859  /* save the results and clean things up */
1860  if (found) {
1861  ast_test_suite_event_notify("PASSWORDCHANGED", "Message: users.conf updated with new password\r\nPasswordSource: users.conf");
1862  reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1863  ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1864  ast_config_text_file_save("users.conf", cfg, "app_voicemail");
1865  }
1866 
1867  ast_config_destroy(cfg);
1868  }
1869  }
1870 }
1871 
1872 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1873 {
1874  char buf[255];
1875  snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1876  ast_debug(1, "External password: %s\n",buf);
1877  if (!ast_safe_system(buf)) {
1878  ast_test_suite_event_notify("PASSWORDCHANGED", "Message: external script updated with new password\r\nPasswordSource: external");
1879  ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1880  /* Reset the password in memory, too */
1881  reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1882  }
1883 }
1884 
1885 /*!
1886  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1887  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1888  * \param len The length of the path string that was written out.
1889  * \param context
1890  * \param ext
1891  * \param folder
1892  *
1893  * The path is constructed as
1894  * VM_SPOOL_DIRcontext/ext/folder
1895  *
1896  * \return zero on success, -1 on error.
1897  */
1898 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1899 {
1900  return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1901 }
1902 
1903 /*!
1904  * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1905  * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1906  * \param len The length of the path string that was written out.
1907  * \param dir
1908  * \param num
1909  *
1910  * The path is constructed as
1911  * VM_SPOOL_DIRcontext/ext/folder
1912  *
1913  * \return zero on success, -1 on error.
1914  */
1915 static int make_file(char *dest, const int len, const char *dir, const int num)
1916 {
1917  return snprintf(dest, len, "%s/msg%04d", dir, num);
1918 }
1919 
1920 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1921  * \param dest String. base directory.
1922  * \param len Length of dest.
1923  * \param context String. Ignored if is null or empty string.
1924  * \param ext String. Ignored if is null or empty string.
1925  * \param folder String. Ignored if is null or empty string.
1926  * \return -1 on failure, 0 on success.
1927  */
1928 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1929 {
1930  mode_t mode = VOICEMAIL_DIR_MODE;
1931  int res;
1932 
1933  make_dir(dest, len, context, ext, folder);
1934  if ((res = ast_mkdir(dest, mode))) {
1935  ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1936  return -1;
1937  }
1938  return 0;
1939 }
1940 
1941 static const char *mbox(struct ast_vm_user *vmu, int id)
1942 {
1943 #ifdef IMAP_STORAGE
1944  if (vmu && id == 0) {
1945  return vmu->imapfolder;
1946  }
1947 #endif
1948  return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1949 }
1950 
1951 static const char *vm_index_to_foldername(int id)
1952 {
1953  return mbox(NULL, id);
1954 }
1955 
1956 
1957 static int get_folder_by_name(const char *name)
1958 {
1959  size_t i;
1960 
1961  for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1962  if (strcasecmp(name, mailbox_folders[i]) == 0) {
1963  return i;
1964  }
1965  }
1966 
1967  return -1;
1968 }
1969 
1970 static void free_user(struct ast_vm_user *vmu)
1971 {
1972  if (!vmu) {
1973  return;
1974  }
1975 
1976  ast_free(vmu->email);
1977  vmu->email = NULL;
1978  ast_free(vmu->emailbody);
1979  vmu->emailbody = NULL;
1980  ast_free(vmu->emailsubject);
1981  vmu->emailsubject = NULL;
1982 
1983  if (ast_test_flag(vmu, VM_ALLOCED)) {
1984  ast_free(vmu);
1985  }
1986 }
1987 
1988 static void free_user_final(struct ast_vm_user *vmu)
1989 {
1990  if (!vmu) {
1991  return;
1992  }
1993 
1994  if (!ast_strlen_zero(vmu->mailbox)) {
1996  }
1997 
1998  free_user(vmu);
1999 }
2000 
2001 static int vm_allocate_dh(struct vm_state *vms, struct ast_vm_user *vmu, int count_msg) {
2002 
2003  int arraysize = (vmu->maxmsg > count_msg ? vmu->maxmsg : count_msg);
2004 
2005  /* remove old allocation */
2006  if (vms->deleted) {
2007  ast_free(vms->deleted);
2008  vms->deleted = NULL;
2009  }
2010  if (vms->heard) {
2011  ast_free(vms->heard);
2012  vms->heard = NULL;
2013  }
2014  vms->dh_arraysize = 0;
2015 
2016  if (arraysize > 0) {
2017  if (!(vms->deleted = ast_calloc(arraysize, sizeof(int)))) {
2018  return -1;
2019  }
2020  if (!(vms->heard = ast_calloc(arraysize, sizeof(int)))) {
2021  ast_free(vms->deleted);
2022  vms->deleted = NULL;
2023  return -1;
2024  }
2025  vms->dh_arraysize = arraysize;
2026  }
2027 
2028  return 0;
2029 }
2030 
2031 /* All IMAP-specific functions should go in this block. This
2032  * keeps them from being spread out all over the code */
2033 #ifdef IMAP_STORAGE
2034 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
2035 {
2036  char arg[10];
2037  struct vm_state *vms;
2038  unsigned long messageNum;
2039 
2040  /* If greetings aren't stored in IMAP, just delete the file */
2041  if (msgnum < 0 && !imapgreetings) {
2043  return;
2044  }
2045 
2046  if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2047  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);
2048  return;
2049  }
2050 
2051  if (msgnum < 0) {
2052  imap_delete_old_greeting(file, vms);
2053  return;
2054  }
2055 
2056  /* find real message number based on msgnum */
2057  /* this may be an index into vms->msgArray based on the msgnum. */
2058  messageNum = vms->msgArray[msgnum];
2059  if (messageNum == 0) {
2060  ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
2061  return;
2062  }
2063  ast_debug(3, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
2064  /* delete message */
2065  snprintf (arg, sizeof(arg), "%lu", messageNum);
2066  ast_mutex_lock(&vms->lock);
2067  mail_setflag (vms->mailstream, arg, "\\DELETED");
2068  mail_expunge(vms->mailstream);
2069  ast_mutex_unlock(&vms->lock);
2070 }
2071 
2072 static void vm_imap_update_msg_id(char *dir, int msgnum, const char *msg_id, struct ast_vm_user *vmu, struct ast_config *msg_cfg, int folder)
2073 {
2074  struct ast_channel *chan;
2075  char *cid;
2076  char *cid_name;
2077  char *cid_num;
2078  struct vm_state *vms;
2079  const char *duration_str;
2080  int duration = 0;
2081 
2082  /*
2083  * First, get things initially set up. If any of this fails, then
2084  * back out before doing anything substantial
2085  */
2086  vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0);
2087  if (!vms) {
2088  return;
2089  }
2090 
2091  if (open_mailbox(vms, vmu, folder)) {
2092  return;
2093  }
2094 
2095  chan = ast_dummy_channel_alloc();
2096  if (!chan) {
2097  close_mailbox(vms, vmu);
2098  return;
2099  }
2100 
2101  /*
2102  * We need to make sure the new message we save has the same
2103  * callerid, flag, and duration as the original message
2104  */
2105  cid = ast_strdupa(ast_variable_retrieve(msg_cfg, "message", "callerid"));
2106 
2107  if (!ast_strlen_zero(cid)) {
2110  if (!ast_strlen_zero(cid_name)) {
2111  ast_channel_caller(chan)->id.name.valid = 1;
2113  }
2114  if (!ast_strlen_zero(cid_num)) {
2115  ast_channel_caller(chan)->id.number.valid = 1;
2117  }
2118  }
2119 
2120  duration_str = ast_variable_retrieve(msg_cfg, "message", "duration");
2121 
2122  if (!ast_strlen_zero(duration_str)) {
2123  sscanf(duration_str, "%30d", &duration);
2124  }
2125 
2126  /*
2127  * IMAP messages cannot be altered once delivered. So we have to delete the
2128  * current message and then re-add it with the updated message ID.
2129  *
2130  * Furthermore, there currently is no atomic way to create a new message and to
2131  * store it in an arbitrary folder. So we have to save it to the INBOX and then
2132  * move to the appropriate folder.
2133  */
2134  if (!imap_store_file(dir, vmu->mailbox, vmu->context, msgnum, chan, vmu, vmfmts,
2135  duration, vms, ast_variable_retrieve(msg_cfg, "message", "flag"), msg_id)) {
2136  if (folder != NEW_FOLDER) {
2137  save_to_folder(vmu, vms, msgnum, folder, NULL, 1);
2138  }
2139  vm_imap_delete(dir, msgnum, vmu);
2140  }
2141  close_mailbox(vms, vmu);
2142  ast_channel_unref(chan);
2143 }
2144 
2145 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
2146 {
2147  struct vm_state *vms_p;
2148  char *file, *filename;
2149  char dest[PATH_MAX];
2150  int i;
2151  BODY *body;
2152  int ret = 0;
2153  int curr_mbox;
2154 
2155  /* This function is only used for retrieval of IMAP greetings
2156  * regular messages are not retrieved this way, nor are greetings
2157  * if they are stored locally*/
2158  if (msgnum > -1 || !imapgreetings) {
2159  return 0;
2160  } else {
2161  file = strrchr(ast_strdupa(dir), '/');
2162  if (file)
2163  *file++ = '\0';
2164  else {
2165  ast_debug(1, "Failed to procure file name from directory passed.\n");
2166  return -1;
2167  }
2168  }
2169 
2170  /* check if someone is accessing this box right now... */
2171  if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
2172  !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2173  /* Unlike when retrieving a message, it is reasonable not to be able to find a
2174  * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
2175  * that's all we need to do.
2176  */
2177  if (!(vms_p = create_vm_state_from_user(vmu))) {
2178  ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
2179  return -1;
2180  }
2181  }
2182 
2183  /* Greetings will never have a prepended message */
2184  *vms_p->introfn = '\0';
2185 
2186  ast_mutex_lock(&vms_p->lock);
2187 
2188  /* get the current mailbox so that we can point the mailstream back to it later */
2189  curr_mbox = get_folder_by_name(vms_p->curbox);
2190 
2191  if (init_mailstream(vms_p, GREETINGS_FOLDER) || !vms_p->mailstream) {
2192  ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL or can't init_mailstream\n");
2193  ast_mutex_unlock(&vms_p->lock);
2194  return -1;
2195  }
2196 
2197  /*XXX Yuck, this could probably be done a lot better */
2198  for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
2199  mail_fetchstructure(vms_p->mailstream, i + 1, &body);
2200  /* We have the body, now we extract the file name of the first attachment. */
2201  if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
2202  char *attachment = body->nested.part->next->body.parameter->value;
2203  char copy[strlen(attachment) + 1];
2204 
2205  strcpy(copy, attachment); /* safe */
2206  attachment = copy;
2207 
2208  filename = strsep(&attachment, ".");
2209  if (!strcmp(filename, file)) {
2210  ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
2211  vms_p->msgArray[vms_p->curmsg] = i + 1;
2212  create_dirpath(dest, sizeof(dest), vmu->context, vms_p->username, "");
2213  save_body(body, vms_p, "2", attachment, 0);
2214  ret = 0;
2215  break;
2216  }
2217  } else {
2218  ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
2219  ret = -1;
2220  break;
2221  }
2222  }
2223 
2224  if (curr_mbox != -1) {
2225  /* restore previous mbox stream */
2226  if (init_mailstream(vms_p, curr_mbox) || !vms_p->mailstream) {
2227  ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL or can't init_mailstream\n");
2228  ret = -1;
2229  }
2230  }
2231  ast_mutex_unlock(&vms_p->lock);
2232  return ret;
2233 }
2234 
2235 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
2236 {
2237  BODY *body;
2238  char *header_content;
2239  char *attachedfilefmt;
2240  char buf[80];
2241  struct vm_state *vms;
2242  char text_file[PATH_MAX];
2243  FILE *text_file_ptr;
2244  int res = 0;
2245  struct ast_vm_user *vmu;
2246  int curr_mbox;
2247 
2248  if (!(vmu = find_user(NULL, context, mailbox))) {
2249  ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
2250  return -1;
2251  }
2252 
2253  if (msgnum < 0) {
2254  if (imapgreetings) {
2255  res = imap_retrieve_greeting(dir, msgnum, vmu);
2256  goto exit;
2257  } else {
2258  res = 0;
2259  goto exit;
2260  }
2261  }
2262 
2263  /* Before anything can happen, we need a vm_state so that we can
2264  * actually access the imap server through the vms->mailstream
2265  */
2266  if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
2267  /* This should not happen. If it does, then I guess we'd
2268  * need to create the vm_state, extract which mailbox to
2269  * open, and then set up the msgArray so that the correct
2270  * IMAP message could be accessed. If I have seen correctly
2271  * though, the vms should be obtainable from the vmstates list
2272  * and should have its msgArray properly set up.
2273  */
2274  ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
2275  res = -1;
2276  goto exit;
2277  }
2278 
2279  /* Ensure we have the correct mailbox open and have a valid mailstream for it */
2280  curr_mbox = get_folder_by_name(vms->curbox);
2281  if (curr_mbox < 0) {
2282  ast_debug(3, "Mailbox folder curbox not set, defaulting to Inbox\n");
2283  curr_mbox = 0;
2284  }
2285  init_mailstream(vms, curr_mbox);
2286  if (!vms->mailstream) {
2287  ast_log(AST_LOG_ERROR, "IMAP mailstream for %s is NULL\n", vmu->mailbox);
2288  res = -1;
2289  goto exit;
2290  }
2291 
2292  make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
2293  snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
2294 
2295  /* Don't try to retrieve a message from IMAP if it already is on the file system */
2296  if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
2297  res = 0;
2298  goto exit;
2299  }
2300 
2301  ast_debug(3, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
2302  if (vms->msgArray[msgnum] == 0) {
2303  ast_log(LOG_WARNING, "Trying to access unknown message\n");
2304  res = -1;
2305  goto exit;
2306  }
2307 
2308  /* This will only work for new messages... */
2309  ast_mutex_lock(&vms->lock);
2310  header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
2311  ast_mutex_unlock(&vms->lock);
2312  /* empty string means no valid header */
2313  if (ast_strlen_zero(header_content)) {
2314  ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
2315  res = -1;
2316  goto exit;
2317  }
2318 
2319  ast_mutex_lock(&vms->lock);
2320  mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
2321  ast_mutex_unlock(&vms->lock);
2322 
2323  /* We have the body, now we extract the file name of the first attachment. */
2324  if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
2325  attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
2326  } else {
2327  ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
2328  res = -1;
2329  goto exit;
2330  }
2331 
2332  /* Find the format of the attached file */
2333 
2334  strsep(&attachedfilefmt, ".");
2335  if (!attachedfilefmt) {
2336  ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
2337  res = -1;
2338  goto exit;
2339  }
2340 
2341  save_body(body, vms, "2", attachedfilefmt, 0);
2342  if (save_body(body, vms, "3", attachedfilefmt, 1)) {
2343  *vms->introfn = '\0';
2344  }
2345 
2346  /* Get info from headers!! */
2347  snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
2348 
2349  if (!(text_file_ptr = fopen(text_file, "w"))) {
2350  ast_log(LOG_ERROR, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
2351  goto exit;
2352  }
2353 
2354  fprintf(text_file_ptr, "%s\n", "[message]");
2355 
2356  if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf))) {
2357  fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
2358  }
2359  if (get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf))) {
2360  fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
2361  }
2362  if (get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf))) {
2363  fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
2364  }
2365  if (get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf))) {
2366  fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
2367  }
2368  if (get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf))) {
2369  fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
2370  }
2371  if (get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf))) {
2372  fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
2373  }
2374  if (get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf))) {
2375  fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
2376  }
2377  if (get_header_by_tag(header_content, "X-Asterisk-VM-Message-ID:", buf, sizeof(buf))) {
2378  fprintf(text_file_ptr, "msg_id=%s\n", S_OR(buf, ""));
2379  }
2380  fclose(text_file_ptr);
2381 
2382 exit:
2383  free_user(vmu);
2384  return res;
2385 }
2386 
2387 static int folder_int(const char *folder)
2388 {
2389  /*assume a NULL folder means INBOX*/
2390  if (!folder) {
2391  return 0;
2392  }
2393  if (!strcasecmp(folder, imapfolder)) {
2394  return 0;
2395  } else if (!strcasecmp(folder, "Old")) {
2396  return 1;
2397  } else if (!strcasecmp(folder, "Work")) {
2398  return 2;
2399  } else if (!strcasecmp(folder, "Family")) {
2400  return 3;
2401  } else if (!strcasecmp(folder, "Friends")) {
2402  return 4;
2403  } else if (!strcasecmp(folder, "Cust1")) {
2404  return 5;
2405  } else if (!strcasecmp(folder, "Cust2")) {
2406  return 6;
2407  } else if (!strcasecmp(folder, "Cust3")) {
2408  return 7;
2409  } else if (!strcasecmp(folder, "Cust4")) {
2410  return 8;
2411  } else if (!strcasecmp(folder, "Cust5")) {
2412  return 9;
2413  } else if (!strcasecmp(folder, "Urgent")) {
2414  return 11;
2415  } else { /*assume they meant INBOX if folder is not found otherwise*/
2416  return 0;
2417  }
2418 }
2419 
2420 static int __messagecount(const char *context, const char *mailbox, const char *folder)
2421 {
2422  SEARCHPGM *pgm;
2423  SEARCHHEADER *hdr;
2424 
2425  struct ast_vm_user *vmu, vmus;
2426  struct vm_state *vms_p;
2427  int ret = 0;
2428  int fold = folder_int(folder);
2429  int urgent = 0;
2430 
2431  /* If URGENT, then look at INBOX */
2432  if (fold == 11) {
2433  fold = NEW_FOLDER;
2434  urgent = 1;
2435  }
2436 
2437  if (ast_strlen_zero(mailbox))
2438  return 0;
2439 
2440  /* We have to get the user before we can open the stream! */
2441  memset(&vmus, 0, sizeof(vmus));
2442  vmu = find_user(&vmus, context, mailbox);
2443  if (!vmu) {
2444  ast_log(AST_LOG_WARNING, "Couldn't find mailbox %s in context %s\n", mailbox, context);
2445  free_user(vmu);
2446  return -1;
2447  } else {
2448  /* No IMAP account available */
2449  if (vmu->imapuser[0] == '\0') {
2450  ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2451  free_user(vmu);
2452  return -1;
2453  }
2454  }
2455 
2456  /* No IMAP account available */
2457  if (vmu->imapuser[0] == '\0') {
2458  ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2459  free_user(vmu);
2460  return -1;
2461  }
2462 
2463  /* check if someone is accessing this box right now... */
2464  vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2465  if (!vms_p) {
2466  vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
2467  }
2468  if (vms_p) {
2469  ast_debug(3, "Returning before search - user is logged in\n");
2470  if (fold == 0) { /* INBOX */
2471  free_user(vmu);
2472  return urgent ? vms_p->urgentmessages : vms_p->newmessages;
2473  }
2474  if (fold == 1) { /* Old messages */
2475  free_user(vmu);
2476  return vms_p->oldmessages;
2477  }
2478  }
2479 
2480  /* add one if not there... */
2481  vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2482  if (!vms_p) {
2483  vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2484  }
2485 
2486  if (!vms_p) {
2487  vms_p = create_vm_state_from_user(vmu);
2488  }
2489  ret = init_mailstream(vms_p, fold);
2490  if (!vms_p->mailstream) {
2491  ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2492  free_user(vmu);
2493  return -1;
2494  }
2495  if (ret == 0) {
2496  ast_mutex_lock(&vms_p->lock);
2497  pgm = mail_newsearchpgm ();
2498  hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2499  hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2500  pgm->header = hdr;
2501  if (fold != OLD_FOLDER) {
2502  pgm->unseen = 1;
2503  pgm->seen = 0;
2504  }
2505  /* In the special case where fold is 1 (old messages) we have to do things a bit
2506  * differently. Old messages are stored in the INBOX but are marked as "seen"
2507  */
2508  else {
2509  pgm->unseen = 0;
2510  pgm->seen = 1;
2511  }
2512  /* look for urgent messages */
2513  if (fold == NEW_FOLDER) {
2514  if (urgent) {
2515  pgm->flagged = 1;
2516  pgm->unflagged = 0;
2517  } else {
2518  pgm->flagged = 0;
2519  pgm->unflagged = 1;
2520  }
2521  }
2522  pgm->undeleted = 1;
2523  pgm->deleted = 0;
2524 
2525  vms_p->vmArrayIndex = 0;
2526  mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2527  if (fold == 0 && urgent == 0)
2528  vms_p->newmessages = vms_p->vmArrayIndex;
2529  if (fold == 1)
2530  vms_p->oldmessages = vms_p->vmArrayIndex;
2531  if (fold == 0 && urgent == 1)
2532  vms_p->urgentmessages = vms_p->vmArrayIndex;
2533  /*Freeing the searchpgm also frees the searchhdr*/
2534  mail_free_searchpgm(&pgm);
2535  ast_mutex_unlock(&vms_p->lock);
2536  free_user(vmu);
2537  vms_p->updated = 0;
2538  return vms_p->vmArrayIndex;
2539  } else {
2540  ast_mutex_lock(&vms_p->lock);
2541  mail_ping(vms_p->mailstream);
2542  ast_mutex_unlock(&vms_p->lock);
2543  }
2544  free_user(vmu);
2545  return 0;
2546 }
2547 
2548 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2549 {
2550  /* Check if mailbox is full */
2551  check_quota(vms, vmu->imapfolder);
2552  if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2553  ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2554  if (chan) {
2555  ast_play_and_wait(chan, "vm-mailboxfull");
2556  }
2557  return -1;
2558  }
2559 
2560  /* Check if we have exceeded maxmsg */
2561  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));
2562  if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
2563  ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
2564  if (chan) {
2565  ast_play_and_wait(chan, "vm-mailboxfull");
2566  pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2567  }
2568  return -1;
2569  }
2570 
2571  return 0;
2572 }
2573 
2574 /*!
2575  * \brief Gets the number of messages that exist in a mailbox folder.
2576  * \param mailbox_id
2577  * \param folder
2578  *
2579  * This method is used when IMAP backend is used.
2580  * \return The number of messages in this mailbox folder (zero or more).
2581  */
2582 static int messagecount(const char *mailbox_id, const char *folder)
2583 {
2584  char *context;
2585  char *mailbox;
2586 
2587  if (ast_strlen_zero(mailbox_id)
2588  || separate_mailbox(ast_strdupa(mailbox_id), &mailbox, &context)) {
2589  return 0;
2590  }
2591 
2592  if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2593  return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2594  } else {
2595  return __messagecount(context, mailbox, folder);
2596  }
2597 }
2598 
2599 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, const char *msg_id)
2600 {
2601  char *myserveremail = serveremail;
2602  char fn[PATH_MAX];
2603  char introfn[PATH_MAX];
2604  char mailbox[256];
2605  char *stringp;
2606  FILE *p = NULL;
2607  char tmp[80] = "/tmp/astmail-XXXXXX";
2608  long len;
2609  void *buf;
2610  int tempcopy = 0;
2611  STRING str;
2612  int ret; /* for better error checking */
2613  char *imap_flags = NIL;
2614  int msgcount;
2615  int box = NEW_FOLDER;
2616 
2617  snprintf(mailbox, sizeof(mailbox), "%s@%s", vmu->mailbox, vmu->context);
2618  msgcount = messagecount(mailbox, "INBOX") + messagecount(mailbox, "Old");
2619 
2620  /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2621  if (msgnum < 0) {
2622  if(!imapgreetings) {
2623  return 0;
2624  } else {
2625  box = GREETINGS_FOLDER;
2626  }
2627  }
2628 
2629  if (imap_check_limits(chan, vms, vmu, msgcount)) {
2630  return -1;
2631  }
2632 
2633  /* Set urgent flag for IMAP message */
2634  if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2635  ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2636  imap_flags = "\\FLAGGED";
2637  }
2638 
2639  /* Attach only the first format */
2640  fmt = ast_strdupa(fmt);
2641  stringp = fmt;
2642  strsep(&stringp, "|");
2643 
2644  if (!ast_strlen_zero(vmu->serveremail))
2645  myserveremail = vmu->serveremail;
2646 
2647  if (msgnum > -1)
2648  make_file(fn, sizeof(fn), dir, msgnum);
2649  else
2650  ast_copy_string (fn, dir, sizeof(fn));
2651 
2652  snprintf(introfn, sizeof(introfn), "%sintro", fn);
2653  if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2654  *introfn = '\0';
2655  }
2656 
2657  if (ast_strlen_zero(vmu->email)) {
2658  /* We need the vmu->email to be set when we call make_email_file, but
2659  * if we keep it set, a duplicate e-mail will be created. So at the end
2660  * of this function, we will revert back to an empty string if tempcopy
2661  * is 1.
2662  */
2663  vmu->email = ast_strdup(vmu->imapuser);
2664  tempcopy = 1;
2665  }
2666 
2667  if (!strcmp(fmt, "wav49"))
2668  fmt = "WAV";
2669  ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2670 
2671  /* Make a temporary file instead of piping directly to sendmail, in case the mail
2672  command hangs. */
2674  ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2675  if (tempcopy) {
2676  ast_free(vmu->email);
2677  vmu->email = NULL;
2678  }
2679  return -1;
2680  }
2681 
2682  if (msgnum < 0 && imapgreetings) {
2683  if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2684  ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2685  return -1;
2686  }
2687  imap_delete_old_greeting(fn, vms);
2688  }
2689 
2690  make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
2691  chan ? S_COR(ast_channel_caller(chan)->id.number.valid, ast_channel_caller(chan)->id.number.str, NULL) : NULL,
2692  chan ? S_COR(ast_channel_caller(chan)->id.name.valid, ast_channel_caller(chan)->id.name.str, NULL) : NULL,
2693  fn, introfn, fmt, duration, 1, chan, NULL, 1, flag, msg_id);
2694  /* read mail file to memory */
2695  len = ftell(p);
2696  rewind(p);
2697  if (!(buf = ast_malloc(len + 1))) {
2698  ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2699  fclose(p);
2700  if (tempcopy)
2701  *(vmu->email) = '\0';
2702  return -1;
2703  }
2704  if (fread(buf, 1, len, p) != len) {
2705  if (ferror(p)) {
2706  ast_log(LOG_ERROR, "Error while reading mail file: %s\n", tmp);
2707  return -1;
2708  }
2709  }
2710  ((char *) buf)[len] = '\0';
2711  INIT(&str, mail_string, buf, len);
2712  ret = init_mailstream(vms, box);
2713  if (ret == 0) {
2714  imap_mailbox_name(mailbox, sizeof(mailbox), vms, box, 1);
2715  ast_mutex_lock(&vms->lock);
2716  if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2717  ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2718  ast_mutex_unlock(&vms->lock);
2719  fclose(p);
2720  unlink(tmp);
2721  ast_free(buf);
2722  } else {
2723  ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2724  fclose(p);
2725  unlink(tmp);
2726  ast_free(buf);
2727  return -1;
2728  }
2729  ast_debug(3, "%s stored\n", fn);
2730 
2731  if (tempcopy)
2732  *(vmu->email) = '\0';
2733  inprocess_count(vmu->mailbox, vmu->context, -1);
2734  return 0;
2735 
2736 }
2737 
2738 /*!
2739  * \brief Gets the number of messages that exist in the inbox folder.
2740  * \param mailbox_context
2741  * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2742  * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2743  * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2744  *
2745  * This method is used when IMAP backend is used.
2746  * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2747  *
2748  * \return zero on success, -1 on error.
2749  */
2750 
2751 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2752 {
2753  char tmp[PATH_MAX] = "";
2754  char *mailboxnc;
2755  char *context;
2756  char *mb;
2757  char *cur;
2758  if (newmsgs)
2759  *newmsgs = 0;
2760  if (oldmsgs)
2761  *oldmsgs = 0;
2762  if (urgentmsgs)
2763  *urgentmsgs = 0;
2764 
2765  ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2766  /* If no mailbox, return immediately */
2767  if (ast_strlen_zero(mailbox_context))
2768  return 0;
2769 
2770  ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2771  context = strchr(tmp, '@');
2772  if (strchr(mailbox_context, ',')) {
2773  int tmpnew, tmpold, tmpurgent;
2774  ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2775  mb = tmp;
2776  while ((cur = strsep(&mb, ", "))) {
2777  if (!ast_strlen_zero(cur)) {
2778  if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2779  return -1;
2780  else {
2781  if (newmsgs)
2782  *newmsgs += tmpnew;
2783  if (oldmsgs)
2784  *oldmsgs += tmpold;
2785  if (urgentmsgs)
2786  *urgentmsgs += tmpurgent;
2787  }
2788  }
2789  }
2790  return 0;
2791  }
2792  if (context) {
2793  *context = '\0';
2794  mailboxnc = tmp;
2795  context++;
2796  } else {
2797  context = "default";
2798  mailboxnc = (char *) mailbox_context;
2799  }
2800 
2801  if (newmsgs) {
2802  struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2803  if (!vmu) {
2804  ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2805  return -1;
2806  }
2807  if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2808  free_user(vmu);
2809  return -1;
2810  }
2811  free_user(vmu);
2812  }
2813  if (oldmsgs) {
2814  if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2815  return -1;
2816  }
2817  }
2818  if (urgentmsgs) {
2819  if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2820  return -1;
2821  }
2822  }
2823  return 0;
2824 }
2825 
2826 /*!
2827  * \brief Determines if the given folder has messages.
2828  * \param mailbox The \@ delimited string for user\@context. If no context is found, uses 'default' for the context.
2829  * \param folder the folder to look in
2830  *
2831  * This function is used when the mailbox is stored in an IMAP back end.
2832  * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2833  * \return 1 if the folder has one or more messages. zero otherwise.
2834  */
2835 
2836 static int has_voicemail(const char *mailbox, const char *folder)
2837 {
2838  char tmp[256], *tmp2, *box, *context;
2839  ast_copy_string(tmp, mailbox, sizeof(tmp));
2840  tmp2 = tmp;
2841  if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2842  while ((box = strsep(&tmp2, ",&"))) {
2843  if (!ast_strlen_zero(box)) {
2844  if (has_voicemail(box, folder)) {
2845  return 1;
2846  }
2847  }
2848  }
2849  }
2850  if ((context = strchr(tmp, '@'))) {
2851  *context++ = '\0';
2852  } else {
2853  context = "default";
2854  }
2855  return __messagecount(context, tmp, folder) ? 1 : 0;
2856 }
2857 
2858 /*!
2859  * \brief Copies a message from one mailbox to another.
2860  * \param chan
2861  * \param vmu
2862  * \param imbox
2863  * \param msgnum
2864  * \param duration
2865  * \param recip
2866  * \param fmt
2867  * \param dir
2868  * \param flag, dest_folder
2869  *
2870  * This works with IMAP storage based mailboxes.
2871  *
2872  * \return zero on success, -1 on error.
2873  */
2874 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, const char *dest_folder)
2875 {
2876  struct vm_state *sendvms = NULL;
2877  char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2878  if (msgnum >= recip->maxmsg) {
2879  ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2880  return -1;
2881  }
2882  if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2883  ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2884  return -1;
2885  }
2886  if (!get_vm_state_by_imapuser(recip->imapuser, 0)) {
2887  ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2888  return -1;
2889  }
2890  snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2891  ast_mutex_lock(&sendvms->lock);
2892  if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2893  ast_mutex_unlock(&sendvms->lock);
2894  return 0;
2895  }
2896  ast_mutex_unlock(&sendvms->lock);
2897  ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2898  return -1;
2899 }
2900 
2901 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2902 {
2903  char tmp[256], *t = tmp;
2904  size_t left = sizeof(tmp);
2905 
2906  if (box == OLD_FOLDER) {
2907  ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2908  } else {
2909  ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2910  }
2911 
2912  if (box == NEW_FOLDER) {
2913  ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2914  } else {
2915  snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2916  }
2917 
2918  /* Build up server information */
2919  ast_build_string(&t, &left, "{%s:%s/imap", S_OR(vms->imapserver, imapserver), S_OR(vms->imapport, imapport));
2920 
2921  /* Add authentication user if present */
2922  if (!ast_strlen_zero(authuser))
2923  ast_build_string(&t, &left, "/authuser=%s", authuser);
2924 
2925  /* Add flags if present */
2926  if (!ast_strlen_zero(imapflags) || !(ast_strlen_zero(vms->imapflags))) {
2927  ast_build_string(&t, &left, "/%s", S_OR(vms->imapflags, imapflags));
2928  }
2929 
2930  /* End with username */
2931 #if 1
2932  ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2933 #else
2934  ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2935 #endif
2936  if (box == NEW_FOLDER || box == OLD_FOLDER)
2937  snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2938  else if (box == GREETINGS_FOLDER)
2939  snprintf(spec, len, "%s%s", tmp, greetingfolder);
2940  else { /* Other folders such as Friends, Family, etc... */
2941  if (!ast_strlen_zero(imapparentfolder)) {
2942  /* imapparentfolder would typically be set to INBOX */
2943  snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2944  } else {
2945  snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2946  }
2947  }
2948 }
2949 
2950 static int init_mailstream(struct vm_state *vms, int box)
2951 {
2952  MAILSTREAM *stream = NIL;
2953  long debug;
2954  char tmp[256];
2955 
2956  if (!vms) {
2957  ast_log(LOG_ERROR, "vm_state is NULL!\n");
2958  return -1;
2959  }
2960  ast_debug(3, "vm_state user is:%s\n", vms->imapuser);
2961  if (vms->mailstream == NIL || !vms->mailstream) {
2962  ast_debug(1, "mailstream not set.\n");
2963  } else {
2964  stream = vms->mailstream;
2965  }
2966  /* debug = T; user wants protocol telemetry? */
2967  debug = NIL; /* NO protocol telemetry? */
2968 
2969  if (delimiter == '\0') { /* did not probe the server yet */
2970  char *cp;
2971 #ifdef USE_SYSTEM_IMAP
2972 #include <imap/linkage.c>
2973 #elif defined(USE_SYSTEM_CCLIENT)
2974 #include <c-client/linkage.c>
2975 #else
2976 #include "linkage.c"
2977 #endif
2978  /* Connect to INBOX first to get folders delimiter */
2979  imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2980  ast_mutex_lock(&vms->lock);
2981  ast_mutex_lock(&mail_open_lock);
2982  stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2983  ast_mutex_unlock(&mail_open_lock);
2984  ast_mutex_unlock(&vms->lock);
2985  if (stream == NIL) {
2986  ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2987  return -1;
2988  }
2989  get_mailbox_delimiter(vms, stream);
2990  /* update delimiter in imapfolder */
2991  for (cp = vms->imapfolder; *cp; cp++)
2992  if (*cp == '/')
2993  *cp = delimiter;
2994  }
2995  /* Now connect to the target folder */
2996  imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2997  ast_debug(3, "Before mail_open, server: %s, box:%d\n", tmp, box);
2998  ast_mutex_lock(&vms->lock);
2999  ast_mutex_lock(&mail_open_lock);
3000  vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
3001  /* Create the folder if it doesn't exist */
3002  if (vms->mailstream && !mail_status(vms->mailstream, tmp, SA_UIDNEXT)) {
3003  mail_create(vms->mailstream, tmp);
3004  }
3005  ast_mutex_unlock(&mail_open_lock);
3006  ast_mutex_unlock(&vms->lock);
3007  if (vms->mailstream == NIL) {
3008  return -1;
3009  } else {
3010  return 0;
3011  }
3012 }
3013 
3014 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
3015 {
3016  SEARCHPGM *pgm;
3017  SEARCHHEADER *hdr;
3018  int urgent = 0;
3019 
3020  /* If Urgent, then look at INBOX */
3021  if (box == 11) {
3022  box = NEW_FOLDER;
3023  urgent = 1;
3024  }
3025 
3026  ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
3027  ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
3028  ast_copy_string(vms->imapserver, vmu->imapserver, sizeof(vms->imapserver));
3029  ast_copy_string(vms->imapport, vmu->imapport, sizeof(vms->imapport));
3030  ast_copy_string(vms->imapflags, vmu->imapflags, sizeof(vms->imapflags));
3031  vms->imapversion = vmu->imapversion;
3032  ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
3033 
3034  if (init_mailstream(vms, box) || !vms->mailstream) {
3035  ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
3036  return -1;
3037  }
3038 
3039  create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
3040 
3041  /* Check Quota */
3042  if (box == 0) {
3043  ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
3044  check_quota(vms, (char *) mbox(vmu, box));
3045  }
3046 
3047  ast_mutex_lock(&vms->lock);
3048  pgm = mail_newsearchpgm();
3049 
3050  /* Check IMAP folder for Asterisk messages only... */
3051  hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
3052  hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
3053  pgm->header = hdr;
3054  pgm->deleted = 0;
3055  pgm->undeleted = 1;
3056 
3057  /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
3058  if (box == NEW_FOLDER && urgent == 1) {
3059  pgm->unseen = 1;
3060  pgm->seen = 0;
3061  pgm->flagged = 1;
3062  pgm->unflagged = 0;
3063  } else if (box == NEW_FOLDER && urgent == 0) {
3064  pgm->unseen = 1;
3065  pgm->seen = 0;
3066  pgm->flagged = 0;
3067  pgm->unflagged = 1;
3068  } else if (box == OLD_FOLDER) {
3069  pgm->seen = 1;
3070  pgm->unseen = 0;
3071  }
3072 
3073  ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
3074 
3075  vms->vmArrayIndex = 0;
3076  mail_search_full (vms->mailstream, NULL, pgm, NIL);
3077  vms->lastmsg = vms->vmArrayIndex - 1;
3078  mail_free_searchpgm(&pgm);
3079  /* Since IMAP storage actually stores both old and new messages in the same IMAP folder,
3080  * ensure to allocate enough space to account for all of them. Warn if old messages
3081  * have not been checked first as that is required.
3082  */
3083  if (box == 0 && !vms->dh_arraysize) {
3084  ast_log(LOG_WARNING, "The code expects the old messages to be checked first, fix the code.\n");
3085  }
3086  if (vm_allocate_dh(vms, vmu, box == 0 ? vms->vmArrayIndex + vms->oldmessages : vms->lastmsg)) {
3087  ast_mutex_unlock(&vms->lock);
3088  return -1;
3089  }
3090 
3091  ast_mutex_unlock(&vms->lock);
3092  return 0;
3093 }
3094 
3095 static void write_file(char *filename, char *buffer, unsigned long len)
3096 {
3097  FILE *output;
3098 
3099  if (!filename || !buffer) {
3100  return;
3101  }
3102 
3103  if (!(output = fopen(filename, "w"))) {
3104  ast_log(LOG_ERROR, "Unable to open/create file %s: %s\n", filename, strerror(errno));
3105  return;
3106  }
3107 
3108  if (fwrite(buffer, len, 1, output) != 1) {
3109  if (ferror(output)) {
3110  ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
3111  }
3112  }
3113  fclose (output);
3114 }
3115 
3116 static void update_messages_by_imapuser(const char *user, unsigned long number)
3117 {
3118  struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
3119 
3120  if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
3121  return;
3122  }
3123 
3124  ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
3125 
3126  /* Ensure we have room for the next message. */
3127  if (vms->vmArrayIndex >= vms->msg_array_max) {
3128  long *new_mem = ast_realloc(vms->msgArray, 2 * vms->msg_array_max * sizeof(long));
3129  if (!new_mem) {
3130  return;
3131  }
3132  vms->msgArray = new_mem;
3133  vms->msg_array_max *= 2;
3134  }
3135 
3136  vms->msgArray[vms->vmArrayIndex++] = number;
3137 }
3138 
3139 void mm_searched(MAILSTREAM *stream, unsigned long number)
3140 {
3141  char *mailbox = stream->mailbox, buf[1024] = "", *user;
3142 
3143  if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
3144  return;
3145 
3146  update_messages_by_imapuser(user, number);
3147 }
3148 
3149 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
3150 {
3151  struct ast_variable *var;
3152  struct ast_vm_user *vmu;
3153 
3154  vmu = ast_calloc(1, sizeof *vmu);
3155  if (!vmu)
3156  return NULL;
3157 
3158  populate_defaults(vmu);
3159  ast_set_flag(vmu, VM_ALLOCED);
3160 
3161  var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
3162  if (var) {
3163  apply_options_full(vmu, var);
3165  return vmu;
3166  } else {
3167  ast_free(vmu);
3168  return NULL;
3169  }
3170 }
3171 
3172 /* Interfaces to C-client */
3173 
3174 void mm_exists(MAILSTREAM * stream, unsigned long number)
3175 {
3176  /* mail_ping will callback here if new mail! */
3177  ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
3178  if (number == 0) return;
3179  set_update(stream);
3180 }
3181 
3182 
3183 void mm_expunged(MAILSTREAM * stream, unsigned long number)
3184 {
3185  /* mail_ping will callback here if expunged mail! */
3186  ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
3187  if (number == 0) return;
3188  set_update(stream);
3189 }
3190 
3191 
3192 void mm_flags(MAILSTREAM * stream, unsigned long number)
3193 {
3194  /* mail_ping will callback here if read mail! */
3195  ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
3196  if (number == 0) return;
3197  set_update(stream);
3198 }
3199 
3200 
3201 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
3202 {
3203  ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
3204  mm_log (string, errflg);
3205 }
3206 
3207 
3208 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
3209 {
3210  if (delimiter == '\0') {
3211  delimiter = delim;
3212  }
3213 
3214  ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
3215  if (attributes & LATT_NOINFERIORS)
3216  ast_debug(5, "no inferiors\n");
3217  if (attributes & LATT_NOSELECT)
3218  ast_debug(5, "no select\n");
3219  if (attributes & LATT_MARKED)
3220  ast_debug(5, "marked\n");
3221  if (attributes & LATT_UNMARKED)
3222  ast_debug(5, "unmarked\n");
3223 }
3224 
3225 
3226 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
3227 {
3228  ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
3229  if (attributes & LATT_NOINFERIORS)
3230  ast_debug(5, "no inferiors\n");
3231  if (attributes & LATT_NOSELECT)
3232  ast_debug(5, "no select\n");
3233  if (attributes & LATT_MARKED)
3234  ast_debug(5, "marked\n");
3235  if (attributes & LATT_UNMARKED)
3236  ast_debug(5, "unmarked\n");
3237 }
3238 
3239 
3240 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
3241 {
3242  struct ast_str *str;
3243 
3244  if (!DEBUG_ATLEAST(5) || !(str = ast_str_create(256))) {
3245  return;
3246  }
3247 
3248  ast_str_append(&str, 0, " Mailbox %s", mailbox);
3249  if (status->flags & SA_MESSAGES) {
3250  ast_str_append(&str, 0, ", %lu messages", status->messages);
3251  }
3252  if (status->flags & SA_RECENT) {
3253  ast_str_append(&str, 0, ", %lu recent", status->recent);
3254  }
3255  if (status->flags & SA_UNSEEN) {
3256  ast_str_append(&str, 0, ", %lu unseen", status->unseen);
3257  }
3258  if (status->flags & SA_UIDVALIDITY) {
3259  ast_str_append(&str, 0, ", %lu UID validity", status->uidvalidity);
3260  }
3261  if (status->flags & SA_UIDNEXT) {
3262  ast_str_append(&str, 0, ", %lu next UID", status->uidnext);
3263  }
3264  ast_log(LOG_DEBUG, "%s\n", ast_str_buffer(str));
3265 
3266  ast_free(str);
3267 }
3268 
3269 
3270 void mm_log(char *string, long errflg)
3271 {
3272  switch ((short) errflg) {
3273  case NIL:
3274  ast_debug(1, "IMAP Info: %s\n", string);
3275  break;
3276  case PARSE:
3277  case WARN:
3278  ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
3279  break;
3280  case ERROR:
3281  ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
3282  break;
3283  }
3284 }
3285 
3286 
3287 void mm_dlog(char *string)
3288 {
3289  ast_log(AST_LOG_NOTICE, "%s\n", string);
3290 }
3291 
3292 
3293 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
3294 {
3295  struct ast_vm_user *vmu;
3296 
3297  ast_debug(4, "Entering callback mm_login\n");
3298 
3299  ast_copy_string(user, mb->user, MAILTMPLEN);
3300 
3301  /* We should only do this when necessary */
3302  if (!ast_strlen_zero(authpassword)) {
3303  ast_copy_string(pwd, authpassword, MAILTMPLEN);
3304  } else {
3305  AST_LIST_TRAVERSE(&users, vmu, list) {
3306  if (!strcasecmp(mb->user, vmu->imapuser)) {
3307  ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
3308  break;
3309  }
3310  }
3311  if (!vmu) {
3312  if ((vmu = find_user_realtime_imapuser(mb->user))) {
3313  ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
3314  free_user(vmu);
3315  }
3316  }
3317  }
3318 }
3319 
3320 
3321 void mm_critical(MAILSTREAM * stream)
3322 {
3323 }
3324 
3325 
3326 void mm_nocritical(MAILSTREAM * stream)
3327 {
3328 }
3329 
3330 
3331 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
3332 {
3333  kill (getpid (), SIGSTOP);
3334  return NIL;
3335 }
3336 
3337 
3338 void mm_fatal(char *string)
3339 {
3340  ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
3341 }
3342 
3343 /* C-client callback to handle quota */
3344 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
3345 {
3346  struct vm_state *vms;
3347  char *mailbox = stream->mailbox, *user;
3348  char buf[1024] = "";
3349  unsigned long usage = 0, limit = 0;
3350 
3351  while (pquota) {
3352  usage = pquota->usage;
3353  limit = pquota->limit;
3354  pquota = pquota->next;
3355  }
3356 
3357  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)))) {
3358  ast_log(AST_LOG_ERROR, "No state found.\n");
3359  return;
3360  }
3361 
3362  ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
3363 
3364  vms->quota_usage = usage;
3365  vms->quota_limit = limit;
3366 }
3367 
3368 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
3369 {
3370  char *start, *eol_pnt;
3371  int taglen;
3372 
3373  if (ast_strlen_zero(header) || ast_strlen_zero(tag))
3374  return NULL;
3375 
3376  taglen = strlen(tag) + 1;
3377  if (taglen < 1)
3378  return NULL;
3379 
3380  if (!(start = strcasestr(header, tag)))
3381  return NULL;
3382 
3383  /* Since we can be called multiple times we should clear our buffer */
3384  memset(buf, 0, len);
3385 
3386  ast_copy_string(buf, start+taglen, len);
3387  if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
3388  *eol_pnt = '\0';
3389  return buf;
3390 }
3391 
3392 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
3393 {
3394  char *start, *eol_pnt, *quote;
3395 
3396  if (ast_strlen_zero(mailbox))
3397  return NULL;
3398 
3399  if (!(start = strstr(mailbox, "/user=")))
3400  return NULL;
3401 
3402  ast_copy_string(buf, start+6, len);
3403 
3404  if (!(quote = strchr(buf, '"'))) {
3405  if ((eol_pnt = strchr(buf, '/')) || (eol_pnt = strchr(buf, '}'))) {
3406  *eol_pnt = '\0';
3407  }
3408  return buf;
3409  } else {
3410  if ((eol_pnt = strchr(quote + 1, '"'))) {
3411  *eol_pnt = '\0';
3412  }
3413  return quote + 1;
3414  }
3415 }
3416 
3417 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
3418 {
3419  struct vm_state *vms_p;
3420 
3421  pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
3422  if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
3423  return vms_p;
3424  }
3425  ast_debug(5, "Adding new vmstate for %s\n", vmu->imapuser);
3426  /* XXX: Is this correctly freed always? */
3427  if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
3428  return NULL;
3429  ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
3430  ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
3431  ast_copy_string(vms_p->imapserver, vmu->imapserver, sizeof(vms_p->imapserver));
3432  ast_copy_string(vms_p->imapport, vmu->imapport, sizeof(vms_p->imapport));
3433  ast_copy_string(vms_p->imapflags, vmu->imapflags, sizeof(vms_p->imapflags));
3434  ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
3435  ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
3436  vms_p->mailstream = NIL; /* save for access from interactive entry point */
3437  vms_p->imapversion = vmu->imapversion;
3438  ast_debug(5, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
3439  vms_p->updated = 1;
3440  /* set mailbox to INBOX! */
3441  ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
3442  init_vm_state(vms_p);
3443  vmstate_insert(vms_p);
3444  return vms_p;
3445 }
3446 
3447 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
3448 {
3449  struct vmstate *vlist = NULL;
3450 
3451  if (interactive) {
3452  struct vm_state *vms;
3453  pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
3454  if ((vms = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms->imapuser, user)) {
3455  return vms;
3456  }
3457  }
3458 
3459  AST_LIST_LOCK(&vmstates);
3460  AST_LIST_TRAVERSE(&vmstates, vlist, list) {
3461  if (!vlist->vms) {
3462  ast_debug(3, "error: vms is NULL for %s\n", user);
3463  continue;
3464  }
3465  if (vlist->vms->imapversion != imapversion) {
3466  continue;
3467  }
3468 
3469  if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
3470  AST_LIST_UNLOCK(&vmstates);
3471  return vlist->vms;
3472  }
3473  }
3474  AST_LIST_UNLOCK(&vmstates);
3475 
3476  ast_debug(3, "%s not found in vmstates\n", user);
3477 
3478  return NULL;
3479 }
3480 
3481 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
3482 {
3483 
3484  struct vmstate *vlist = NULL;
3485  const char *local_context = S_OR(context, "default");
3486 
3487  if (interactive) {
3488  struct vm_state *vms;
3489  pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
3490  if ((vms = pthread_getspecific(ts_vmstate.key)) &&
3491  !strcmp(vms->username,mailbox) && !strcmp(vms->context, local_context)) {
3492  return vms;
3493  }
3494  }
3495 
3496  AST_LIST_LOCK(&vmstates);
3497  AST_LIST_TRAVERSE(&vmstates, vlist, list) {
3498  if (!vlist->vms) {
3499  ast_debug(3, "error: vms is NULL for %s\n", mailbox);
3500  continue;
3501  }
3502  if (vlist->vms->imapversion != imapversion) {
3503  continue;
3504  }
3505 
3506  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);
3507 
3508  if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
3509  ast_debug(3, "Found it!\n");
3510  AST_LIST_UNLOCK(&vmstates);
3511  return vlist->vms;
3512  }
3513  }
3514  AST_LIST_UNLOCK(&vmstates);
3515 
3516  ast_debug(3, "%s not found in vmstates\n", mailbox);
3517 
3518  return NULL;
3519 }
3520 
3521 static void vmstate_insert(struct vm_state *vms)
3522 {
3523  struct vmstate *v;
3524  struct vm_state *altvms;
3525 
3526  /* If interactive, it probably already exists, and we should
3527  use the one we already have since it is more up to date.
3528  We can compare the username to find the duplicate */
3529  if (vms->interactive == 1) {
3530  altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
3531  if (altvms) {
3532  ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3533  vms->newmessages = altvms->newmessages;
3534  vms->oldmessages = altvms->oldmessages;
3535  vms->vmArrayIndex = altvms->vmArrayIndex;
3536  /* XXX: no msgArray copying? */
3537  vms->lastmsg = altvms->lastmsg;
3538  vms->curmsg = altvms->curmsg;
3539  /* get a pointer to the persistent store */
3540  vms->persist_vms = altvms;
3541  /* Reuse the mailstream? */
3542 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
3543  vms->mailstream = altvms->mailstream;
3544 #else
3545  vms->mailstream = NIL;
3546 #endif
3547  }
3548  return;
3549  }
3550 
3551  if (!(v = ast_calloc(1, sizeof(*v))))
3552  return;
3553 
3554  v->vms = vms;
3555 
3556  ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3557 
3558  AST_LIST_LOCK(&vmstates);
3559  AST_LIST_INSERT_TAIL(&vmstates, v, list);
3560  AST_LIST_UNLOCK(&vmstates);
3561 }
3562 
3563 static void vmstate_delete(struct vm_state *vms)
3564 {
3565  struct vmstate *vc = NULL;
3566  struct vm_state *altvms = NULL;
3567 
3568  /* If interactive, we should copy pertinent info
3569  back to the persistent state (to make update immediate) */
3570  if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
3571  ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3572  altvms->newmessages = vms->newmessages;
3573  altvms->oldmessages = vms->oldmessages;
3574  altvms->updated = 1;
3575  vms->mailstream = mail_close(vms->mailstream);
3576 
3577  /* Interactive states are not stored within the persistent list */
3578  return;
3579  }
3580 
3581  ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3582 
3583  AST_LIST_LOCK(&vmstates);
3584  AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
3585  if (vc->vms == vms) {
3587  break;
3588  }
3589  }
3591  AST_LIST_UNLOCK(&vmstates);
3592 
3593  if (vc) {
3594  ast_mutex_destroy(&vc->vms->lock);
3595  ast_free(vc->vms->msgArray);
3596  vc->vms->msgArray = NULL;
3597  vc->vms->msg_array_max = 0;
3598  /* XXX: is no one supposed to free vms itself? */
3599  ast_free(vc);
3600  } else {
3601  ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3602  }
3603 }
3604 
3605 static void set_update(MAILSTREAM * stream)
3606 {
3607  struct vm_state *vms;
3608  char *mailbox = stream->mailbox, *user;
3609  char buf[1024] = "";
3610 
3611  if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
3612  if (user && DEBUG_ATLEAST(3))
3613  ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
3614  return;
3615  }
3616 
3617  ast_debug(3, "User %s mailbox set for update.\n", user);
3618 
3619  vms->updated = 1; /* Set updated flag since mailbox changed */
3620 }
3621 
3622 static void init_vm_state(struct vm_state *vms)
3623 {
3624  vms->msg_array_max = VMSTATE_MAX_MSG_ARRAY;
3625  vms->msgArray = ast_calloc(vms->msg_array_max, sizeof(long));
3626  if (!vms->msgArray) {
3627  /* Out of mem? This can't be good. */
3628  vms->msg_array_max = 0;
3629  }
3630  vms->vmArrayIndex = 0;
3631  ast_mutex_init(&vms->lock);
3632 }
3633 
3634 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
3635 {
3636  char *body_content;
3637  char *body_decoded;
3638  char *fn = is_intro ? vms->introfn : vms->fn;
3639  unsigned long len = 0;
3640  unsigned long newlen = 0;
3641  char filename[256];
3642 
3643  if (!body || body == NIL)
3644  return -1;
3645 
3646  ast_mutex_lock(&vms->lock);
3647  body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
3648  ast_mutex_unlock(&vms->lock);
3651  "Msgno %ld, section %s. The body's content size %ld is huge (max %ld). User:%s, mailbox %s\n",
3652  vms->msgArray[vms->curmsg], section, len, MAX_MAIL_BODY_CONTENT_SIZE, vms->imapuser, vms->username);
3653  return -1;
3654  }
3655  if (body_content != NIL && len) {
3656  snprintf(filename, sizeof(filename), "%s.%s", fn, format);
3657  /* ast_debug(1, body_content); */
3658  body_decoded = rfc822_base64((unsigned char *) body_content, len, &newlen);
3659  /* If the body of the file is empty, return an error */
3660  if (!newlen || !body_decoded) {
3661  return -1;
3662  }
3663  write_file(filename, (char *) body_decoded, newlen);
3664  } else {
3665  ast_debug(5, "Body of message is NULL.\n");
3666  return -1;
3667  }
3668  return 0;
3669 }
3670 
3671 /*!
3672  * \brief Get delimiter via mm_list callback
3673  * \param vms The voicemail state object
3674  * \param stream
3675  *
3676  * Determines the delimiter character that is used by the underlying IMAP based mail store.
3677  */
3678 /* MUTEX should already be held */
3679 static void get_mailbox_delimiter(struct vm_state *vms, MAILSTREAM *stream) {
3680  char tmp[50];
3681  snprintf(tmp, sizeof(tmp), "{%s}", S_OR(vms->imapserver, imapserver));
3682  mail_list(stream, tmp, "*");
3683 }
3684 
3685 /*!
3686  * \brief Check Quota for user
3687  * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
3688  * \param mailbox the mailbox to check the quota for.
3689  *
3690  * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
3691  */
3692 static void check_quota(struct vm_state *vms, char *mailbox) {
3693  ast_mutex_lock(&vms->lock);
3694  mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
3695  ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
3696  if (vms && vms->mailstream != NULL) {
3697  imap_getquotaroot(vms->mailstream, mailbox);
3698  } else {
3699  ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
3700  }
3701  ast_mutex_unlock(&vms->lock);
3702 }
3703 
3704 #endif /* IMAP_STORAGE */
3705 
3706 /*! \brief Lock file path
3707  * only return failure if ast_lock_path returns 'timeout',
3708  * not if the path does not exist or any other reason
3709  */
3710 static int vm_lock_path(const char *path)
3711 {
3712  switch (ast_lock_path(path)) {
3713  case AST_LOCK_TIMEOUT:
3714  return -1;
3715  default:
3716  return 0;
3717  }
3718 }
3719 
3720 #define MSG_ID_LEN 256
3721 
3722 /* Used to attach a unique identifier to an msg_id */
3724 
3725 /*!
3726  * \brief Sets the destination string to a uniquely identifying msg_id string
3727  * \param dst pointer to a character buffer that should contain MSG_ID_LEN characters.
3728  */
3729 static void generate_msg_id(char *dst);
3730 
3731 #ifdef ODBC_STORAGE
3732 struct generic_prepare_struct {
3733  char *sql;
3734  int argc;
3735  char **argv;
3736 };
3737 
3738 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
3739 {
3740  struct generic_prepare_struct *gps = data;
3741  int res, i;
3742  SQLHSTMT stmt;
3743 
3744  res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3745  if (!SQL_SUCCEEDED(res)) {
3746  ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3747  return NULL;
3748  }
3749  res = ast_odbc_prepare(obj, stmt, gps->sql);
3750  if (!SQL_SUCCEEDED(res)) {
3751  ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
3752  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3753  return NULL;
3754  }
3755  for (i = 0; i < gps->argc; i++)
3756  SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
3757 
3758  return stmt;
3759 }
3760 
3761 static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
3762 {
3763  SQLHSTMT stmt;
3764  char sql[PATH_MAX];
3765  struct odbc_obj *obj;
3766  char msg_num_str[20];
3767  char *argv[] = { msg_id, dir, msg_num_str };
3768  struct generic_prepare_struct gps = { .sql = sql, .argc = 3, .argv = argv };
3769 
3770  obj = ast_odbc_request_obj(odbc_database, 0);
3771  if (!obj) {
3772  ast_log(LOG_WARNING, "Unable to update message ID for message %d in %s\n", msg_num, dir);
3773  return;
3774  }
3775 
3776  snprintf(msg_num_str, sizeof(msg_num_str), "%d", msg_num);
3777  snprintf(sql, sizeof(sql), "UPDATE %s SET msg_id=? WHERE dir=? AND msgnum=?", odbc_table);
3778  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3779  if (!stmt) {
3780  ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3781  } else {
3782  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3783  }
3784  ast_odbc_release_obj(obj);
3785  return;
3786 }
3787 
3788 /*!
3789  * \brief Retrieves a file from an ODBC data store.
3790  * \param dir the path to the file to be retrieved.
3791  * \param msgnum the message number, such as within a mailbox folder.
3792  *
3793  * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
3794  * The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
3795  *
3796  * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
3797  * The output is the message information file with the name msgnum and the extension .txt
3798  * and the message file with the extension of its format, in the directory with base file name of the msgnum.
3799  *
3800  * \return 0 on success, -1 on error.
3801  */
3802 static int retrieve_file(char *dir, int msgnum)
3803 {
3804  int x = 0;
3805  int res;
3806  int fd = -1;
3807  size_t fdlen = 0;
3808  void *fdm = MAP_FAILED;
3809  SQLSMALLINT colcount = 0;
3810  SQLHSTMT stmt;
3811  char sql[PATH_MAX];
3812  char fmt[80] = "";
3813  char *c;
3814  char coltitle[256];
3815  SQLSMALLINT collen;
3816  SQLSMALLINT datatype;
3817  SQLSMALLINT decimaldigits;
3818  SQLSMALLINT nullable;
3819  SQLULEN colsize;
3820  SQLLEN colsize2;
3821  FILE *f = NULL;
3822  char rowdata[80];
3823  char fn[PATH_MAX];
3824  char full_fn[PATH_MAX];
3825  char msgnums[80];
3826  char msg_id[MSG_ID_LEN] = "";
3827  char *argv[] = { dir, msgnums };
3828  struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3829  struct odbc_obj *obj;
3830 
3831  obj = ast_odbc_request_obj(odbc_database, 0);
3832  if (!obj) {
3833  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3834  return -1;
3835  }
3836 
3837  ast_copy_string(fmt, vmfmts, sizeof(fmt));
3838  c = strchr(fmt, '|');
3839  if (c)
3840  *c = '\0';
3841  if (!strcasecmp(fmt, "wav49"))
3842  strcpy(fmt, "WAV");
3843 
3844  snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3845  if (msgnum > -1)
3846  make_file(fn, sizeof(fn), dir, msgnum);
3847  else
3848  ast_copy_string(fn, dir, sizeof(fn));
3849 
3850  /* Create the information file */
3851  snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3852 
3853  if (!(f = fopen(full_fn, "w+"))) {
3854  ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
3855  goto bail;
3856  }
3857 
3858  snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3859  snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
3860 
3861  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3862  if (!stmt) {
3863  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3864  goto bail;
3865  }
3866 
3867  res = SQLFetch(stmt);
3868  if (!SQL_SUCCEEDED(res)) {
3869  if (res != SQL_NO_DATA) {
3870  ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
3871  }
3872  goto bail_with_handle;
3873  }
3874 
3875  fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
3876  if (fd < 0) {
3877  ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
3878  goto bail_with_handle;
3879  }
3880 
3881  res = SQLNumResultCols(stmt, &colcount);
3882  if (!SQL_SUCCEEDED(res)) {
3883  ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
3884  goto bail_with_handle;
3885  }
3886 
3887  fprintf(f, "[message]\n");
3888  for (x = 0; x < colcount; x++) {
3889  rowdata[0] = '\0';
3890  colsize = 0;
3891  collen = sizeof(coltitle);
3892  res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
3893  &datatype, &colsize, &decimaldigits, &nullable);
3894  if (!SQL_SUCCEEDED(res)) {
3895  ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
3896  goto bail_with_handle;
3897  }
3898  if (!strcasecmp(coltitle, "recording")) {
3899  off_t offset;
3900  res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
3901  fdlen = colsize2;
3902  if (fd > -1) {
3903  char tmp[1] = "";
3904  lseek(fd, fdlen - 1, SEEK_SET);
3905  if (write(fd, tmp, 1) != 1) {
3906  close(fd);
3907  fd = -1;
3908  continue;
3909  }
3910  /* Read out in small chunks */
3911  for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
3912  if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
3913  ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
3914  goto bail_with_handle;
3915  }
3916  res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
3917  munmap(fdm, CHUNKSIZE);
3918  if (!SQL_SUCCEEDED(res)) {
3919  ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
3920  unlink(full_fn);
3921  goto bail_with_handle;
3922  }
3923  }
3924  if (truncate(full_fn, fdlen) < 0) {
3925  ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
3926  }
3927  }
3928  } else {
3929  res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
3930  if (res == SQL_NULL_DATA && !strcasecmp(coltitle, "msg_id")) {
3931  /* Generate msg_id now, but don't store it until we're done with this
3932  connection */
3933  generate_msg_id(msg_id);
3934  snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
3935  } else if (res == SQL_NULL_DATA && !strcasecmp(coltitle, "category")) {
3936  /* Ignore null column value for category */
3937  ast_debug(3, "Ignoring null category column in ODBC voicemail retrieve_file.\n");
3938  continue;
3939  } else if (!SQL_SUCCEEDED(res)) {
3940  ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
3941  goto bail_with_handle;
3942  }
3943  if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir")) {
3944  fprintf(f, "%s=%s\n", coltitle, rowdata);
3945  }
3946  }
3947  }
3948 
3949 bail_with_handle:
3950  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3951 
3952 bail:
3953  if (f)
3954  fclose(f);
3955  if (fd > -1)
3956  close(fd);
3957 
3958  ast_odbc_release_obj(obj);
3959 
3960  /* If res_odbc is configured to only allow a single database connection, we
3961  will deadlock if we try to do this before releasing the connection we
3962  were just using. */
3963  if (!ast_strlen_zero(msg_id)) {
3964  odbc_update_msg_id(dir, msgnum, msg_id);
3965  }
3966 
3967  return x - 1;
3968 }
3969 
3970 /*!
3971  * \brief Determines the highest message number in use for a given user and mailbox folder.
3972  * \param vmu
3973  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3974  *
3975  * This method is used when mailboxes are stored in an ODBC back end.
3976  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3977  *
3978  * \return the value of zero or greater to indicate the last message index in use, -1 to indicate none.
3979 
3980  */
3981 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3982 {
3983  int x = -1;
3984  int res;
3985  SQLHSTMT stmt;
3986  char sql[PATH_MAX];
3987  char rowdata[20];
3988  char *argv[] = { dir };
3989  struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
3990  struct odbc_obj *obj;
3991 
3992  obj = ast_odbc_request_obj(odbc_database, 0);
3993  if (!obj) {
3994  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3995  return -1;
3996  }
3997 
3998  snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", odbc_table);
3999 
4000  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
4001  if (!stmt) {
4002  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
4003  goto bail;
4004  }
4005 
4006  res = SQLFetch(stmt);
4007  if (!SQL_SUCCEEDED(res)) {
4008  if (res == SQL_NO_DATA) {
4009  ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
4010  } else {
4011  ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
4012  }
4013  goto bail_with_handle;
4014  }
4015 
4016  res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
4017  if (!SQL_SUCCEEDED(res)) {
4018  ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
4019  goto bail_with_handle;
4020  }
4021 
4022  if (sscanf(rowdata, "%30d", &x) != 1) {
4023  ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
4024  }
4025 
4026 bail_with_handle:
4027  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4028 
4029 bail:
4030  ast_odbc_release_obj(obj);
4031 
4032  return x;
4033 }
4034 
4035 /*!
4036  * \brief Determines if the specified message exists.
4037  * \param dir the folder the mailbox folder to look for messages.
4038  * \param msgnum the message index to query for.
4039  *
4040  * This method is used when mailboxes are stored in an ODBC back end.
4041  *
4042  * \return greater than zero if the message exists, zero when the message does not exist or on error.
4043  */
4044 static int message_exists(char *dir, int msgnum)
4045 {
4046  int x = 0;
4047  int res;
4048  SQLHSTMT stmt;
4049  char sql[PATH_MAX];
4050  char rowdata[20];
4051  char msgnums[20];
4052  char *argv[] = { dir, msgnums };
4053  struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
4054  struct odbc_obj *obj;
4055 
4056  obj = ast_odbc_request_obj(odbc_database, 0);
4057  if (!obj) {
4058  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
4059  return 0;
4060  }
4061 
4062  snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
4063  snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
4064  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
4065  if (!stmt) {
4066  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
4067  goto bail;
4068  }
4069 
4070  res = SQLFetch(stmt);
4071  if (!SQL_SUCCEEDED(res)) {
4072  ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
4073  goto bail_with_handle;
4074  }
4075 
4076  res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
4077  if (!SQL_SUCCEEDED(res)) {
4078  ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
4079  goto bail_with_handle;
4080  }
4081 
4082  if (sscanf(rowdata, "%30d", &x) != 1) {
4083  ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
4084  }
4085 
4086 bail_with_handle:
4087  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4088 
4089 bail:
4090  ast_odbc_release_obj(obj);
4091  return x;
4092 }
4093 
4094 /*!
4095  * \brief returns the number of messages found.
4096  * \param vmu
4097  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
4098  *
4099  * This method is used when mailboxes are stored in an ODBC back end.
4100  *
4101  * \return The count of messages being zero or more, less than zero on error.
4102  */
4103 static int count_messages(struct ast_vm_user *vmu, char *dir)
4104 {
4105  int x = -1;
4106  int res;
4107  SQLHSTMT stmt;
4108  char sql[PATH_MAX];
4109  char rowdata[20];
4110  char *argv[] = { dir };
4111  struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
4112  struct odbc_obj *obj;
4113 
4114  obj = ast_odbc_request_obj(odbc_database, 0);
4115  if (!obj) {
4116  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
4117  return -1;
4118  }
4119 
4120  snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
4121  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
4122  if (!stmt) {
4123  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
4124  goto bail;
4125  }
4126 
4127  res = SQLFetch(stmt);
4128  if (!SQL_SUCCEEDED(res)) {
4129  ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
4130  goto bail_with_handle;
4131  }
4132 
4133  res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
4134  if (!SQL_SUCCEEDED(res)) {
4135  ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
4136  goto bail_with_handle;
4137  }
4138 
4139  if (sscanf(rowdata, "%30d", &x) != 1) {
4140  ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
4141  }
4142 
4143 bail_with_handle:
4144  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4145 
4146 bail:
4147  ast_odbc_release_obj(obj);
4148  return x;
4149 }
4150 
4151 /*!
4152  * \brief Deletes a message from the mailbox folder.
4153  * \param sdir The mailbox folder to work in.
4154  * \param smsg The message index to be deleted.
4155  *
4156  * This method is used when mailboxes are stored in an ODBC back end.
4157  * The specified message is directly deleted from the database 'voicemessages' table.
4158  */
4159 static void delete_file(const char *sdir, int smsg)
4160 {
4161  SQLHSTMT stmt;
4162  char sql[PATH_MAX];
4163  char msgnums[20];
4164  char *argv[] = { NULL, msgnums };
4165  struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
4166  struct odbc_obj *obj;
4167 
4168  obj = ast_odbc_request_obj(odbc_database, 0);
4169  if (!obj) {
4170  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
4171  return;
4172  }
4173 
4174  argv[0] = ast_strdupa(sdir);
4175 
4176  snprintf(msgnums, sizeof(msgnums), "%d", smsg);
4177  snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
4178  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
4179  if (!stmt) {
4180  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
4181  } else {
4182  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4183  }
4184  ast_odbc_release_obj(obj);
4185 
4186  return;
4187 }
4188 
4189 /*!
4190  * \brief Copies a voicemail from one mailbox to another.
4191  * \param sdir the folder for which to look for the message to be copied.
4192  * \param smsg the index of the message to be copied.
4193  * \param ddir the destination folder to copy the message into.
4194  * \param dmsg the index to be used for the copied message.
4195  * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
4196  * \param dmailboxcontext The context for the destination user.
4197  *
4198  * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
4199  */
4200 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
4201 {
4202  SQLHSTMT stmt;
4203  char sql[512];
4204  char msgnums[20];
4205  char msgnumd[20];
4206  char msg_id[MSG_ID_LEN];
4207  struct odbc_obj *obj;
4208  char *argv[] = { ddir, msgnumd, msg_id, dmailboxuser, dmailboxcontext, sdir, msgnums };
4209  struct generic_prepare_struct gps = { .sql = sql, .argc = 7, .argv = argv };
4210 
4211  generate_msg_id(msg_id);
4212  delete_file(ddir, dmsg);
4213  obj = ast_odbc_request_obj(odbc_database, 0);
4214  if (!obj) {
4215  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
4216  return;
4217  }
4218 
4219  snprintf(msgnums, sizeof(msgnums), "%d", smsg);
4220  snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
4221  snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
4222  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
4223  if (!stmt)
4224  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
4225  else
4226  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4227  ast_odbc_release_obj(obj);
4228 
4229  return;
4230 }
4231 
4232 struct insert_data {
4233  char *sql;
4234  const char *dir;
4235  const char *msgnums;
4236  void *data;
4237  SQLLEN datalen;
4238  SQLLEN indlen;
4239  const char *context;
4240  const char *macrocontext;
4241  const char *callerid;
4242  const char *origtime;
4243  const char *duration;
4244  const char *mailboxuser;
4245  const char *mailboxcontext;
4246  const char *category;
4247  const char *flag;
4248  const char *msg_id;
4249 };
4250 
4251 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
4252 {
4253  struct insert_data *data = vdata;
4254  int res;
4255  SQLHSTMT stmt;
4256 
4257  res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
4258  if (!SQL_SUCCEEDED(res)) {
4259  ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
4260  return NULL;
4261  }
4262 
4263  SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *) data->dir, 0, NULL);
4264  SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *) data->msgnums, 0, NULL);
4265  SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *) data->data, data->datalen, &data->indlen);
4266  SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *) data->context, 0, NULL);
4267  SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *) data->macrocontext, 0, NULL);
4268  SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *) data->callerid, 0, NULL);
4269  SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *) data->origtime, 0, NULL);
4270  SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *) data->duration, 0, NULL);
4271  SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *) data->mailboxuser, 0, NULL);
4272  SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *) data->mailboxcontext, 0, NULL);
4273  SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *) data->flag, 0, NULL);
4274  SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msg_id), 0, (void *) data->msg_id, 0, NULL);
4275  if (!ast_strlen_zero(data->category)) {
4276  SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
4277  }
4278  res = ast_odbc_execute_sql(obj, stmt, data->sql);
4279  if (!SQL_SUCCEEDED(res)) {
4280  ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
4281  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4282  return NULL;
4283  }
4284 
4285  return stmt;
4286 }
4287 
4288 /*!
4289  * \brief Stores a voicemail into the database.
4290  * \param dir the folder the mailbox folder to store the message.
4291  * \param mailboxuser the user owning the mailbox folder.
4292  * \param mailboxcontext
4293  * \param msgnum the message index for the message to be stored.
4294  *
4295  * This method is used when mailboxes are stored in an ODBC back end.
4296  * The message sound file and information file is looked up on the file system.
4297  * A SQL query is invoked to store the message into the (MySQL) database.
4298  *
4299  * \return the zero on success -1 on error.
4300  */
4301 static int store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum)
4302 {
4303  int res = 0;
4304  int fd = -1;
4305  void *fdm = MAP_FAILED;
4306  off_t fdlen = -1;
4307  SQLHSTMT stmt;
4308  char sql[PATH_MAX];
4309  char msgnums[20];
4310  char fn[PATH_MAX];
4311  char full_fn[PATH_MAX];
4312  char fmt[80]="";
4313  char *c;
4314  struct ast_config *cfg = NULL;
4315  struct odbc_obj *obj;
4316  struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext,
4317  .context = "", .macrocontext = "", .callerid = "", .origtime = "", .duration = "", .category = "", .flag = "", .msg_id = "" };
4318  struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
4319 
4320  delete_file(dir, msgnum);
4321 
4322  obj = ast_odbc_request_obj(odbc_database, 0);
4323  if (!obj) {
4324  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
4325  return -1;
4326  }
4327 
4328  do {
4329  ast_copy_string(fmt, vmfmts, sizeof(fmt));
4330  c = strchr(fmt, '|');
4331  if (c)
4332  *c = '\0';
4333  if (!strcasecmp(fmt, "wav49"))
4334  strcpy(fmt, "WAV");
4335  snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
4336  if (msgnum > -1)
4337  make_file(fn, sizeof(fn), dir, msgnum);
4338  else
4339  ast_copy_string(fn, dir, sizeof(fn));
4340  snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
4341  cfg = ast_config_load(full_fn, config_flags);
4342  snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
4343  fd = open(full_fn, O_RDWR);
4344  if (fd < 0) {
4345  ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
4346  res = -1;
4347  break;
4348  }
4349  if (valid_config(cfg)) {
4350  if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
4351  idata.context = "";
4352  }
4353  if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
4354  idata.macrocontext = "";
4355  }
4356  if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
4357  idata.callerid = "";
4358  }
4359  if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
4360  idata.origtime = "";
4361  }
4362  if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
4363  idata.duration = "";
4364  }
4365  if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
4366  idata.category = "";
4367  }
4368  if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
4369  idata.flag = "";
4370  }
4371  if (!(idata.msg_id = ast_variable_retrieve(cfg, "message", "msg_id"))) {
4372  idata.msg_id = "";
4373  }
4374  }
4375  fdlen = lseek(fd, 0, SEEK_END);
4376  if (fdlen < 0 || lseek(fd, 0, SEEK_SET) < 0) {
4377  ast_log(AST_LOG_WARNING, "Failed to process sound file '%s': %s\n", full_fn, strerror(errno));
4378  res = -1;
4379  break;
4380  }
4381  fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
4382  if (fdm == MAP_FAILED) {
4383  ast_log(AST_LOG_WARNING, "Memory map failed for sound file '%s'!\n", full_fn);
4384  res = -1;
4385  break;
4386  }
4387  idata.data = fdm;
4388  idata.datalen = idata.indlen = fdlen;
4389 
4390  if (!ast_strlen_zero(idata.category))
4391  snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
4392  else
4393  snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
4394 
4395  if (ast_strlen_zero(idata.origtime)) {
4396  idata.origtime = "0";
4397  }
4398 
4399  if (ast_strlen_zero(idata.duration)) {
4400  idata.duration = "0";
4401  }
4402 
4403  if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
4404  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4405  } else {
4406  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
4407  res = -1;
4408  }
4409  } while (0);
4410 
4411  ast_odbc_release_obj(obj);
4412 
4413  if (valid_config(cfg))
4414  ast_config_destroy(cfg);
4415  if (fdm != MAP_FAILED)
4416  munmap(fdm, fdlen);
4417  if (fd > -1)
4418  close(fd);
4419  return res;
4420 }
4421 
4422 /*!
4423  * \brief Renames a message in a mailbox folder.
4424  * \param sdir The folder of the message to be renamed.
4425  * \param smsg The index of the message to be renamed.
4426  * \param mailboxuser The user to become the owner of the message after it is renamed. Usually this will be the same as the original owner.
4427  * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
4428  * \param ddir The destination folder for the message to be renamed into
4429  * \param dmsg The destination message for the message to be renamed.
4430  *
4431  * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
4432  * The is usually used to resequence the messages in the mailbox, such as to delete messag index 0, it would be called successively to slide all the other messages down one index.
4433  * But in theory, because the SQL query performs an update on (dir, msgnum, mailboxuser, mailboxcontext) in the database, it should be possible to have the message relocated to another mailbox or context as well.
4434  */
4435 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
4436 {
4437  SQLHSTMT stmt;
4438  char sql[PATH_MAX];
4439  char msgnums[20];
4440  char msgnumd[20];
4441  struct odbc_obj *obj;
4442  char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
4443  struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
4444 
4445  delete_file(ddir, dmsg);
4446 
4447  obj = ast_odbc_request_obj(odbc_database, 0);
4448  if (!obj) {
4449  ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
4450  return;
4451  }
4452 
4453  snprintf(msgnums, sizeof(msgnums), "%d", smsg);
4454  snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
4455  snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
4456  stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
4457  if (!stmt)
4458  ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
4459  else
4460  SQLFreeHandle(SQL_HANDLE_STMT, stmt);
4461  ast_odbc_release_obj(obj);
4462  return;
4463 }
4464 
4465 /*!
4466  * \brief Removes a voicemail message file.
4467  * \param dir the path to the message file.
4468  * \param msgnum the unique number for the message within the mailbox.
4469  *
4470  * Removes the message content file and the information file.
4471  * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
4472  * Typical use is to clean up after a RETRIEVE operation.
4473  * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
4474  * \return zero on success, -1 on error.
4475  */
4476 static int remove_file(char *dir, int msgnum)
4477 {
4478  char fn[PATH_MAX];
4479  char full_fn[PATH_MAX];
4480  char msgnums[80];
4481 
4482  if (msgnum > -1) {
4483  snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
4484  make_file(fn, sizeof(fn), dir, msgnum);
4485  } else
4486  ast_copy_string(fn, dir, sizeof(fn));
4487  ast_filedelete(fn, NULL);
4488  snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
4489  unlink(full_fn);
4490  return 0;
4491 }
4492 #else
4493 #ifndef IMAP_STORAGE
4494 /*!
4495  * \brief Find all .txt files - even if they are not in sequence from 0000.
4496  * \param vmu
4497  * \param dir
4498  *
4499  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
4500  *
4501  * \return the count of messages, zero or more.
4502  */
4503 static int count_messages(struct ast_vm_user *vmu, char *dir)
4504 {
4505 
4506  int vmcount = 0;
4507  DIR *vmdir = NULL;
4508  struct dirent *vment = NULL;
4509 
4510  if (vm_lock_path(dir))
4511  return ERROR_LOCK_PATH;
4512 
4513  if ((vmdir = opendir(dir))) {
4514  while ((vment = readdir(vmdir))) {
4515  if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
4516  vmcount++;
4517  }
4518  }
4519  closedir(vmdir);
4520  }
4521  ast_unlock_path(dir);
4522 
4523  return vmcount;
4524 }
4525 
4526 /*!
4527  * \brief Renames a message in a mailbox folder.
4528  * \param sfn The path to the mailbox information and data file to be renamed.
4529  * \param dfn The path for where the message data and information files will be renamed to.
4530  *
4531  * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
4532  */
4533 static void rename_file(char *sfn, char *dfn)
4534 {
4535  char stxt[PATH_MAX];
4536  char dtxt[PATH_MAX];
4537  ast_filerename(sfn, dfn, NULL);
4538  snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
4539  snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
4540  if (ast_check_realtime("voicemail_data")) {
4541  ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
4542  }
4543  rename(stxt, dtxt);
4544 }
4545 
4546 /*!
4547  * \brief Determines the highest message number in use for a given user and mailbox folder.
4548  * \param vmu
4549  * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
4550  *
4551  * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
4552  * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
4553  *
4554  * \note Should always be called with a lock already set on dir.
4555  * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
4556  */
4557 static int last_message_index(struct ast_vm_user *vmu, char *dir)
4558 {
4559  int x;
4560  unsigned char map[MAXMSGLIMIT] = "";
4561  DIR *msgdir;
4562  struct dirent *msgdirent;
4563  int msgdirint;
4564  char extension[4];
4565  int stopcount = 0;
4566 
4567  /* Reading the entire directory into a file map scales better than
4568  * doing a stat repeatedly on a predicted sequence. I suspect this
4569  * is partially due to stat(2) internally doing a readdir(2) itself to
4570  * find each file. */
4571  if (!(msgdir = opendir(dir))) {
4572  return -1;
4573  }
4574 
4575  while ((msgdirent = readdir(msgdir))) {
4576  if (sscanf(msgdirent->d_name, "msg%30d.%3s", &msgdirint, extension) == 2 && !strcmp(extension, "txt") && msgdirint < MAXMSGLIMIT) {
4577  map[msgdirint] = 1;
4578  stopcount++;
4579  ast_debug(4, "%s map[%d] = %d, count = %d\n", dir, msgdirint, map[msgdirint], stopcount);
4580  }
4581  }
4582  closedir(msgdir);
4583 
4584  for (x = 0; x < vmu->maxmsg; x++) {
4585  if (map[x] == 1) {
4586  stopcount--;
4587  } else if (map[x] == 0 && !stopcount) {
4588  break;
4589  }
4590  }
4591 
4592  return x - 1;
4593 }
4594 
4595 #endif /* #ifndef IMAP_STORAGE */
4596 #endif /* #else of #ifdef ODBC_STORAGE */
4597 #ifndef IMAP_STORAGE
4598 /*!
4599  * \brief Utility function to copy a file.
4600  * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
4601  * \param outfile The path for which to copy the file to. The directory permissions must allow the creation (or truncation) of the file, and allow for opening the file in write only mode.
4602  *
4603  * When the compiler option HARDLINK_WHEN_POSSIBLE is set, the copy operation will attempt to use the hard link facility instead of copy the file (to save disk space). If the link operation fails, it falls back to the copy operation.
4604  * The copy operation copies up to 4096 bytes at once.
4605  *
4606  * \return zero on success, -1 on error.
4607  */
4608 static int copy(char *infile, char *outfile)
4609 {
4610  int ifd;
4611  int ofd;
4612  int res = -1;
4613  int len;
4614  char buf[4096];
4615 
4616 #ifdef HARDLINK_WHEN_POSSIBLE
4617  /* Hard link if possible; saves disk space & is faster */
4618  if (!link(infile, outfile)) {
4619  return 0;
4620  }
4621 #endif
4622 
4623  if ((ifd = open(infile, O_RDONLY)) < 0) {
4624  ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
4625  return -1;
4626  }
4627 
4628  if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
4629  ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
4630  close(ifd);
4631  return -1;
4632  }
4633 
4634  for (;;) {
4635  int wrlen;
4636 
4637  len = read(ifd, buf, sizeof(buf));
4638  if (!len) {
4639  res = 0;
4640  break;
4641  }
4642 
4643  if (len < 0) {
4644  ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
4645  break;
4646  }
4647 
4648  wrlen = write(ofd, buf, len);
4649  if (errno == ENOMEM || errno == ENOSPC || wrlen != len) {
4650  ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, wrlen, len, strerror(errno));
4651  break;
4652  }
4653  }
4654 
4655  close(ifd);
4656  close(ofd);
4657  if (res) {
4658  unlink(outfile);
4659  }
4660 
4661  return res;
4662 }
4663 
4664 /*!
4665  * \brief Copies a voicemail information (envelope) file.
4666  * \param frompath
4667  * \param topath
4668  *
4669  * Every voicemail has the data (.wav) file, and the information file.
4670  * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
4671  * This is used by the COPY macro when not using IMAP storage.
4672  */
4673 static void copy_plain_file(char *frompath, char *topath)
4674 {
4675  char frompath2[PATH_MAX], topath2[PATH_MAX];
4676  struct ast_variable *tmp, *var = NULL;
4677  const char *origmailbox = "", *context = "", *macrocontext = "", *exten = "";
4678  const char *priority = "", *callerchan = "", *callerid = "", *origdate = "";
4679  const char *origtime = "", *category = "", *duration = "";
4680 
4681  ast_filecopy(frompath, topath, NULL);
4682  snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
4683  snprintf(topath2, sizeof(topath2), "%s.txt", topath);
4684 
4685  if (ast_check_realtime("voicemail_data")) {
4686  var = ast_load_realtime("voicemail_data", "filename", frompath, SENTINEL);
4687  /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
4688  for (tmp = var; tmp; tmp = tmp->next) {
4689  if (!strcasecmp(tmp->name, "origmailbox")) {
4690  origmailbox = tmp->value;
4691  } else if (!strcasecmp(tmp->name, "context")) {
4692  context = tmp->value;
4693  } else if (!strcasecmp(tmp->name, "macrocontext")) {
4694  macrocontext = tmp->value;
4695  } else if (!strcasecmp(tmp->name, "exten")) {
4696  exten = tmp->value;
4697  } else if (!strcasecmp(tmp->name, "priority")) {
4698  priority = tmp->value;
4699  } else if (!strcasecmp(tmp->name, "callerchan")) {
4700  callerchan = tmp->value;
4701  } else if (!strcasecmp(tmp->name, "callerid")) {
4702  callerid = tmp->value;
4703  } else if (!strcasecmp(tmp->name, "origdate")) {
4704  origdate = tmp->value;
4705  } else if (!strcasecmp(tmp->name, "origtime")) {
4706  origtime = tmp->value;
4707  } else if (!strcasecmp(tmp->name, "category")) {
4708  category = tmp->value;
4709  } else if (!strcasecmp(tmp->name, "duration")) {
4710  duration = tmp->value;
4711  }
4712  }
4713  ast_store_realtime("voicemail_data", "filename", topath, "origmailbox", origmailbox, "context", context, "macrocontext", macrocontext, "exten", exten, "priority", priority, "callerchan", callerchan, "callerid", callerid, "origdate", origdate, "origtime", origtime, "category", category, "duration", duration, SENTINEL);
4714  }
4715  copy(frompath2, topath2);
4717 }
4718 #endif
4719 
4720 /*!
4721  * \brief Removes the voicemail sound and information file.
4722  * \param file The path to the sound file. This will be the folder and message index, without the extension.
4723  *
4724  * This is used by the DELETE macro when voicemails are stored on the file system.
4725  *
4726  * \return zero on success, -1 on error.
4727  */
4728 static int vm_delete(char *file)
4729 {
4730  char *txt;
4731  int txtsize = 0;
4732 
4733  txtsize = (strlen(file) + 5)*sizeof(char);
4734  txt = ast_alloca(txtsize);
4735  /* Sprintf here would safe because we alloca'd exactly the right length,
4736  * but trying to eliminate all sprintf's anyhow
4737  */
4738  if (ast_check_realtime("voicemail_data")) {
4739  ast_destroy_realtime("voicemail_data", "filename", file, SENTINEL);
4740  }
4741  snprintf(txt, txtsize, "%s.txt", file);
4742  unlink(txt);
4743  return ast_filedelete(file, NULL);
4744 }
4745 
4746 static void prep_email_sub_vars(struct ast_channel *ast, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *dur, char *date, const char *category, const char *flag)
4747 {
4748  char callerid[256];
4749  char num[12];
4750  char fromdir[256], fromfile[256];
4751  struct ast_config *msg_cfg;
4752  const char *origcallerid, *origtime;
4753  char origcidname[80], origcidnum[80], origdate[80];
4754  int inttime;
4755  struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
4756 
4757  /* Prepare variables for substitution in email body and subject */
4758  pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
4759  pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
4760  snprintf(num, sizeof(num), "%d", msgnum);
4761  pbx_builtin_setvar_helper(ast, "VM_MSGNUM", num);
4762  pbx_builtin_setvar_helper(ast, "VM_CONTEXT", context);
4763  pbx_builtin_setvar_helper(ast, "VM_MAILBOX", mailbox);
4764  pbx_builtin_setvar_helper(ast, "VM_CALLERID", (!ast_strlen_zero(cidname) || !ast_strlen_zero(cidnum)) ?
4765  ast_callerid_merge(callerid, sizeof(callerid), cidname, cidnum, NULL) : "an unknown caller");
4766  pbx_builtin_setvar_helper(ast, "VM_CIDNAME", (!ast_strlen_zero(cidname) ? cidname : "an unknown caller"));
4767  pbx_builtin_setvar_helper(ast, "VM_CIDNUM", (!ast_strlen_zero(cidnum) ? cidnum : "an unknown caller"));
4768  pbx_builtin_setvar_helper(ast, "VM_DATE", date);
4769  pbx_builtin_setvar_helper(ast, "VM_CATEGORY", category ? ast_strdupa(category) : "no category");
4770  pbx_builtin_setvar_helper(ast, "VM_FLAG", flag);
4771 
4772  /* Retrieve info from VM attribute file */
4773  make_dir(fromdir, sizeof(fromdir), vmu->context, vmu->mailbox, fromfolder);
4774  make_file(fromfile, sizeof(fromfile), fromdir, msgnum - 1);
4775  if (strlen(fromfile) < sizeof(fromfile) - 5) {
4776  strcat(fromfile, ".txt");
4777  }
4778  if (!(msg_cfg = ast_config_load(fromfile, config_flags)) || !(valid_config(msg_cfg))) {
4779  ast_debug(1, "Config load for message text file '%s' failed\n", fromfile);
4780  return;
4781  }
4782 
4783  if ((origcallerid = ast_variable_retrieve(msg_cfg, "message", "callerid"))) {
4784  pbx_builtin_setvar_helper(ast, "ORIG_VM_CALLERID", origcallerid);
4785  ast_callerid_split(origcallerid, origcidname, sizeof(origcidname), origcidnum, sizeof(origcidnum));
4786  pbx_builtin_setvar_helper(ast, "ORIG_VM_CIDNAME", origcidname);
4787  pbx_builtin_setvar_helper(ast, "ORIG_VM_CIDNUM", origcidnum);
4788  }
4789 
4790  if ((origtime = ast_variable_retrieve(msg_cfg, "message", "origtime")) && sscanf(origtime, "%30d", &inttime) == 1) {
4791  struct timeval tv = { inttime, };
4792  struct ast_tm tm;
4793  ast_localtime(&tv, &tm, NULL);
4794  ast_strftime_locale(origdate, sizeof(origdate), emaildateformat, &tm, S_OR(vmu->locale, NULL));
4795  pbx_builtin_setvar_helper(ast, "ORIG_VM_DATE", origdate);
4796  }
4797  ast_config_destroy(msg_cfg);
4798 }
4799 
4800 /*!
4801  * \brief Wraps a character sequence in double quotes, escaping occurences of quotes within the string.
4802  * \param from The string to work with.
4803  * \param buf The buffer into which to write the modified quoted string.
4804  * \param maxlen Always zero, but see \see ast_str
4805  *
4806  * \return The destination string with quotes wrapped on it (the to field).
4807  */
4808 static const char *ast_str_quote(struct ast_str **buf, ssize_t maxlen, const char *from)
4809 {
4810  const char *ptr;
4811 
4812  /* We're only ever passing 0 to maxlen, so short output isn't possible */
4813  ast_str_set(buf, maxlen, "\"");
4814  for (ptr = from; *ptr; ptr++) {
4815  if (*ptr == '"' || *ptr == '\\') {
4816  ast_str_append(buf, maxlen, "\\%c", *ptr);
4817  } else {
4818  ast_str_append(buf, maxlen, "%c", *ptr);
4819  }
4820  }
4821  ast_str_append(buf, maxlen, "\"");
4822 
4823  return ast_str_buffer(*buf);
4824 }
4825 
4826 /*! \brief
4827  * fill in *tm for current time according to the proper timezone, if any.
4828  * \return tm so it can be used as a function argument.
4829  */
4830 static const struct ast_tm *vmu_tm(const struct ast_vm_user *vmu, struct ast_tm *tm)
4831 {
4832  const struct vm_zone *z = NULL;
4833  struct timeval t = ast_tvnow();
4834 
4835  /* Does this user have a timezone specified? */
4836  if (!ast_strlen_zero(vmu->zonetag)) {
4837  /* Find the zone in the list */
4838  AST_LIST_LOCK(&zones);
4839  AST_LIST_TRAVERSE(&zones, z, list) {
4840  if (!strcmp(z->name, vmu->zonetag))
4841  break;
4842  }
4844  }
4845  ast_localtime(&t, tm, z ? z->timezone : NULL);
4846  return tm;
4847 }
4848 
4849 /*!\brief Check if the string would need encoding within the MIME standard, to
4850  * avoid confusing certain mail software that expects messages to be 7-bit
4851  * clean.
4852  */
4853 static int check_mime(const char *str)
4854 {
4855  for (; *str; str++) {
4856  if (*str > 126 || *str < 32 || strchr("()<>@,:;/\"[]?.=", *str)) {
4857  return 1;
4858  }
4859  }
4860  return 0;
4861 }
4862 
4863 /*!\brief Encode a string according to the MIME rules for encoding strings
4864  * that are not 7-bit clean or contain control characters.
4865  *
4866  * Additionally, if the encoded string would exceed the MIME limit of 76
4867  * characters per line, then the encoding will be broken up into multiple
4868  * sections, separated by a space character, in order to facilitate
4869  * breaking up the associated header across multiple lines.
4870  *
4871  * \param end An expandable buffer for holding the result
4872  * \param maxlen Always zero, but see \see ast_str
4873  * \param start A string to be encoded
4874  * \param preamble The length of the first line already used for this string,
4875  * to ensure that each line maintains a maximum length of 76 chars.
4876  * \param postamble the length of any additional characters appended to the
4877  * line, used to ensure proper field wrapping.
4878  * \retval The encoded string.
4879  */
4880 static const char *ast_str_encode_mime(struct ast_str **end, ssize_t maxlen, const char *start, size_t preamble, size_t postamble)
4881 {
4882  struct ast_str *tmp = ast_str_alloca(80);
4883  int first_section = 1;
4884 
4885