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