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