2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 1999 - 2006, Digium, Inc.
6 * Mark Spencer <markster@digium.com>
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.
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.
21 * \author Mark Spencer <markster@digium.com>
22 * \brief Comedian Mail - Voicemail System
24 * \extref unixODBC (http://www.unixodbc.org/)
25 * \extref A source distribution of University of Washington's IMAP c-client
26 * (http://www.washington.edu/imap/)
30 * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
31 * \ingroup applications
32 * \note This module requires res_adsi to load. This needs to be optional
35 * \note 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.
46 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" touch_on_change="apps/app_voicemail.c apps/app_directory.c">
47 <member name="FILE_STORAGE" displayname="Storage of Voicemail using filesystem">
48 <conflict>ODBC_STORAGE</conflict>
49 <conflict>IMAP_STORAGE</conflict>
50 <defaultenabled>yes</defaultenabled>
52 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
53 <depend>generic_odbc</depend>
55 <conflict>IMAP_STORAGE</conflict>
56 <conflict>FILE_STORAGE</conflict>
57 <defaultenabled>no</defaultenabled>
59 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
60 <depend>imap_tk</depend>
61 <conflict>ODBC_STORAGE</conflict>
62 <conflict>FILE_STORAGE</conflict>
64 <defaultenabled>no</defaultenabled>
75 #ifdef USE_SYSTEM_IMAP
76 #include <imap/c-client.h>
77 #include <imap/imap4r1.h>
78 #include <imap/linkage.h>
79 #elif defined (USE_SYSTEM_CCLIENT)
80 #include <c-client/c-client.h>
81 #include <c-client/imap4r1.h>
82 #include <c-client/linkage.h>
90 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
92 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
98 #if defined(__FreeBSD__) || defined(__OpenBSD__)
102 #include "asterisk/logger.h"
103 #include "asterisk/lock.h"
104 #include "asterisk/file.h"
105 #include "asterisk/channel.h"
106 #include "asterisk/pbx.h"
107 #include "asterisk/config.h"
108 #include "asterisk/say.h"
109 #include "asterisk/module.h"
110 #include "asterisk/adsi.h"
111 #include "asterisk/app.h"
112 #include "asterisk/manager.h"
113 #include "asterisk/dsp.h"
114 #include "asterisk/localtime.h"
115 #include "asterisk/cli.h"
116 #include "asterisk/utils.h"
117 #include "asterisk/stringfields.h"
118 #include "asterisk/smdi.h"
119 #include "asterisk/astobj2.h"
120 #include "asterisk/event.h"
121 #include "asterisk/taskprocessor.h"
122 #include "asterisk/test.h"
125 #include "asterisk/res_odbc.h"
129 #include "asterisk/threadstorage.h"
133 <application name="VoiceMail" language="en_US">
135 Leave a Voicemail message.
138 <parameter name="mailboxs" argsep="&" required="true">
139 <argument name="mailbox1" argsep="@" required="true">
140 <argument name="mailbox" required="true" />
141 <argument name="context" />
143 <argument name="mailbox2" argsep="@" multiple="true">
144 <argument name="mailbox" required="true" />
145 <argument name="context" />
148 <parameter name="options">
151 <para>Play the <literal>busy</literal> greeting to the calling party.</para>
154 <argument name="c" />
155 <para>Accept digits for a new extension in context <replaceable>c</replaceable>,
156 if played during the greeting. Context defaults to the current context.</para>
159 <argument name="#" required="true" />
160 <para>Use the specified amount of gain when recording the voicemail
161 message. The units are whole-number decibels (dB). Only works on supported
162 technologies, which is DAHDI only.</para>
165 <para>Skip the playback of instructions for leaving a message to the
166 calling party.</para>
169 <para>Play the <literal>unavailable</literal> greeting.</para>
172 <para>Mark message as <literal>URGENT</literal>.</para>
175 <para>Mark message as <literal>PRIORITY</literal>.</para>
181 <para>This application allows the calling party to leave a message for the specified
182 list of mailboxes. When multiple mailboxes are specified, the greeting will be taken from
183 the first mailbox specified. Dialplan execution will stop if the specified mailbox does not
185 <para>The Voicemail application will exit if any of the following DTMF digits are received:</para>
188 <para>Jump to the <literal>o</literal> extension in the current dialplan context.</para>
191 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
194 <para>This application will set the following channel variable upon completion:</para>
196 <variable name="VMSTATUS">
197 <para>This indicates the status of the execution of the VoiceMail application.</para>
198 <value name="SUCCESS" />
199 <value name="USEREXIT" />
200 <value name="FAILED" />
205 <application name="VoiceMailMain" language="en_US">
207 Check Voicemail messages.
210 <parameter name="mailbox" required="true" argsep="@">
211 <argument name="mailbox" />
212 <argument name="context" />
214 <parameter name="options">
217 <para>Consider the <replaceable>mailbox</replaceable> parameter as a prefix to
218 the mailbox that is entered by the caller.</para>
221 <argument name="#" required="true" />
222 <para>Use the specified amount of gain when recording a voicemail message.
223 The units are whole-number decibels (dB).</para>
226 <para>Skip checking the passcode for the mailbox.</para>
229 <argument name="folder" required="true" />
230 <para>Skip folder prompt and go directly to <replaceable>folder</replaceable> specified.
231 Defaults to <literal>INBOX</literal> (or <literal>0</literal>).</para>
233 <enum name="0"><para>INBOX</para></enum>
234 <enum name="1"><para>Old</para></enum>
235 <enum name="2"><para>Work</para></enum>
236 <enum name="3"><para>Family</para></enum>
237 <enum name="4"><para>Friends</para></enum>
238 <enum name="5"><para>Cust1</para></enum>
239 <enum name="6"><para>Cust2</para></enum>
240 <enum name="7"><para>Cust3</para></enum>
241 <enum name="8"><para>Cust4</para></enum>
242 <enum name="9"><para>Cust5</para></enum>
249 <para>This application allows the calling party to check voicemail messages. A specific
250 <replaceable>mailbox</replaceable>, and optional corresponding <replaceable>context</replaceable>,
251 may be specified. If a <replaceable>mailbox</replaceable> is not provided, the calling party will
252 be prompted to enter one. If a <replaceable>context</replaceable> is not specified, the
253 <literal>default</literal> context will be used.</para>
254 <para>The VoiceMailMain application will exit if the following DTMF digit is entered as Mailbox
255 or Password, and the extension exists:</para>
258 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
263 <application name="MailboxExists" language="en_US">
265 Check to see if Voicemail mailbox exists.
268 <parameter name="mailbox" required="true" argsep="@">
269 <argument name="mailbox" required="true" />
270 <argument name="context" />
272 <parameter name="options">
273 <para>None options.</para>
277 <para>Check to see if the specified <replaceable>mailbox</replaceable> exists. If no voicemail
278 <replaceable>context</replaceable> is specified, the <literal>default</literal> context
280 <para>This application will set the following channel variable upon completion:</para>
282 <variable name="VMBOXEXISTSSTATUS">
283 <para>This will contain the status of the execution of the MailboxExists application.
284 Possible values include:</para>
285 <value name="SUCCESS" />
286 <value name="FAILED" />
291 <application name="VMAuthenticate" language="en_US">
293 Authenticate with Voicemail passwords.
296 <parameter name="mailbox" required="true" argsep="@">
297 <argument name="mailbox" />
298 <argument name="context" />
300 <parameter name="options">
303 <para>Skip playing the initial prompts.</para>
309 <para>This application behaves the same way as the Authenticate application, but the passwords
310 are taken from <filename>voicemail.conf</filename>. If the <replaceable>mailbox</replaceable> is
311 specified, only that mailbox's password will be considered valid. If the <replaceable>mailbox</replaceable>
312 is not specified, the channel variable <variable>AUTH_MAILBOX</variable> will be set with the authenticated
314 <para>The VMAuthenticate application will exit if the following DTMF digit is entered as Mailbox
315 or Password, and the extension exists:</para>
318 <para>Jump to the <literal>a</literal> extension in the current dialplan context.</para>
323 <application name="VMSayName" language="en_US">
325 Play the name of a voicemail user
328 <parameter name="mailbox" required="true" argsep="@">
329 <argument name="mailbox" />
330 <argument name="context" />
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>
338 <function name="MAILBOX_EXISTS" language="en_US">
340 Tell if a mailbox is configured.
343 <parameter name="mailbox" required="true" />
344 <parameter name="context" />
347 <para>Returns a boolean of whether the corresponding <replaceable>mailbox</replaceable> exists.
348 If <replaceable>context</replaceable> is not specified, defaults to the <literal>default</literal>
352 <manager name="VoicemailUsersList" language="en_US">
354 List All Voicemail User Information.
357 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
365 static char imapserver[48];
366 static char imapport[8];
367 static char imapflags[128];
368 static char imapfolder[64];
369 static char imapparentfolder[64] = "\0";
370 static char greetingfolder[64];
371 static char authuser[32];
372 static char authpassword[42];
373 static int imapversion = 1;
375 static int expungeonhangup = 1;
376 static int imapgreetings = 0;
377 static char delimiter = '\0';
382 AST_THREADSTORAGE(ts_vmstate);
384 /* Forward declarations for IMAP */
385 static int init_mailstream(struct vm_state *vms, int box);
386 static void write_file(char *filename, char *buffer, unsigned long len);
387 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
388 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu);
389 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
390 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive);
391 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
392 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
393 static void vmstate_insert(struct vm_state *vms);
394 static void vmstate_delete(struct vm_state *vms);
395 static void set_update(MAILSTREAM * stream);
396 static void init_vm_state(struct vm_state *vms);
397 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
398 static void get_mailbox_delimiter(MAILSTREAM *stream);
399 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
400 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
401 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
402 static void update_messages_by_imapuser(const char *user, unsigned long number);
403 static int vm_delete(char *file);
405 static int imap_remove_file (char *dir, int msgnum);
406 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
407 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
408 static void check_quota(struct vm_state *vms, char *mailbox);
409 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
411 struct vm_state *vms;
412 AST_LIST_ENTRY(vmstate) list;
415 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
419 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
421 #define COMMAND_TIMEOUT 5000
422 /* Don't modify these here; set your umask at runtime instead */
423 #define VOICEMAIL_DIR_MODE 0777
424 #define VOICEMAIL_FILE_MODE 0666
425 #define CHUNKSIZE 65536
427 #define VOICEMAIL_CONFIG "voicemail.conf"
428 #define ASTERISK_USERNAME "asterisk"
430 /* Define fast-forward, pause, restart, and reverse keys
431 while listening to a voicemail message - these are
432 strings, not characters */
433 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
434 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
435 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
436 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
437 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
438 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
440 /* Default mail command to mail voicemail. Change it with the
441 mailcmd= command in voicemail.conf */
442 #define SENDMAIL "/usr/sbin/sendmail -t"
444 #define INTRO "vm-intro"
447 #define MAXMSGLIMIT 9999
449 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
451 #define BASELINELEN 72
452 #define BASEMAXINLINE 256
459 #define MAX_DATETIME_FORMAT 512
460 #define MAX_NUM_CID_CONTEXTS 10
462 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
463 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
464 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
465 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
466 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
467 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
468 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
469 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
470 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
471 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
472 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
473 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
474 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
475 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
476 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
477 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
478 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
479 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
480 #define VM_FWDURGAUTO (1 << 18) /*!< Autoset of Urgent flag on forwarded Urgent messages set globally */
481 #define ERROR_LOCK_PATH -100
482 #define OPERATOR_EXIT 300
494 enum vm_option_flags {
495 OPT_SILENT = (1 << 0),
496 OPT_BUSY_GREETING = (1 << 1),
497 OPT_UNAVAIL_GREETING = (1 << 2),
498 OPT_RECORDGAIN = (1 << 3),
499 OPT_PREPEND_MAILBOX = (1 << 4),
500 OPT_AUTOPLAY = (1 << 6),
501 OPT_DTMFEXIT = (1 << 7),
502 OPT_MESSAGE_Urgent = (1 << 8),
503 OPT_MESSAGE_PRIORITY = (1 << 9)
506 enum vm_option_args {
507 OPT_ARG_RECORDGAIN = 0,
508 OPT_ARG_PLAYFOLDER = 1,
509 OPT_ARG_DTMFEXIT = 2,
510 /* This *must* be the last value in this enum! */
511 OPT_ARG_ARRAY_SIZE = 3,
514 enum vm_passwordlocation {
515 OPT_PWLOC_VOICEMAILCONF = 0,
516 OPT_PWLOC_SPOOLDIR = 1,
517 OPT_PWLOC_USERSCONF = 2,
520 AST_APP_OPTIONS(vm_app_options, {
521 AST_APP_OPTION('s', OPT_SILENT),
522 AST_APP_OPTION('b', OPT_BUSY_GREETING),
523 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
524 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
525 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
526 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
527 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
528 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
529 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
532 static int load_config(int reload);
534 /*! \page vmlang Voicemail Language Syntaxes Supported
536 \par Syntaxes supported, not really language codes.
543 \arg \b pt - Portuguese
544 \arg \b pt_BR - Portuguese (Brazil)
546 \arg \b no - Norwegian
548 \arg \b tw - Chinese (Taiwan)
549 \arg \b ua - Ukrainian
551 German requires the following additional soundfile:
552 \arg \b 1F einE (feminine)
554 Spanish requires the following additional soundfile:
555 \arg \b 1M un (masculine)
557 Dutch, Portuguese & Spanish require the following additional soundfiles:
558 \arg \b vm-INBOXs singular of 'new'
559 \arg \b vm-Olds singular of 'old/heard/read'
562 \arg \b vm-INBOX nieuwe (nl)
563 \arg \b vm-Old oude (nl)
566 \arg \b vm-new-a 'new', feminine singular accusative
567 \arg \b vm-new-e 'new', feminine plural accusative
568 \arg \b vm-new-ych 'new', feminine plural genitive
569 \arg \b vm-old-a 'old', feminine singular accusative
570 \arg \b vm-old-e 'old', feminine plural accusative
571 \arg \b vm-old-ych 'old', feminine plural genitive
572 \arg \b digits/1-a 'one', not always same as 'digits/1'
573 \arg \b digits/2-ie 'two', not always same as 'digits/2'
576 \arg \b vm-nytt singular of 'new'
577 \arg \b vm-nya plural of 'new'
578 \arg \b vm-gammalt singular of 'old'
579 \arg \b vm-gamla plural of 'old'
580 \arg \b digits/ett 'one', not always same as 'digits/1'
583 \arg \b vm-ny singular of 'new'
584 \arg \b vm-nye plural of 'new'
585 \arg \b vm-gammel singular of 'old'
586 \arg \b vm-gamle plural of 'old'
594 Italian requires the following additional soundfile:
598 \arg \b vm-nuovi new plural
599 \arg \b vm-vecchio old
600 \arg \b vm-vecchi old plural
602 Chinese (Taiwan) requires the following additional soundfile:
603 \arg \b vm-tong A class-word for call (tong1)
604 \arg \b vm-ri A class-word for day (ri4)
605 \arg \b vm-you You (ni3)
606 \arg \b vm-haveno Have no (mei2 you3)
607 \arg \b vm-have Have (you3)
608 \arg \b vm-listen To listen (yao4 ting1)
611 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
612 spelled among others when you have to change folder. For the above reasons, vm-INBOX
613 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
622 unsigned char iobuf[BASEMAXINLINE];
625 /*! Structure for linked list of users
626 * Use ast_vm_user_destroy() to free one of these structures. */
628 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
629 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
630 char password[80]; /*!< Secret pin code, numbers only */
631 char fullname[80]; /*!< Full name, for directory app */
632 char email[80]; /*!< E-mail address */
633 char *emailsubject; /*!< E-mail subject */
634 char *emailbody; /*!< E-mail body */
635 char pager[80]; /*!< E-mail address to pager (no attachment) */
636 char serveremail[80]; /*!< From: Mail address */
637 char mailcmd[160]; /*!< Configurable mail command */
638 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
639 char zonetag[80]; /*!< Time zone */
640 char locale[20]; /*!< The locale (for presentation of date/time) */
643 char uniqueid[80]; /*!< Unique integer identifier */
645 char attachfmt[20]; /*!< Attachment format */
646 unsigned int flags; /*!< VM_ flags */
648 int minsecs; /*!< Minimum number of seconds per message for this mailbox */
649 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
650 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
651 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
652 int passwordlocation; /*!< Storage location of the password */
654 char imapuser[80]; /*!< IMAP server login */
655 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
656 char imapfolder[64]; /*!< IMAP voicemail folder */
657 char imapvmshareid[80]; /*!< Shared mailbox ID to use rather than the dialed one */
658 int imapversion; /*!< If configuration changes, use the new values */
660 double volgain; /*!< Volume gain for voicemails sent via email */
661 AST_LIST_ENTRY(ast_vm_user) list;
664 /*! Voicemail time zones */
666 AST_LIST_ENTRY(vm_zone) list;
669 char msg_format[512];
672 #define VMSTATE_MAX_MSG_ARRAY 256
674 /*! Voicemail mailbox state */
679 char curdir[PATH_MAX];
680 char vmbox[PATH_MAX];
682 char intro[PATH_MAX];
685 int dh_arraysize; /* used for deleted / heard allocation */
695 int updated; /*!< decremented on each mail check until 1 -allows delay */
696 long msgArray[VMSTATE_MAX_MSG_ARRAY];
697 MAILSTREAM *mailstream;
699 char imapuser[80]; /*!< IMAP server login */
700 char imapfolder[64]; /*!< IMAP voicemail folder */
703 char introfn[PATH_MAX]; /*!< Name of prepended file */
704 unsigned int quota_limit;
705 unsigned int quota_usage;
706 struct vm_state *persist_vms;
711 static char odbc_database[80];
712 static char odbc_table[80];
713 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
714 #define DISPOSE(a,b) remove_file(a,b)
715 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
716 #define EXISTS(a,b,c,d) (message_exists(a,b))
717 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
718 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
719 #define DELETE(a,b,c,d) (delete_file(a,b))
722 #define DISPOSE(a,b) (imap_remove_file(a,b))
723 #define STORE(a,b,c,d,e,f,g,h,i,j) (imap_store_file(a,b,c,d,e,f,g,h,i,j))
724 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
725 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
726 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
727 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
728 #define DELETE(a,b,c,d) (vm_imap_delete(a,b,d))
730 #define RETRIEVE(a,b,c,d)
732 #define STORE(a,b,c,d,e,f,g,h,i,j)
733 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
734 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
735 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
736 #define DELETE(a,b,c,d) (vm_delete(c))
740 static char VM_SPOOL_DIR[PATH_MAX];
742 static char ext_pass_cmd[128];
743 static char ext_pass_check_cmd[128];
747 #define PWDCHANGE_INTERNAL (1 << 1)
748 #define PWDCHANGE_EXTERNAL (1 << 2)
749 static int pwdchange = PWDCHANGE_INTERNAL;
752 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
755 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
757 # define tdesc "Comedian Mail (Voicemail System)"
761 static char userscontext[AST_MAX_EXTENSION] = "default";
763 static char *addesc = "Comedian Mail";
765 /* Leave a message */
766 static char *app = "VoiceMail";
768 /* Check mail, control, etc */
769 static char *app2 = "VoiceMailMain";
771 static char *app3 = "MailboxExists";
772 static char *app4 = "VMAuthenticate";
774 static char *sayname_app = "VMSayName";
776 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
777 static AST_LIST_HEAD_STATIC(zones, vm_zone);
778 static char zonetag[80];
779 static char locale[20];
780 static int maxsilence;
782 static int maxdeletedmsg;
783 static int silencethreshold = 128;
784 static char serveremail[80];
785 static char mailcmd[160]; /* Configurable mail cmd */
786 static char externnotify[160];
787 static struct ast_smdi_interface *smdi_iface = NULL;
788 static char vmfmts[80];
789 static double volgain;
790 static int vmminsecs;
791 static int vmmaxsecs;
794 static int maxlogins;
795 static int minpassword;
796 static int passwordlocation;
798 /*! Poll mailboxes for changes since there is something external to
799 * app_voicemail that may change them. */
800 static unsigned int poll_mailboxes;
802 /*! Polling frequency */
803 static unsigned int poll_freq;
804 /*! By default, poll every 30 seconds */
805 #define DEFAULT_POLL_FREQ 30
807 AST_MUTEX_DEFINE_STATIC(poll_lock);
808 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
809 static pthread_t poll_thread = AST_PTHREADT_NULL;
810 static unsigned char poll_thread_run;
812 /*! Subscription to ... MWI event subscriptions */
813 static struct ast_event_sub *mwi_sub_sub;
814 /*! Subscription to ... MWI event un-subscriptions */
815 static struct ast_event_sub *mwi_unsub_sub;
818 * \brief An MWI subscription
820 * This is so we can keep track of which mailboxes are subscribed to.
821 * This way, we know which mailboxes to poll when the pollmailboxes
822 * option is being used.
825 AST_RWLIST_ENTRY(mwi_sub) entry;
833 struct mwi_sub_task {
839 static struct ast_taskprocessor *mwi_subscription_tps;
841 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
843 /* custom audio control prompts for voicemail playback */
844 static char listen_control_forward_key[12];
845 static char listen_control_reverse_key[12];
846 static char listen_control_pause_key[12];
847 static char listen_control_restart_key[12];
848 static char listen_control_stop_key[12];
850 /* custom password sounds */
851 static char vm_password[80] = "vm-password";
852 static char vm_newpassword[80] = "vm-newpassword";
853 static char vm_passchanged[80] = "vm-passchanged";
854 static char vm_reenterpassword[80] = "vm-reenterpassword";
855 static char vm_mismatch[80] = "vm-mismatch";
856 static char vm_invalid_password[80] = "vm-invalid-password";
857 static char vm_pls_try_again[80] = "vm-pls-try-again";
859 static struct ast_flags globalflags = {0};
861 static int saydurationminfo;
863 static char dialcontext[AST_MAX_CONTEXT] = "";
864 static char callcontext[AST_MAX_CONTEXT] = "";
865 static char exitcontext[AST_MAX_CONTEXT] = "";
867 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
870 static char *emailbody = NULL;
871 static char *emailsubject = NULL;
872 static char *pagerbody = NULL;
873 static char *pagersubject = NULL;
874 static char fromstring[100];
875 static char pagerfromstring[100];
876 static char charset[32] = "ISO-8859-1";
878 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
879 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
880 static int adsiver = 1;
881 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
882 static char pagerdateformat[32] = "%A, %B %d, %Y at %r";
884 /* Forward declarations - generic */
885 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
886 static int advanced_options(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msg, int option, signed char record_gain);
887 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
888 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
889 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
890 signed char record_gain, struct vm_state *vms, char *flag);
891 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
892 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
893 static int notify_new_message(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, int msgnum, long duration, char *fmt, char *cidnum, char *cidname, const char *flag);
894 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, const char *fromfolder, char *cidnum, char *cidname, char *attach, char *attach2, char *format, int duration, int attach_user_voicemail, struct ast_channel *chan, const char *category, int imap, const char *flag);
895 static void apply_options(struct ast_vm_user *vmu, const char *options);
896 static int add_email_attachment(FILE *p, struct ast_vm_user *vmu, char *format, char *attach, char *greeting_attachment, char *mailbox, char *bound, char *filename, int last, int msgnum);
897 static int is_valid_dtmf(const char *key);
898 static void read_password_from_file(const char *secretfn, char *password, int passwordlen);
899 static int write_password_to_file(const char *secretfn, const char *password);
901 struct ao2_container *inprocess_container;
909 static int inprocess_hash_fn(const void *obj, const int flags)
911 const struct inprocess *i = obj;
912 return atoi(i->mailbox);
915 static int inprocess_cmp_fn(void *obj, void *arg, int flags)
917 struct inprocess *i = obj, *j = arg;
918 if (strcmp(i->mailbox, j->mailbox)) {
921 return !strcmp(i->context, j->context) ? CMP_MATCH : 0;
924 static int inprocess_count(const char *context, const char *mailbox, int delta)
926 struct inprocess *i, *arg = alloca(sizeof(*arg) + strlen(context) + strlen(mailbox) + 2);
927 arg->context = arg->mailbox + strlen(mailbox) + 1;
928 strcpy(arg->mailbox, mailbox); /* SAFE */
929 strcpy(arg->context, context); /* SAFE */
930 ao2_lock(inprocess_container);
931 if ((i = ao2_find(inprocess_container, arg, 0))) {
932 int ret = ast_atomic_fetchadd_int(&i->count, delta);
933 ao2_unlock(inprocess_container);
938 ast_log(LOG_WARNING, "BUG: ref count decrement on non-existing object???\n");
940 if (!(i = ao2_alloc(sizeof(*i) + strlen(context) + strlen(mailbox) + 2, NULL))) {
941 ao2_unlock(inprocess_container);
944 i->context = i->mailbox + strlen(mailbox) + 1;
945 strcpy(i->mailbox, mailbox); /* SAFE */
946 strcpy(i->context, context); /* SAFE */
948 ao2_link(inprocess_container, i);
949 ao2_unlock(inprocess_container);
954 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
955 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
959 * \brief Strips control and non 7-bit clean characters from input string.
961 * \note To map control and none 7-bit characters to a 7-bit clean characters
962 * please use ast_str_encode_mine().
964 static char *strip_control_and_high(const char *input, char *buf, size_t buflen)
967 for (; *input; input++) {
972 if (bufptr == buf + buflen - 1) {
982 * \brief Sets default voicemail system options to a voicemail user.
984 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
985 * - all the globalflags
986 * - the saydurationminfo
990 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
993 static void populate_defaults(struct ast_vm_user *vmu)
995 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
996 vmu->passwordlocation = passwordlocation;
997 if (saydurationminfo) {
998 vmu->saydurationm = saydurationminfo;
1000 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
1001 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
1002 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
1003 ast_copy_string(vmu->zonetag, zonetag, sizeof(vmu->zonetag));
1004 ast_copy_string(vmu->locale, locale, sizeof(vmu->locale));
1006 vmu->minsecs = vmminsecs;
1009 vmu->maxsecs = vmmaxsecs;
1012 vmu->maxmsg = maxmsg;
1014 if (maxdeletedmsg) {
1015 vmu->maxdeletedmsg = maxdeletedmsg;
1017 vmu->volgain = volgain;
1018 vmu->emailsubject = NULL;
1019 vmu->emailbody = NULL;
1021 ast_copy_string(vmu->imapfolder, imapfolder, sizeof(vmu->imapfolder));
1026 * \brief Sets a a specific property value.
1027 * \param vmu The voicemail user object to work with.
1028 * \param var The name of the property to be set.
1029 * \param value The value to be set to the property.
1031 * The property name must be one of the understood properties. See the source for details.
1033 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
1036 if (!strcasecmp(var, "attach")) {
1037 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
1038 } else if (!strcasecmp(var, "attachfmt")) {
1039 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
1040 } else if (!strcasecmp(var, "serveremail")) {
1041 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
1042 } else if (!strcasecmp(var, "language")) {
1043 ast_copy_string(vmu->language, value, sizeof(vmu->language));
1044 } else if (!strcasecmp(var, "tz")) {
1045 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
1046 } else if (!strcasecmp(var, "locale")) {
1047 ast_copy_string(vmu->locale, value, sizeof(vmu->locale));
1049 } else if (!strcasecmp(var, "imapuser")) {
1050 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
1051 vmu->imapversion = imapversion;
1052 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
1053 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
1054 vmu->imapversion = imapversion;
1055 } else if (!strcasecmp(var, "imapfolder")) {
1056 ast_copy_string(vmu->imapfolder, value, sizeof(vmu->imapfolder));
1057 } else if (!strcasecmp(var, "imapvmshareid")) {
1058 ast_copy_string(vmu->imapvmshareid, value, sizeof(vmu->imapvmshareid));
1059 vmu->imapversion = imapversion;
1061 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
1062 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
1063 } else if (!strcasecmp(var, "saycid")){
1064 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
1065 } else if (!strcasecmp(var, "sendvoicemail")){
1066 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
1067 } else if (!strcasecmp(var, "review")){
1068 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
1069 } else if (!strcasecmp(var, "tempgreetwarn")){
1070 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
1071 } else if (!strcasecmp(var, "messagewrap")){
1072 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
1073 } else if (!strcasecmp(var, "operator")) {
1074 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
1075 } else if (!strcasecmp(var, "envelope")){
1076 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
1077 } else if (!strcasecmp(var, "moveheard")){
1078 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
1079 } else if (!strcasecmp(var, "sayduration")){
1080 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
1081 } else if (!strcasecmp(var, "saydurationm")){
1082 if (sscanf(value, "%30d", &x) == 1) {
1083 vmu->saydurationm = x;
1085 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
1087 } else if (!strcasecmp(var, "forcename")){
1088 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
1089 } else if (!strcasecmp(var, "forcegreetings")){
1090 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
1091 } else if (!strcasecmp(var, "callback")) {
1092 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
1093 } else if (!strcasecmp(var, "dialout")) {
1094 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
1095 } else if (!strcasecmp(var, "exitcontext")) {
1096 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
1097 } else if (!strcasecmp(var, "minsecs")) {
1098 if (sscanf(value, "%30d", &x) == 1 && x >= 0) {
1101 ast_log(LOG_WARNING, "Invalid min message length of %s. Using global value %d\n", value, vmminsecs);
1102 vmu->minsecs = vmminsecs;
1104 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
1105 vmu->maxsecs = atoi(value);
1106 if (vmu->maxsecs <= 0) {
1107 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
1108 vmu->maxsecs = vmmaxsecs;
1110 vmu->maxsecs = atoi(value);
1112 if (!strcasecmp(var, "maxmessage"))
1113 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
1114 } else if (!strcasecmp(var, "maxmsg")) {
1115 vmu->maxmsg = atoi(value);
1116 /* Accept maxmsg=0 (Greetings only voicemail) */
1117 if (vmu->maxmsg < 0) {
1118 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
1119 vmu->maxmsg = MAXMSG;
1120 } else if (vmu->maxmsg > MAXMSGLIMIT) {
1121 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
1122 vmu->maxmsg = MAXMSGLIMIT;
1124 } else if (!strcasecmp(var, "nextaftercmd")) {
1125 ast_set2_flag(vmu, ast_true(value), VM_SKIPAFTERCMD);
1126 } else if (!strcasecmp(var, "backupdeleted")) {
1127 if (sscanf(value, "%30d", &x) == 1)
1128 vmu->maxdeletedmsg = x;
1129 else if (ast_true(value))
1130 vmu->maxdeletedmsg = MAXMSG;
1132 vmu->maxdeletedmsg = 0;
1134 if (vmu->maxdeletedmsg < 0) {
1135 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
1136 vmu->maxdeletedmsg = MAXMSG;
1137 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
1138 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
1139 vmu->maxdeletedmsg = MAXMSGLIMIT;
1141 } else if (!strcasecmp(var, "volgain")) {
1142 sscanf(value, "%30lf", &vmu->volgain);
1143 } else if (!strcasecmp(var, "passwordlocation")) {
1144 if (!strcasecmp(value, "spooldir")) {
1145 vmu->passwordlocation = OPT_PWLOC_SPOOLDIR;
1147 vmu->passwordlocation = OPT_PWLOC_VOICEMAILCONF;
1149 } else if (!strcasecmp(var, "options")) {
1150 apply_options(vmu, value);
1154 static char *vm_check_password_shell(char *command, char *buf, size_t len)
1156 int fds[2], pid = 0;
1158 memset(buf, 0, len);
1161 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
1164 pid = ast_safe_fork(0);
1170 snprintf(buf, len, "FAILURE: Fork failed");
1174 if (read(fds[0], buf, len) < 0) {
1175 ast_log(LOG_WARNING, "read() failed: %s\n", strerror(errno));
1180 AST_DECLARE_APP_ARGS(arg,
1183 char *mycmd = ast_strdupa(command);
1186 dup2(fds[1], STDOUT_FILENO);
1188 ast_close_fds_above_n(STDOUT_FILENO);
1190 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
1192 execv(arg.v[0], arg.v);
1193 printf("FAILURE: %s", strerror(errno));
1201 * \brief Check that password meets minimum required length
1202 * \param vmu The voicemail user to change the password for.
1203 * \param password The password string to check
1205 * \return zero on ok, 1 on not ok.
1207 static int check_password(struct ast_vm_user *vmu, char *password)
1209 /* check minimum length */
1210 if (strlen(password) < minpassword)
1212 if (!ast_strlen_zero(ext_pass_check_cmd)) {
1213 char cmd[255], buf[255];
1215 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
1217 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
1218 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
1219 ast_debug(5, "Result: %s\n", buf);
1220 if (!strncasecmp(buf, "VALID", 5)) {
1221 ast_debug(3, "Passed password check: '%s'\n", buf);
1223 } else if (!strncasecmp(buf, "FAILURE", 7)) {
1224 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
1227 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
1236 * \brief Performs a change of the voicemail passowrd in the realtime engine.
1237 * \param vmu The voicemail user to change the password for.
1238 * \param password The new value to be set to the password for this user.
1240 * This only works if there is a realtime engine configured.
1241 * This is called from the (top level) vm_change_password.
1243 * \return zero on success, -1 on error.
1245 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
1248 if (!strcmp(vmu->password, password)) {
1249 /* No change (but an update would return 0 rows updated, so we opt out here) */
1253 if (strlen(password) > 10) {
1254 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
1256 if (ast_update2_realtime("voicemail", "context", vmu->context, "mailbox", vmu->mailbox, SENTINEL, "password", password, SENTINEL) > 0) {
1257 ast_copy_string(vmu->password, password, sizeof(vmu->password));
1264 * \brief Destructively Parse options and apply.
1266 static void apply_options(struct ast_vm_user *vmu, const char *options)
1271 stringp = ast_strdupa(options);
1272 while ((s = strsep(&stringp, "|"))) {
1274 if ((var = strsep(&value, "=")) && value) {
1275 apply_option(vmu, var, value);
1281 * \brief Loads the options specific to a voicemail user.
1283 * This is called when a vm_user structure is being set up, such as from load_options.
1285 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
1287 for (; var; var = var->next) {
1288 if (!strcasecmp(var->name, "vmsecret")) {
1289 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1290 } else if (!strcasecmp(var->name, "secret") || !strcasecmp(var->name, "password")) { /* don't overwrite vmsecret if it exists */
1291 if (ast_strlen_zero(retval->password))
1292 ast_copy_string(retval->password, var->value, sizeof(retval->password));
1293 } else if (!strcasecmp(var->name, "uniqueid")) {
1294 ast_copy_string(retval->uniqueid, var->value, sizeof(retval->uniqueid));
1295 } else if (!strcasecmp(var->name, "pager")) {
1296 ast_copy_string(retval->pager, var->value, sizeof(retval->pager));
1297 } else if (!strcasecmp(var->name, "email")) {
1298 ast_copy_string(retval->email, var->value, sizeof(retval->email));
1299 } else if (!strcasecmp(var->name, "fullname")) {
1300 ast_copy_string(retval->fullname, var->value, sizeof(retval->fullname));
1301 } else if (!strcasecmp(var->name, "context")) {
1302 ast_copy_string(retval->context, var->value, sizeof(retval->context));
1303 } else if (!strcasecmp(var->name, "emailsubject")) {
1304 retval->emailsubject = ast_strdup(var->value);
1305 } else if (!strcasecmp(var->name, "emailbody")) {
1306 retval->emailbody = ast_strdup(var->value);
1308 } else if (!strcasecmp(var->name, "imapuser")) {
1309 ast_copy_string(retval->imapuser, var->value, sizeof(retval->imapuser));
1310 retval->imapversion = imapversion;
1311 } else if (!strcasecmp(var->name, "imappassword") || !strcasecmp(var->name, "imapsecret")) {
1312 ast_copy_string(retval->imappassword, var->value, sizeof(retval->imappassword));
1313 retval->imapversion = imapversion;
1314 } else if (!strcasecmp(var->name, "imapfolder")) {
1315 ast_copy_string(retval->imapfolder, var->value, sizeof(retval->imapfolder));
1316 } else if (!strcasecmp(var->name, "imapvmshareid")) {
1317 ast_copy_string(retval->imapvmshareid, var->value, sizeof(retval->imapvmshareid));
1318 retval->imapversion = imapversion;
1321 apply_option(retval, var->name, var->value);
1326 * \brief Determines if a DTMF key entered is valid.
1327 * \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.
1329 * Tests the character entered against the set of valid DTMF characters.
1330 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1332 static int is_valid_dtmf(const char *key)
1335 char *local_key = ast_strdupa(key);
1337 for (i = 0; i < strlen(key); ++i) {
1338 if (!strchr(VALID_DTMF, *local_key)) {
1339 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1348 * \brief Finds a voicemail user from the realtime engine.
1353 * 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.
1355 * \return The ast_vm_user structure for the user that was found.
1357 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1359 struct ast_variable *var;
1360 struct ast_vm_user *retval;
1362 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1364 ast_set_flag(retval, VM_ALLOCED);
1366 memset(retval, 0, sizeof(*retval));
1368 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1369 populate_defaults(retval);
1370 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1371 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1373 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1375 apply_options_full(retval, var);
1376 ast_variables_destroy(var);
1387 * \brief Finds a voicemail user from the users file or the realtime engine.
1392 * \return The ast_vm_user structure for the user that was found.
1394 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1396 /* This function could be made to generate one from a database, too */
1397 struct ast_vm_user *vmu = NULL, *cur;
1398 AST_LIST_LOCK(&users);
1400 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1401 context = "default";
1403 AST_LIST_TRAVERSE(&users, cur, list) {
1405 if (cur->imapversion != imapversion) {
1409 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1411 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1415 /* Make a copy, so that on a reload, we have no race */
1416 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1417 memcpy(vmu, cur, sizeof(*vmu));
1418 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1419 AST_LIST_NEXT(vmu, list) = NULL;
1422 vmu = find_user_realtime(ivm, context, mailbox);
1423 AST_LIST_UNLOCK(&users);
1428 * \brief Resets a user password to a specified password.
1433 * This does the actual change password work, called by the vm_change_password() function.
1435 * \return zero on success, -1 on error.
1437 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1439 /* This function could be made to generate one from a database, too */
1440 struct ast_vm_user *cur;
1442 AST_LIST_LOCK(&users);
1443 AST_LIST_TRAVERSE(&users, cur, list) {
1444 if ((!context || !strcasecmp(context, cur->context)) &&
1445 (!strcasecmp(mailbox, cur->mailbox)))
1449 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1452 AST_LIST_UNLOCK(&users);
1457 * \brief The handler for the change password option.
1458 * \param vmu The voicemail user to work with.
1459 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1460 * 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.
1461 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1463 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1465 struct ast_config *cfg = NULL;
1466 struct ast_variable *var = NULL;
1467 struct ast_category *cat = NULL;
1468 char *category = NULL, *value = NULL, *new = NULL;
1469 const char *tmp = NULL;
1470 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1471 char secretfn[PATH_MAX] = "";
1474 if (!change_password_realtime(vmu, newpassword))
1477 /* check if we should store the secret in the spool directory next to the messages */
1478 switch (vmu->passwordlocation) {
1479 case OPT_PWLOC_SPOOLDIR:
1480 snprintf(secretfn, sizeof(secretfn), "%s%s/%s/secret.conf", VM_SPOOL_DIR, vmu->context, vmu->mailbox);
1481 if (write_password_to_file(secretfn, newpassword) == 0) {
1482 ast_verb(4, "Writing voicemail password to file %s succeeded\n", secretfn);
1483 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1484 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1487 ast_verb(4, "Writing voicemail password to file %s failed, falling back to config file\n", secretfn);
1490 case OPT_PWLOC_VOICEMAILCONF:
1491 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1492 while ((category = ast_category_browse(cfg, category))) {
1493 if (!strcasecmp(category, vmu->context)) {
1494 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1495 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1498 value = strstr(tmp, ",");
1500 new = alloca(strlen(newpassword)+1);
1501 sprintf(new, "%s", newpassword);
1503 new = alloca((strlen(value) + strlen(newpassword) + 1));
1504 sprintf(new, "%s%s", newpassword, value);
1506 if (!(cat = ast_category_get(cfg, category))) {
1507 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1510 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1514 /* save the results */
1516 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1517 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1518 ast_config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1523 case OPT_PWLOC_USERSCONF:
1524 /* check users.conf and update the password stored for the mailbox */
1525 /* if no vmsecret entry exists create one. */
1526 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1527 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1528 for (category = ast_category_browse(cfg, NULL); category; category = ast_category_browse(cfg, category)) {
1529 ast_debug(4, "users.conf: %s\n", category);
1530 if (!strcasecmp(category, vmu->mailbox)) {
1531 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1532 ast_debug(3, "looks like we need to make vmsecret!\n");
1533 var = ast_variable_new("vmsecret", newpassword, "");
1537 new = alloca(strlen(newpassword) + 1);
1538 sprintf(new, "%s", newpassword);
1539 if (!(cat = ast_category_get(cfg, category))) {
1540 ast_debug(4, "failed to get category!\n");
1545 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1547 ast_variable_append(cat, var);
1553 /* save the results and clean things up */
1555 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1556 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1557 ast_config_text_file_save("users.conf", cfg, "AppVoicemail");
1563 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1566 snprintf(buf, sizeof(buf), "%s %s %s %s", ext_pass_cmd, vmu->context, vmu->mailbox, newpassword);
1567 if (!ast_safe_system(buf)) {
1568 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1569 /* Reset the password in memory, too */
1570 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1575 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1576 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1577 * \param len The length of the path string that was written out.
1582 * The path is constructed as
1583 * VM_SPOOL_DIRcontext/ext/folder
1585 * \return zero on success, -1 on error.
1587 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1589 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1593 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1594 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1595 * \param len The length of the path string that was written out.
1599 * The path is constructed as
1600 * VM_SPOOL_DIRcontext/ext/folder
1602 * \return zero on success, -1 on error.
1604 static int make_file(char *dest, const int len, const char *dir, const int num)
1606 return snprintf(dest, len, "%s/msg%04d", dir, num);
1609 /* same as mkstemp, but return a FILE * */
1610 static FILE *vm_mkftemp(char *template)
1613 int pfd = mkstemp(template);
1614 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1616 p = fdopen(pfd, "w+");
1625 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1626 * \param dest String. base directory.
1627 * \param len Length of dest.
1628 * \param context String. Ignored if is null or empty string.
1629 * \param ext String. Ignored if is null or empty string.
1630 * \param folder String. Ignored if is null or empty string.
1631 * \return -1 on failure, 0 on success.
1633 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1635 mode_t mode = VOICEMAIL_DIR_MODE;
1638 make_dir(dest, len, context, ext, folder);
1639 if ((res = ast_mkdir(dest, mode))) {
1640 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1646 static const char * const mailbox_folders[] = {
1665 static const char *mbox(struct ast_vm_user *vmu, int id)
1668 if (vmu && id == 0) {
1669 return vmu->imapfolder;
1672 return (id >= 0 && id < ARRAY_LEN(mailbox_folders)) ? mailbox_folders[id] : "Unknown";
1675 static int get_folder_by_name(const char *name)
1679 for (i = 0; i < ARRAY_LEN(mailbox_folders); i++) {
1680 if (strcasecmp(name, mailbox_folders[i]) == 0) {
1688 static void free_user(struct ast_vm_user *vmu)
1690 if (ast_test_flag(vmu, VM_ALLOCED)) {
1691 if (vmu->emailbody != NULL) {
1692 ast_free(vmu->emailbody);
1693 vmu->emailbody = NULL;
1695 if (vmu->emailsubject != NULL) {
1696 ast_free(vmu->emailsubject);
1697 vmu->emailsubject = NULL;
1703 static int vm_allocate_dh(struct vm_state *vms, struct ast_vm_user *vmu, int count_msg) {
1705 int arraysize = (vmu->maxmsg > count_msg ? vmu->maxmsg : count_msg);
1706 if (!vms->dh_arraysize) {
1707 /* initial allocation */
1708 if (!(vms->deleted = ast_calloc(arraysize, sizeof(int)))) {
1711 if (!(vms->heard = ast_calloc(arraysize, sizeof(int)))) {
1714 vms->dh_arraysize = arraysize;
1715 } else if (vms->dh_arraysize < arraysize) {
1716 if (!(vms->deleted = ast_realloc(vms->deleted, arraysize * sizeof(int)))) {
1719 if (!(vms->heard = ast_realloc(vms->heard, arraysize * sizeof(int)))) {
1722 memset(vms->deleted, 0, arraysize * sizeof(int));
1723 memset(vms->heard, 0, arraysize * sizeof(int));
1724 vms->dh_arraysize = arraysize;
1730 /* All IMAP-specific functions should go in this block. This
1731 * keeps them from being spread out all over the code */
1733 static void vm_imap_delete(char *file, int msgnum, struct ast_vm_user *vmu)
1736 struct vm_state *vms;
1737 unsigned long messageNum;
1739 /* If greetings aren't stored in IMAP, just delete the file */
1740 if (msgnum < 0 && !imapgreetings) {
1741 ast_filedelete(file, NULL);
1745 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1746 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);
1750 /* find real message number based on msgnum */
1751 /* this may be an index into vms->msgArray based on the msgnum. */
1752 messageNum = vms->msgArray[msgnum];
1753 if (messageNum == 0) {
1754 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n", msgnum, messageNum);
1757 if (option_debug > 2)
1758 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n", msgnum, messageNum);
1759 /* delete message */
1760 snprintf (arg, sizeof(arg), "%lu", messageNum);
1761 ast_mutex_lock(&vms->lock);
1762 mail_setflag (vms->mailstream, arg, "\\DELETED");
1763 mail_expunge(vms->mailstream);
1764 ast_mutex_unlock(&vms->lock);
1767 static int imap_retrieve_greeting(const char *dir, const int msgnum, struct ast_vm_user *vmu)
1769 struct vm_state *vms_p;
1770 char *file, *filename;
1775 /* This function is only used for retrieval of IMAP greetings
1776 * regular messages are not retrieved this way, nor are greetings
1777 * if they are stored locally*/
1778 if (msgnum > -1 || !imapgreetings) {
1781 file = strrchr(ast_strdupa(dir), '/');
1785 ast_debug (1, "Failed to procure file name from directory passed.\n");
1790 /* check if someone is accessing this box right now... */
1791 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) &&
1792 !(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1793 /* Unlike when retrieving a message, it is reasonable not to be able to find a
1794 * vm_state for a mailbox when trying to retrieve a greeting. Just create one,
1795 * that's all we need to do.
1797 if (!(vms_p = create_vm_state_from_user(vmu))) {
1798 ast_log(LOG_NOTICE, "Unable to create vm_state object!\n");
1803 /* Greetings will never have a prepended message */
1804 *vms_p->introfn = '\0';
1806 ast_mutex_lock(&vms_p->lock);
1807 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1808 if (!vms_p->mailstream) {
1809 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1810 ast_mutex_unlock(&vms_p->lock);
1814 /*XXX Yuck, this could probably be done a lot better */
1815 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1816 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1817 /* We have the body, now we extract the file name of the first attachment. */
1818 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1819 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1821 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1822 ast_mutex_unlock(&vms_p->lock);
1825 filename = strsep(&attachment, ".");
1826 if (!strcmp(filename, file)) {
1827 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1828 vms_p->msgArray[vms_p->curmsg] = i + 1;
1829 save_body(body, vms_p, "2", attachment, 0);
1830 ast_mutex_unlock(&vms_p->lock);
1834 ast_mutex_unlock(&vms_p->lock);
1839 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1842 char *header_content;
1843 char *attachedfilefmt;
1845 struct vm_state *vms;
1846 char text_file[PATH_MAX];
1847 FILE *text_file_ptr;
1849 struct ast_vm_user *vmu;
1851 if (!(vmu = find_user(NULL, context, mailbox))) {
1852 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1857 if (imapgreetings) {
1858 res = imap_retrieve_greeting(dir, msgnum, vmu);
1866 /* Before anything can happen, we need a vm_state so that we can
1867 * actually access the imap server through the vms->mailstream
1869 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1870 /* This should not happen. If it does, then I guess we'd
1871 * need to create the vm_state, extract which mailbox to
1872 * open, and then set up the msgArray so that the correct
1873 * IMAP message could be accessed. If I have seen correctly
1874 * though, the vms should be obtainable from the vmstates list
1875 * and should have its msgArray properly set up.
1877 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1882 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1883 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1885 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1886 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1891 if (option_debug > 2)
1892 ast_log(LOG_DEBUG, "Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1893 if (vms->msgArray[msgnum] == 0) {
1894 ast_log(LOG_WARNING, "Trying to access unknown message\n");
1899 /* This will only work for new messages... */
1900 ast_mutex_lock(&vms->lock);
1901 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1902 ast_mutex_unlock(&vms->lock);
1903 /* empty string means no valid header */
1904 if (ast_strlen_zero(header_content)) {
1905 ast_log(LOG_ERROR, "Could not fetch header for message number %ld\n", vms->msgArray[msgnum]);
1910 ast_mutex_lock(&vms->lock);
1911 mail_fetchstructure(vms->mailstream, vms->msgArray[msgnum], &body);
1912 ast_mutex_unlock(&vms->lock);
1914 /* We have the body, now we extract the file name of the first attachment. */
1915 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1916 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1918 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1923 /* Find the format of the attached file */
1925 strsep(&attachedfilefmt, ".");
1926 if (!attachedfilefmt) {
1927 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1932 save_body(body, vms, "2", attachedfilefmt, 0);
1933 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1934 *vms->introfn = '\0';
1937 /* Get info from headers!! */
1938 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1940 if (!(text_file_ptr = fopen(text_file, "w"))) {
1941 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1944 fprintf(text_file_ptr, "%s\n", "[message]");
1946 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1947 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1948 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1949 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1950 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1951 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1952 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1953 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1954 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1955 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1956 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1957 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1958 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1959 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1960 fclose(text_file_ptr);
1967 static int folder_int(const char *folder)
1969 /*assume a NULL folder means INBOX*/
1973 if (!strcasecmp(folder, imapfolder)) {
1975 } else if (!strcasecmp(folder, "Old")) {
1977 } else if (!strcasecmp(folder, "Work")) {
1979 } else if (!strcasecmp(folder, "Family")) {
1981 } else if (!strcasecmp(folder, "Friends")) {
1983 } else if (!strcasecmp(folder, "Cust1")) {
1985 } else if (!strcasecmp(folder, "Cust2")) {
1987 } else if (!strcasecmp(folder, "Cust3")) {
1989 } else if (!strcasecmp(folder, "Cust4")) {
1991 } else if (!strcasecmp(folder, "Cust5")) {
1993 } else if (!strcasecmp(folder, "Urgent")) {
1995 } else { /*assume they meant INBOX if folder is not found otherwise*/
2000 static int __messagecount(const char *context, const char *mailbox, const char *folder)
2005 struct ast_vm_user *vmu, vmus;
2006 struct vm_state *vms_p;
2008 int fold = folder_int(folder);
2011 /* If URGENT, then look at INBOX */
2017 if (ast_strlen_zero(mailbox))
2020 /* We have to get the user before we can open the stream! */
2021 vmu = find_user(&vmus, context, mailbox);
2023 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
2026 /* No IMAP account available */
2027 if (vmu->imapuser[0] == '\0') {
2028 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2033 /* No IMAP account available */
2034 if (vmu->imapuser[0] == '\0') {
2035 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
2040 /* check if someone is accessing this box right now... */
2041 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 1);
2043 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
2046 ast_debug(3, "Returning before search - user is logged in\n");
2047 if (fold == 0) { /* INBOX */
2048 return urgent ? vms_p->urgentmessages : vms_p->newmessages;
2050 if (fold == 1) { /* Old messages */
2051 return vms_p->oldmessages;
2055 /* add one if not there... */
2056 vms_p = get_vm_state_by_imapuser(vmu->imapuser, 0);
2058 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
2062 vms_p = create_vm_state_from_user(vmu);
2064 ret = init_mailstream(vms_p, fold);
2065 if (!vms_p->mailstream) {
2066 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
2070 ast_mutex_lock(&vms_p->lock);
2071 pgm = mail_newsearchpgm ();
2072 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)(!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : mailbox));
2073 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", (char *) S_OR(context, "default"));
2075 if (fold != OLD_FOLDER) {
2079 /* In the special case where fold is 1 (old messages) we have to do things a bit
2080 * differently. Old messages are stored in the INBOX but are marked as "seen"
2086 /* look for urgent messages */
2087 if (fold == NEW_FOLDER) {
2099 vms_p->vmArrayIndex = 0;
2100 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
2101 if (fold == 0 && urgent == 0)
2102 vms_p->newmessages = vms_p->vmArrayIndex;
2104 vms_p->oldmessages = vms_p->vmArrayIndex;
2105 if (fold == 0 && urgent == 1)
2106 vms_p->urgentmessages = vms_p->vmArrayIndex;
2107 /*Freeing the searchpgm also frees the searchhdr*/
2108 mail_free_searchpgm(&pgm);
2109 ast_mutex_unlock(&vms_p->lock);
2111 return vms_p->vmArrayIndex;
2113 ast_mutex_lock(&vms_p->lock);
2114 mail_ping(vms_p->mailstream);
2115 ast_mutex_unlock(&vms_p->lock);
2120 static int imap_check_limits(struct ast_channel *chan, struct vm_state *vms, struct ast_vm_user *vmu, int msgnum)
2122 /* Check if mailbox is full */
2123 check_quota(vms, vmu->imapfolder);
2124 if (vms->quota_limit && vms->quota_usage >= vms->quota_limit) {
2125 ast_debug(1, "*** QUOTA EXCEEDED!! %u >= %u\n", vms->quota_usage, vms->quota_limit);
2126 ast_play_and_wait(chan, "vm-mailboxfull");
2130 /* Check if we have exceeded maxmsg */
2131 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));
2132 if (msgnum >= vmu->maxmsg - inprocess_count(vmu->mailbox, vmu->context, +1)) {
2133 ast_log(LOG_WARNING, "Unable to leave message since we will exceed the maximum number of messages allowed (%u >= %u)\n", msgnum, vmu->maxmsg);
2134 ast_play_and_wait(chan, "vm-mailboxfull");
2135 pbx_builtin_setvar_helper(chan, "VMSTATUS", "FAILED");
2143 * \brief Gets the number of messages that exist in a mailbox folder.
2148 * This method is used when IMAP backend is used.
2149 * \return The number of messages in this mailbox folder (zero or more).
2151 static int messagecount(const char *context, const char *mailbox, const char *folder)
2153 if (ast_strlen_zero(folder) || !strcmp(folder, "INBOX")) {
2154 return __messagecount(context, mailbox, "INBOX") + __messagecount(context, mailbox, "Urgent");
2156 return __messagecount(context, mailbox, folder);
2160 static int imap_store_file(const char *dir, const char *mailboxuser, const char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
2162 char *myserveremail = serveremail;
2164 char introfn[PATH_MAX];
2168 char tmp[80] = "/tmp/astmail-XXXXXX";
2173 int ret; /* for better error checking */
2174 char *imap_flags = NIL;
2175 int msgcount = (messagecount(vmu->context, vmu->mailbox, "INBOX") + messagecount(vmu->context, vmu->mailbox, "Old"));
2177 /* Back out early if this is a greeting and we don't want to store greetings in IMAP */
2178 if (msgnum < 0 && !imapgreetings) {
2182 if (imap_check_limits(chan, vms, vmu, msgcount)) {
2186 /* Set urgent flag for IMAP message */
2187 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
2188 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
2189 imap_flags = "\\FLAGGED";
2192 /* Attach only the first format */
2193 fmt = ast_strdupa(fmt);
2195 strsep(&stringp, "|");
2197 if (!ast_strlen_zero(vmu->serveremail))
2198 myserveremail = vmu->serveremail;
2201 make_file(fn, sizeof(fn), dir, msgnum);
2203 ast_copy_string (fn, dir, sizeof(fn));
2205 snprintf(introfn, sizeof(introfn), "%sintro", fn);
2206 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
2210 if (ast_strlen_zero(vmu->email)) {
2211 /* We need the vmu->email to be set when we call make_email_file, but
2212 * if we keep it set, a duplicate e-mail will be created. So at the end
2213 * of this function, we will revert back to an empty string if tempcopy
2216 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
2220 if (!strcmp(fmt, "wav49"))
2222 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
2224 /* Make a temporary file instead of piping directly to sendmail, in case the mail
2226 if (!(p = vm_mkftemp(tmp))) {
2227 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
2229 *(vmu->email) = '\0';
2233 if (msgnum < 0 && imapgreetings) {
2234 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
2235 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
2238 imap_delete_old_greeting(fn, vms);
2241 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, "INBOX",
2242 S_COR(chan->caller.id.number.valid, chan->caller.id.number.str, NULL),
2243 S_COR(chan->caller.id.name.valid, chan->caller.id.name.str, NULL),
2244 fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
2245 /* read mail file to memory */
2248 if (!(buf = ast_malloc(len + 1))) {
2249 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
2252 *(vmu->email) = '\0';
2255 if (fread(buf, len, 1, p) < len) {
2257 ast_log(LOG_ERROR, "Short read while reading in mail file.\n");
2261 ((char *) buf)[len] = '\0';
2262 INIT(&str, mail_string, buf, len);
2263 ret = init_mailstream(vms, NEW_FOLDER);
2265 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
2266 ast_mutex_lock(&vms->lock);
2267 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
2268 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
2269 ast_mutex_unlock(&vms->lock);
2274 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n", mailbox);
2280 ast_debug(3, "%s stored\n", fn);
2283 *(vmu->email) = '\0';
2284 inprocess_count(vmu->mailbox, vmu->context, -1);
2290 * \brief Gets the number of messages that exist in the inbox folder.
2291 * \param mailbox_context
2292 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
2293 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
2294 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
2296 * This method is used when IMAP backend is used.
2297 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
2299 * \return zero on success, -1 on error.
2302 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
2304 char tmp[PATH_MAX] = "";
2316 ast_debug(3, "Mailbox is set to %s\n", mailbox_context);
2317 /* If no mailbox, return immediately */
2318 if (ast_strlen_zero(mailbox_context))
2321 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2322 context = strchr(tmp, '@');
2323 if (strchr(mailbox_context, ',')) {
2324 int tmpnew, tmpold, tmpurgent;
2325 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
2327 while ((cur = strsep(&mb, ", "))) {
2328 if (!ast_strlen_zero(cur)) {
2329 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
2337 *urgentmsgs += tmpurgent;
2348 context = "default";
2349 mailboxnc = (char *) mailbox_context;
2353 struct ast_vm_user *vmu = find_user(NULL, context, mailboxnc);
2355 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailboxnc, context);
2358 if ((*newmsgs = __messagecount(context, mailboxnc, vmu->imapfolder)) < 0) {
2363 if ((*oldmsgs = __messagecount(context, mailboxnc, "Old")) < 0) {
2368 if ((*urgentmsgs = __messagecount(context, mailboxnc, "Urgent")) < 0) {
2376 * \brief Determines if the given folder has messages.
2377 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
2378 * \param folder the folder to look in
2380 * This function is used when the mailbox is stored in an IMAP back end.
2381 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
2382 * \return 1 if the folder has one or more messages. zero otherwise.
2385 static int has_voicemail(const char *mailbox, const char *folder)
2387 char tmp[256], *tmp2, *box, *context;
2388 ast_copy_string(tmp, mailbox, sizeof(tmp));
2390 if (strchr(tmp2, ',') || strchr(tmp2, '&')) {
2391 while ((box = strsep(&tmp2, ",&"))) {
2392 if (!ast_strlen_zero(box)) {
2393 if (has_voicemail(box, folder)) {
2399 if ((context = strchr(tmp, '@'))) {
2402 context = "default";
2404 return __messagecount(context, tmp, folder) ? 1 : 0;
2408 * \brief Copies a message from one mailbox to another.
2418 * This works with IMAP storage based mailboxes.
2420 * \return zero on success, -1 on error.
2422 static int copy_message(struct ast_channel *chan, struct ast_vm_user *vmu, int imbox, int msgnum, long duration, struct ast_vm_user *recip, char *fmt, char *dir, char *flag)
2424 struct vm_state *sendvms = NULL, *destvms = NULL;
2425 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
2426 if (msgnum >= recip->maxmsg) {
2427 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
2430 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
2431 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
2434 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
2435 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
2438 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
2439 ast_mutex_lock(&sendvms->lock);
2440 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(vmu, imbox)) == T)) {
2441 ast_mutex_unlock(&sendvms->lock);
2444 ast_mutex_unlock(&sendvms->lock);
2445 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
2449 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
2451 char tmp[256], *t = tmp;
2452 size_t left = sizeof(tmp);
2454 if (box == OLD_FOLDER) {
2455 ast_copy_string(vms->curbox, mbox(NULL, NEW_FOLDER), sizeof(vms->curbox));
2457 ast_copy_string(vms->curbox, mbox(NULL, box), sizeof(vms->curbox));
2460 if (box == NEW_FOLDER) {
2461 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
2463 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(NULL, box));
2466 /* Build up server information */
2467 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
2469 /* Add authentication user if present */
2470 if (!ast_strlen_zero(authuser))
2471 ast_build_string(&t, &left, "/authuser=%s", authuser);
2473 /* Add flags if present */
2474 if (!ast_strlen_zero(imapflags))
2475 ast_build_string(&t, &left, "/%s", imapflags);
2477 /* End with username */
2479 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
2481 ast_build_string(&t, &left, "/user=%s/novalidate-cert}", vms->imapuser);
2483 if (box == NEW_FOLDER || box == OLD_FOLDER)
2484 snprintf(spec, len, "%s%s", tmp, use_folder? vms->imapfolder: "INBOX");
2485 else if (box == GREETINGS_FOLDER)
2486 snprintf(spec, len, "%s%s", tmp, greetingfolder);
2487 else { /* Other folders such as Friends, Family, etc... */
2488 if (!ast_strlen_zero(imapparentfolder)) {
2489 /* imapparentfolder would typically be set to INBOX */
2490 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(NULL, box));
2492 snprintf(spec, len, "%s%s", tmp, mbox(NULL, box));
2497 static int init_mailstream(struct vm_state *vms, int box)
2499 MAILSTREAM *stream = NIL;
2504 ast_log(LOG_ERROR, "vm_state is NULL!\n");
2507 if (option_debug > 2)
2508 ast_log(LOG_DEBUG, "vm_state user is:%s\n", vms->imapuser);
2509 if (vms->mailstream == NIL || !vms->mailstream) {
2511 ast_log(LOG_DEBUG, "mailstream not set.\n");
2513 stream = vms->mailstream;
2515 /* debug = T; user wants protocol telemetry? */
2516 debug = NIL; /* NO protocol telemetry? */
2518 if (delimiter == '\0') { /* did not probe the server yet */
2520 #ifdef USE_SYSTEM_IMAP
2521 #include <imap/linkage.c>
2522 #elif defined(USE_SYSTEM_CCLIENT)
2523 #include <c-client/linkage.c>
2525 #include "linkage.c"
2527 /* Connect to INBOX first to get folders delimiter */
2528 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2529 ast_mutex_lock(&vms->lock);
2530 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2531 ast_mutex_unlock(&vms->lock);
2532 if (stream == NIL) {
2533 ast_log(LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2536 get_mailbox_delimiter(stream);
2537 /* update delimiter in imapfolder */
2538 for (cp = vms->imapfolder; *cp; cp++)
2542 /* Now connect to the target folder */
2543 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2544 if (option_debug > 2)
2545 ast_log(LOG_DEBUG, "Before mail_open, server: %s, box:%d\n", tmp, box);
2546 ast_mutex_lock(&vms->lock);
2547 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2548 ast_mutex_unlock(&vms->lock);
2549 if (vms->mailstream == NIL) {
2556 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2560 int ret, urgent = 0;
2562 /* If Urgent, then look at INBOX */
2568 ast_copy_string(vms->imapuser, vmu->imapuser, sizeof(vms->imapuser));
2569 ast_copy_string(vms->imapfolder, vmu->imapfolder, sizeof(vms->imapfolder));
2570 vms->imapversion = vmu->imapversion;
2571 ast_debug(3, "Before init_mailstream, user is %s\n", vmu->imapuser);
2573 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2574 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2578 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2582 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(vmu, box));
2583 check_quota(vms, (char *) mbox(vmu, box));
2586 ast_mutex_lock(&vms->lock);
2587 pgm = mail_newsearchpgm();
2589 /* Check IMAP folder for Asterisk messages only... */
2590 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", (!ast_strlen_zero(vmu->imapvmshareid) ? vmu->imapvmshareid : vmu->mailbox));
2591 hdr->next = mail_newsearchheader("X-Asterisk-VM-Context", vmu->context);
2596 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2597 if (box == NEW_FOLDER && urgent == 1) {
2602 } else if (box == NEW_FOLDER && urgent == 0) {
2607 } else if (box == OLD_FOLDER) {
2612 ast_debug(3, "Before mail_search_full, user is %s\n", vmu->imapuser);
2614 vms->vmArrayIndex = 0;
2615 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2616 vms->lastmsg = vms->vmArrayIndex - 1;
2617 mail_free_searchpgm(&pgm);
2618 /* Since IMAP storage actually stores both old and new messages in the same IMAP folder,
2619 * ensure to allocate enough space to account for all of them. Warn if old messages
2620 * have not been checked first as that is required.
2622 if (box == 0 && !vms->dh_arraysize) {
2623 ast_log(LOG_WARNING, "The code expects the old messages to be checked first, fix the code.\n");
2625 if (vm_allocate_dh(vms, vmu, box == 0 ? vms->vmArrayIndex + vms->oldmessages : vms->lastmsg)) {
2626 ast_mutex_unlock(&vms->lock);
2630 ast_mutex_unlock(&vms->lock);
2634 static void write_file(char *filename, char *buffer, unsigned long len)
2638 output = fopen (filename, "w");
2639 if (fwrite(buffer, len, 1, output) != 1) {
2640 if (ferror(output)) {
2641 ast_log(LOG_ERROR, "Short write while writing e-mail body: %s.\n", strerror(errno));
2647 static void update_messages_by_imapuser(const char *user, unsigned long number)
2649 struct vm_state *vms = get_vm_state_by_imapuser(user, 1);
2651 if (!vms && !(vms = get_vm_state_by_imapuser(user, 0))) {
2655 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vms->vmArrayIndex, vms->interactive);
2656 vms->msgArray[vms->vmArrayIndex++] = number;
2659 void mm_searched(MAILSTREAM *stream, unsigned long number)
2661 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2663 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2666 update_messages_by_imapuser(user, number);
2669 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2671 struct ast_variable *var;
2672 struct ast_vm_user *vmu;
2674 vmu = ast_calloc(1, sizeof *vmu);
2677 ast_set_flag(vmu, VM_ALLOCED);
2678 populate_defaults(vmu);
2680 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2682 apply_options_full(vmu, var);
2683 ast_variables_destroy(var);
2691 /* Interfaces to C-client */
2693 void mm_exists(MAILSTREAM * stream, unsigned long number)
2695 /* mail_ping will callback here if new mail! */
2696 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2697 if (number == 0) return;
2702 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2704 /* mail_ping will callback here if expunged mail! */
2705 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2706 if (number == 0) return;
2711 void mm_flags(MAILSTREAM * stream, unsigned long number)
2713 /* mail_ping will callback here if read mail! */
2714 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2715 if (number == 0) return;
2720 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2722 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2723 mm_log (string, errflg);
2727 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2729 if (delimiter == '\0') {
2733 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2734 if (attributes & LATT_NOINFERIORS)
2735 ast_debug(5, "no inferiors\n");
2736 if (attributes & LATT_NOSELECT)
2737 ast_debug(5, "no select\n");
2738 if (attributes & LATT_MARKED)
2739 ast_debug(5, "marked\n");
2740 if (attributes & LATT_UNMARKED)
2741 ast_debug(5, "unmarked\n");
2745 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2747 ast_debug(5, "Delimiter set to %c and mailbox %s\n", delim, mailbox);
2748 if (attributes & LATT_NOINFERIORS)
2749 ast_debug(5, "no inferiors\n");
2750 if (attributes & LATT_NOSELECT)
2751 ast_debug(5, "no select\n");
2752 if (attributes & LATT_MARKED)
2753 ast_debug(5, "marked\n");
2754 if (attributes & LATT_UNMARKED)
2755 ast_debug(5, "unmarked\n");
2759 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2761 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2762 if (status->flags & SA_MESSAGES)
2763 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2764 if (status->flags & SA_RECENT)
2765 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2766 if (status->flags & SA_UNSEEN)
2767 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2768 if (status->flags & SA_UIDVALIDITY)
2769 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2770 if (status->flags & SA_UIDNEXT)
2771 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2772 ast_log(AST_LOG_NOTICE, "\n");
2776 void mm_log(char *string, long errflg)
2778 switch ((short) errflg) {
2780 ast_debug(1, "IMAP Info: %s\n", string);
2784 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2787 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2793 void mm_dlog(char *string)
2795 ast_log(AST_LOG_NOTICE, "%s\n", string);
2799 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2801 struct ast_vm_user *vmu;
2803 ast_debug(4, "Entering callback mm_login\n");
2805 ast_copy_string(user, mb->user, MAILTMPLEN);
2807 /* We should only do this when necessary */
2808 if (!ast_strlen_zero(authpassword)) {
2809 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2811 AST_LIST_TRAVERSE(&users, vmu, list) {
2812 if (!strcasecmp(mb->user, vmu->imapuser)) {
2813 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2818 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2819 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2827 void mm_critical(MAILSTREAM * stream)
2832 void mm_nocritical(MAILSTREAM * stream)
2837 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2839 kill (getpid (), SIGSTOP);
2844 void mm_fatal(char *string)
2846 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2849 /* C-client callback to handle quota */
2850 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2852 struct vm_state *vms;
2853 char *mailbox = stream->mailbox, *user;
2854 char buf[1024] = "";
2855 unsigned long usage = 0, limit = 0;
2858 usage = pquota->usage;
2859 limit = pquota->limit;
2860 pquota = pquota->next;
2863 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)))) {
2864 ast_log(AST_LOG_ERROR, "No state found.\n");
2868 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2870 vms->quota_usage = usage;
2871 vms->quota_limit = limit;
2874 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2876 char *start, *eol_pnt;
2879 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2882 taglen = strlen(tag) + 1;
2886 if (!(start = strstr(header, tag)))
2889 /* Since we can be called multiple times we should clear our buffer */
2890 memset(buf, 0, len);
2892 ast_copy_string(buf, start+taglen, len);
2893 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2898 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2900 char *start, *quote, *eol_pnt;
2902 if (ast_strlen_zero(mailbox))
2905 if (!(start = strstr(mailbox, "/user=")))
2908 ast_copy_string(buf, start+6, len);
2910 if (!(quote = strchr(buf, '\"'))) {
2911 if (!(eol_pnt = strchr(buf, '/')))
2912 eol_pnt = strchr(buf,'}');
2916 eol_pnt = strchr(buf+1,'\"');
2922 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2924 struct vm_state *vms_p;
2926 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2927 if ((vms_p = pthread_getspecific(ts_vmstate.key)) && !strcmp(vms_p->imapuser, vmu->imapuser) && !strcmp(vms_p->username, vmu->mailbox)) {
2930 if (option_debug > 4)
2931 ast_log(AST_LOG_DEBUG, "Adding new vmstate for %s\n", vmu->imapuser);
2932 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2934 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2935 ast_copy_string(vms_p->imapfolder, vmu->imapfolder, sizeof(vms_p->imapfolder));
2936 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2937 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2938 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2939 vms_p->imapversion = vmu->imapversion;
2940 if (option_debug > 4)
2941 ast_log(AST_LOG_DEBUG, "Copied %s to %s\n", vmu->imapuser, vms_p->imapuser);
2943 /* set mailbox to INBOX! */
2944 ast_copy_string(vms_p->curbox, mbox(vmu, 0), sizeof(vms_p->curbox));
2945 init_vm_state(vms_p);
2946 vmstate_insert(vms_p);
2950 static struct vm_state *get_vm_state_by_imapuser(const char *user, int interactive)
2952 struct vmstate *vlist = NULL;
2955 struct vm_state *vms;
2956 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2957 vms = pthread_getspecific(ts_vmstate.key);
2961 AST_LIST_LOCK(&vmstates);
2962 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2964 ast_debug(3, "error: vms is NULL for %s\n", user);
2967 if (vlist->vms->imapversion != imapversion) {
2970 if (!vlist->vms->imapuser) {
2971 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2975 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2976 AST_LIST_UNLOCK(&vmstates);
2980 AST_LIST_UNLOCK(&vmstates);
2982 ast_debug(3, "%s not found in vmstates\n", user);
2987 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2990 struct vmstate *vlist = NULL;
2991 const char *local_context = S_OR(context, "default");
2994 struct vm_state *vms;
2995 pthread_once(&ts_vmstate.once, ts_vmstate.key_init);
2996 vms = pthread_getspecific(ts_vmstate.key);
3000 AST_LIST_LOCK(&vmstates);
3001 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
3003 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
3006 if (vlist->vms->imapversion != imapversion) {
3009 if (!vlist->vms->username || !vlist->vms->context) {
3010 ast_debug(3, "error: username is NULL for %s\n", mailbox);
3014 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);
3016 if (!strcmp(vlist->vms->username, mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
3017 ast_debug(3, "Found it!\n");
3018 AST_LIST_UNLOCK(&vmstates);
3022 AST_LIST_UNLOCK(&vmstates);
3024 ast_debug(3, "%s not found in vmstates\n", mailbox);
3029 static void vmstate_insert(struct vm_state *vms)
3032 struct vm_state *altvms;
3034 /* If interactive, it probably already exists, and we should
3035 use the one we already have since it is more up to date.
3036 We can compare the username to find the duplicate */
3037 if (vms->interactive == 1) {
3038 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
3040 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3041 vms->newmessages = altvms->newmessages;
3042 vms->oldmessages = altvms->oldmessages;
3043 vms->vmArrayIndex = altvms->vmArrayIndex;
3044 vms->lastmsg = altvms->lastmsg;
3045 vms->curmsg = altvms->curmsg;
3046 /* get a pointer to the persistent store */
3047 vms->persist_vms = altvms;
3048 /* Reuse the mailstream? */
3049 #ifdef REALLY_FAST_EVEN_IF_IT_MEANS_RESOURCE_LEAKS
3050 vms->mailstream = altvms->mailstream;
3052 vms->mailstream = NIL;
3058 if (!(v = ast_calloc(1, sizeof(*v))))
3063 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3065 AST_LIST_LOCK(&vmstates);
3066 AST_LIST_INSERT_TAIL(&vmstates, v, list);
3067 AST_LIST_UNLOCK(&vmstates);
3070 static void vmstate_delete(struct vm_state *vms)
3072 struct vmstate *vc = NULL;
3073 struct vm_state *altvms = NULL;
3075 /* If interactive, we should copy pertinent info
3076 back to the persistent state (to make update immediate) */
3077 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
3078 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
3079 altvms->newmessages = vms->newmessages;
3080 altvms->oldmessages = vms->oldmessages;
3081 altvms->updated = 1;
3082 vms->mailstream = mail_close(vms->mailstream);
3084 /* Interactive states are not stored within the persistent list */
3088 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3090 AST_LIST_LOCK(&vmstates);
3091 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
3092 if (vc->vms == vms) {
3093 AST_LIST_REMOVE_CURRENT(list);
3097 AST_LIST_TRAVERSE_SAFE_END
3098 AST_LIST_UNLOCK(&vmstates);
3101 ast_mutex_destroy(&vc->vms->lock);
3105 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
3108 static void set_update(MAILSTREAM * stream)
3110 struct vm_state *vms;
3111 char *mailbox = stream->mailbox, *user;
3112 char buf[1024] = "";
3114 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
3115 if (user && option_debug > 2)
3116 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
3120 ast_debug(3, "User %s mailbox set for update.\n", user);
3122 vms->updated = 1; /* Set updated flag since mailbox changed */