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