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