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 * \brief Comedian Mail - Voicemail System
23 * \author Mark Spencer <markster@digium.com>
25 * \extref Unixodbc - http://www.unixodbc.org
26 * \extref A source distribution of University of Washington's IMAP
27 c-client (http://www.washington.edu/imap/
31 * \note For information about voicemail IMAP storage, read doc/imapstorage.txt
32 * \ingroup applications
33 * \note This module requires res_adsi to load. This needs to be optional
38 * \note This file is now almost impossible to work with, due to all \#ifdefs.
39 * Feels like the database code before realtime. Someone - please come up
40 * with a plan to clean this up.
44 <depend>res_smdi</depend>
48 <category name="MENUSELECT_OPTS_app_voicemail" displayname="Voicemail Build Options" positive_output="yes" remove_on_change="apps/app_voicemail.o apps/app_directory.o">
49 <member name="ODBC_STORAGE" displayname="Storage of Voicemail using ODBC">
50 <depend>unixodbc_or_iodbc</depend>
54 <conflict>IMAP_STORAGE</conflict>
55 <defaultenabled>no</defaultenabled>
57 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
58 <depend>imap_tk</depend>
59 <conflict>ODBC_STORAGE</conflict>
61 <defaultenabled>no</defaultenabled>
72 #ifdef USE_SYSTEM_IMAP
73 #include <imap/c-client.h>
74 #include <imap/imap4r1.h>
75 #include <imap/linkage.h>
76 #elif defined (USE_SYSTEM_CCLIENT)
77 #include <c-client/c-client.h>
78 #include <c-client/imap4r1.h>
79 #include <c-client/linkage.h>
87 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
89 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
96 #include "asterisk/logger.h"
97 #include "asterisk/lock.h"
98 #include "asterisk/file.h"
99 #include "asterisk/channel.h"
100 #include "asterisk/pbx.h"
101 #include "asterisk/config.h"
102 #include "asterisk/say.h"
103 #include "asterisk/module.h"
104 #include "asterisk/adsi.h"
105 #include "asterisk/app.h"
106 #include "asterisk/manager.h"
107 #include "asterisk/dsp.h"
108 #include "asterisk/localtime.h"
109 #include "asterisk/cli.h"
110 #include "asterisk/utils.h"
111 #include "asterisk/stringfields.h"
112 #include "asterisk/smdi.h"
113 #include "asterisk/event.h"
114 #include "asterisk/taskprocessor.h"
117 #include "asterisk/res_odbc.h"
121 static char imapserver[48];
122 static char imapport[8];
123 static char imapflags[128];
124 static char imapfolder[64];
125 static char imapparentfolder[64] = "\0";
126 static char greetingfolder[64];
127 static char authuser[32];
128 static char authpassword[42];
130 static int expungeonhangup = 1;
131 static int imapgreetings = 0;
132 static char delimiter = '\0';
137 /* Forward declarations for IMAP */
138 static int init_mailstream(struct vm_state *vms, int box);
139 static void write_file(char *filename, char *buffer, unsigned long len);
140 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
141 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
142 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
143 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
144 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
145 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
146 static void vmstate_insert(struct vm_state *vms);
147 static void vmstate_delete(struct vm_state *vms);
148 static void set_update(MAILSTREAM * stream);
149 static void init_vm_state(struct vm_state *vms);
150 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
151 static void get_mailbox_delimiter(MAILSTREAM *stream);
152 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
153 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
154 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag);
155 static void update_messages_by_imapuser(const char *user, unsigned long number);
157 static int imap_remove_file (char *dir, int msgnum);
158 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
159 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
160 static void check_quota(struct vm_state *vms, char *mailbox);
161 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
163 struct vm_state *vms;
164 AST_LIST_ENTRY(vmstate) list;
167 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
171 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
173 #define COMMAND_TIMEOUT 5000
174 /* Don't modify these here; set your umask at runtime instead */
175 #define VOICEMAIL_DIR_MODE 0777
176 #define VOICEMAIL_FILE_MODE 0666
177 #define CHUNKSIZE 65536
179 #define VOICEMAIL_CONFIG "voicemail.conf"
180 #define ASTERISK_USERNAME "asterisk"
182 /* Define fast-forward, pause, restart, and reverse keys
183 while listening to a voicemail message - these are
184 strings, not characters */
185 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
186 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
187 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
188 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
189 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
190 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
192 /* Default mail command to mail voicemail. Change it with the
193 mailcmd= command in voicemail.conf */
194 #define SENDMAIL "/usr/sbin/sendmail -t"
196 #define INTRO "vm-intro"
199 #define MAXMSGLIMIT 9999
201 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
203 #define BASELINELEN 72
204 #define BASEMAXINLINE 256
207 #define MAX_DATETIME_FORMAT 512
208 #define MAX_NUM_CID_CONTEXTS 10
210 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
211 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
212 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
213 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
214 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
215 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
216 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
217 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
218 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
219 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
220 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
221 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
222 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
223 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
224 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
225 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
226 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
227 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
228 #define ERROR_LOCK_PATH -100
241 OPT_SILENT = (1 << 0),
242 OPT_BUSY_GREETING = (1 << 1),
243 OPT_UNAVAIL_GREETING = (1 << 2),
244 OPT_RECORDGAIN = (1 << 3),
245 OPT_PREPEND_MAILBOX = (1 << 4),
246 OPT_AUTOPLAY = (1 << 6),
247 OPT_DTMFEXIT = (1 << 7),
248 OPT_MESSAGE_Urgent = (1 << 8),
249 OPT_MESSAGE_PRIORITY = (1 << 9)
253 OPT_ARG_RECORDGAIN = 0,
254 OPT_ARG_PLAYFOLDER = 1,
255 OPT_ARG_DTMFEXIT = 2,
256 /* This *must* be the last value in this enum! */
257 OPT_ARG_ARRAY_SIZE = 3,
260 AST_APP_OPTIONS(vm_app_options, {
261 AST_APP_OPTION('s', OPT_SILENT),
262 AST_APP_OPTION('b', OPT_BUSY_GREETING),
263 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
264 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
265 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
266 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
267 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
268 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
269 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
272 static int load_config(int reload);
274 /*! \page vmlang Voicemail Language Syntaxes Supported
276 \par Syntaxes supported, not really language codes.
283 \arg \b pt - Portuguese
284 \arg \b pt_BR - Portuguese (Brazil)
286 \arg \b no - Norwegian
288 \arg \b tw - Chinese (Taiwan)
289 \arg \b ua - Ukrainian
291 German requires the following additional soundfile:
292 \arg \b 1F einE (feminine)
294 Spanish requires the following additional soundfile:
295 \arg \b 1M un (masculine)
297 Dutch, Portuguese & Spanish require the following additional soundfiles:
298 \arg \b vm-INBOXs singular of 'new'
299 \arg \b vm-Olds singular of 'old/heard/read'
302 \arg \b vm-INBOX nieuwe (nl)
303 \arg \b vm-Old oude (nl)
306 \arg \b vm-new-a 'new', feminine singular accusative
307 \arg \b vm-new-e 'new', feminine plural accusative
308 \arg \b vm-new-ych 'new', feminine plural genitive
309 \arg \b vm-old-a 'old', feminine singular accusative
310 \arg \b vm-old-e 'old', feminine plural accusative
311 \arg \b vm-old-ych 'old', feminine plural genitive
312 \arg \b digits/1-a 'one', not always same as 'digits/1'
313 \arg \b digits/2-ie 'two', not always same as 'digits/2'
316 \arg \b vm-nytt singular of 'new'
317 \arg \b vm-nya plural of 'new'
318 \arg \b vm-gammalt singular of 'old'
319 \arg \b vm-gamla plural of 'old'
320 \arg \b digits/ett 'one', not always same as 'digits/1'
323 \arg \b vm-ny singular of 'new'
324 \arg \b vm-nye plural of 'new'
325 \arg \b vm-gammel singular of 'old'
326 \arg \b vm-gamle plural of 'old'
334 Ukrainian requires the following additional soundfile:
335 \arg \b vm-nove 'nove'
336 \arg \b vm-stare 'stare'
337 \arg \b digits/ua/1e 'odne'
339 Italian requires the following additional soundfile:
343 \arg \b vm-nuovi new plural
344 \arg \b vm-vecchio old
345 \arg \b vm-vecchi old plural
347 Chinese (Taiwan) requires the following additional soundfile:
348 \arg \b vm-tong A class-word for call (tong1)
349 \arg \b vm-ri A class-word for day (ri4)
350 \arg \b vm-you You (ni3)
351 \arg \b vm-haveno Have no (mei2 you3)
352 \arg \b vm-have Have (you3)
353 \arg \b vm-listen To listen (yao4 ting1)
356 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
357 spelled among others when you have to change folder. For the above reasons, vm-INBOX
358 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
367 unsigned char iobuf[BASEMAXINLINE];
370 /*! Structure for linked list of users
371 * Use ast_vm_user_destroy() to free one of these structures. */
373 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
374 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
375 char password[80]; /*!< Secret pin code, numbers only */
376 char fullname[80]; /*!< Full name, for directory app */
377 char email[80]; /*!< E-mail address */
378 char pager[80]; /*!< E-mail address to pager (no attachment) */
379 char serveremail[80]; /*!< From: Mail address */
380 char mailcmd[160]; /*!< Configurable mail command */
381 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
382 char zonetag[80]; /*!< Time zone */
385 char uniqueid[80]; /*!< Unique integer identifier */
387 char attachfmt[20]; /*!< Attachment format */
388 unsigned int flags; /*!< VM_ flags */
390 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
391 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
392 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
394 char imapuser[80]; /*!< IMAP server login */
395 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
397 double volgain; /*!< Volume gain for voicemails sent via email */
398 AST_LIST_ENTRY(ast_vm_user) list;
401 /*! Voicemail time zones */
403 AST_LIST_ENTRY(vm_zone) list;
406 char msg_format[512];
409 #define VMSTATE_MAX_MSG_ARRAY 256
411 /*! Voicemail mailbox state */
416 char curdir[PATH_MAX];
417 char vmbox[PATH_MAX];
419 char intro[PATH_MAX];
431 int updated; /*!< decremented on each mail check until 1 -allows delay */
432 long msgArray[VMSTATE_MAX_MSG_ARRAY];
433 MAILSTREAM *mailstream;
435 char imapuser[80]; /*!< IMAP server login */
437 char introfn[PATH_MAX]; /*!< Name of prepended file */
438 unsigned int quota_limit;
439 unsigned int quota_usage;
440 struct vm_state *persist_vms;
445 static char odbc_database[80];
446 static char odbc_table[80];
447 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
448 #define DISPOSE(a,b) remove_file(a,b)
449 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
450 #define EXISTS(a,b,c,d) (message_exists(a,b))
451 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
452 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
453 #define DELETE(a,b,c,d) (delete_file(a,b))
456 #define DISPOSE(a,b) (imap_remove_file(a,b))
457 #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))
458 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
459 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
460 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
461 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
462 #define DELETE(a,b,c,d) (vm_imap_delete(b,d))
464 #define RETRIEVE(a,b,c,d)
466 #define STORE(a,b,c,d,e,f,g,h,i,j)
467 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
468 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
469 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
470 #define DELETE(a,b,c,d) (vm_delete(c))
474 static char VM_SPOOL_DIR[PATH_MAX];
476 static char ext_pass_cmd[128];
477 static char ext_pass_check_cmd[128];
481 #define PWDCHANGE_INTERNAL (1 << 1)
482 #define PWDCHANGE_EXTERNAL (1 << 2)
483 static int pwdchange = PWDCHANGE_INTERNAL;
486 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
489 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
491 # define tdesc "Comedian Mail (Voicemail System)"
495 static char userscontext[AST_MAX_EXTENSION] = "default";
497 static char *addesc = "Comedian Mail";
499 static char *synopsis_vm = "Leave a Voicemail message";
501 static char *descrip_vm =
502 " VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
503 "application allows the calling party to leave a message for the specified\n"
504 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
505 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
506 "specified mailbox does not exist.\n"
507 " The Voicemail application will exit if any of the following DTMF digits are\n"
509 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
510 " * - Jump to the 'a' extension in the current dialplan context.\n"
511 " This application will set the following channel variable upon completion:\n"
512 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
513 " application. The possible values are:\n"
514 " SUCCESS | USEREXIT | FAILED\n\n"
516 " b - Play the 'busy' greeting to the calling party.\n"
517 " d([c]) - Accept digits for a new extension in context c, if played during\n"
518 " the greeting. Context defaults to the current context.\n"
519 " g(#) - Use the specified amount of gain when recording the voicemail\n"
520 " message. The units are whole-number decibels (dB).\n"
521 " Only works on supported technologies, which is DAHDI only.\n"
522 " s - Skip the playback of instructions for leaving a message to the\n"
524 " u - Play the 'unavailable' greeting.\n"
525 " U - Mark message as Urgent.\n"
526 " P - Mark message as PRIORITY.\n";
528 static char *synopsis_vmain = "Check Voicemail messages";
530 static char *descrip_vmain =
531 " VoiceMailMain([mailbox][@context][,options]): This application allows the\n"
532 "calling party to check voicemail messages. A specific mailbox, and optional\n"
533 "corresponding context, may be specified. If a mailbox is not provided, the\n"
534 "calling party will be prompted to enter one. If a context is not specified,\n"
535 "the 'default' context will be used.\n\n"
537 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
538 " is entered by the caller.\n"
539 " g(#) - Use the specified amount of gain when recording a voicemail\n"
540 " message. The units are whole-number decibels (dB).\n"
541 " s - Skip checking the passcode for the mailbox.\n"
542 " a(#) - Skip folder prompt and go directly to folder specified.\n"
543 " Defaults to INBOX\n";
545 static char *synopsis_vm_box_exists =
546 "Check to see if Voicemail mailbox exists";
548 static char *descrip_vm_box_exists =
549 " MailboxExists(mailbox[@context][,options]): Check to see if the specified\n"
550 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
552 " This application will set the following channel variable upon completion:\n"
553 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
554 " MailboxExists application. Possible values include:\n"
555 " SUCCESS | FAILED\n\n"
556 " Options: (none)\n";
558 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
560 static char *descrip_vmauthenticate =
561 " VMAuthenticate([mailbox][@context][,options]): This application behaves the\n"
562 "same way as the Authenticate application, but the passwords are taken from\n"
564 " If the mailbox is specified, only that mailbox's password will be considered\n"
565 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
566 "be set with the authenticated mailbox.\n\n"
568 " s - Skip playing the initial prompts.\n";
570 /* Leave a message */
571 static char *app = "VoiceMail";
573 /* Check mail, control, etc */
574 static char *app2 = "VoiceMailMain";
576 static char *app3 = "MailboxExists";
577 static char *app4 = "VMAuthenticate";
579 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
580 static AST_LIST_HEAD_STATIC(zones, vm_zone);
581 static int maxsilence;
583 static int maxdeletedmsg;
584 static int silencethreshold = 128;
585 static char serveremail[80];
586 static char mailcmd[160]; /* Configurable mail cmd */
587 static char externnotify[160];
588 static struct ast_smdi_interface *smdi_iface = NULL;
589 static char vmfmts[80];
590 static double volgain;
591 static int vmminsecs;
592 static int vmmaxsecs;
595 static int maxlogins;
596 static int minpassword;
598 /*! Poll mailboxes for changes since there is something external to
599 * app_voicemail that may change them. */
600 static unsigned int poll_mailboxes;
602 /*! Polling frequency */
603 static unsigned int poll_freq;
604 /*! By default, poll every 30 seconds */
605 #define DEFAULT_POLL_FREQ 30
607 AST_MUTEX_DEFINE_STATIC(poll_lock);
608 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
609 static pthread_t poll_thread = AST_PTHREADT_NULL;
610 static unsigned char poll_thread_run;
612 /*! Subscription to ... MWI event subscriptions */
613 static struct ast_event_sub *mwi_sub_sub;
614 /*! Subscription to ... MWI event un-subscriptions */
615 static struct ast_event_sub *mwi_unsub_sub;
618 * \brief An MWI subscription
620 * This is so we can keep track of which mailboxes are subscribed to.
621 * This way, we know which mailboxes to poll when the pollmailboxes
622 * option is being used.
625 AST_RWLIST_ENTRY(mwi_sub) entry;
633 struct mwi_sub_task {
639 static struct ast_taskprocessor *mwi_subscription_tps;
641 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
643 /* custom audio control prompts for voicemail playback */
644 static char listen_control_forward_key[12];
645 static char listen_control_reverse_key[12];
646 static char listen_control_pause_key[12];
647 static char listen_control_restart_key[12];
648 static char listen_control_stop_key[12];
650 /* custom password sounds */
651 static char vm_password[80] = "vm-password";
652 static char vm_newpassword[80] = "vm-newpassword";
653 static char vm_passchanged[80] = "vm-passchanged";
654 static char vm_reenterpassword[80] = "vm-reenterpassword";
655 static char vm_mismatch[80] = "vm-mismatch";
656 static char vm_invalid_password[80] = "vm-invalid-password";
658 static struct ast_flags globalflags = {0};
660 static int saydurationminfo;
662 static char dialcontext[AST_MAX_CONTEXT] = "";
663 static char callcontext[AST_MAX_CONTEXT] = "";
664 static char exitcontext[AST_MAX_CONTEXT] = "";
666 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
669 static char *emailbody = NULL;
670 static char *emailsubject = NULL;
671 static char *pagerbody = NULL;
672 static char *pagersubject = NULL;
673 static char fromstring[100];
674 static char pagerfromstring[100];
675 static char charset[32] = "ISO-8859-1";
677 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
678 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
679 static int adsiver = 1;
680 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
682 /* Forward declarations - generic */
683 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
684 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);
685 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
686 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
687 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
688 signed char record_gain, struct vm_state *vms, char *flag);
689 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
690 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
691 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);
692 static void make_email_file(FILE *p, char *srcemail, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, 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);
693 static void apply_options(struct ast_vm_user *vmu, const char *options);
694 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);
695 static int is_valid_dtmf(const char *key);
697 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
698 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
701 static char *strip_control(const char *input, char *buf, size_t buflen)
704 for (; *input; input++) {
709 if (bufptr == buf + buflen - 1) {
719 * \brief Sets default voicemail system options to a voicemail user.
721 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
722 * - all the globalflags
723 * - the saydurationminfo
727 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
730 static void populate_defaults(struct ast_vm_user *vmu)
732 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
733 if (saydurationminfo)
734 vmu->saydurationm = saydurationminfo;
735 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
736 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
737 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
739 vmu->maxsecs = vmmaxsecs;
741 vmu->maxmsg = maxmsg;
743 vmu->maxdeletedmsg = maxdeletedmsg;
744 vmu->volgain = volgain;
748 * \brief Sets a a specific property value.
749 * \param vmu The voicemail user object to work with.
750 * \param var The name of the property to be set.
751 * \param value The value to be set to the property.
753 * The property name must be one of the understood properties. See the source for details.
755 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
758 if (!strcasecmp(var, "attach")) {
759 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
760 } else if (!strcasecmp(var, "attachfmt")) {
761 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
762 } else if (!strcasecmp(var, "serveremail")) {
763 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
764 } else if (!strcasecmp(var, "language")) {
765 ast_copy_string(vmu->language, value, sizeof(vmu->language));
766 } else if (!strcasecmp(var, "tz")) {
767 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
769 } else if (!strcasecmp(var, "imapuser")) {
770 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
771 } else if (!strcasecmp(var, "imappassword") || !strcasecmp(var, "imapsecret")) {
772 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
774 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
775 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
776 } else if (!strcasecmp(var, "saycid")){
777 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
778 } else if (!strcasecmp(var,"sendvoicemail")){
779 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
780 } else if (!strcasecmp(var, "review")){
781 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
782 } else if (!strcasecmp(var, "tempgreetwarn")){
783 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
784 } else if (!strcasecmp(var, "messagewrap")){
785 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
786 } else if (!strcasecmp(var, "operator")) {
787 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
788 } else if (!strcasecmp(var, "envelope")){
789 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
790 } else if (!strcasecmp(var, "moveheard")){
791 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
792 } else if (!strcasecmp(var, "sayduration")){
793 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
794 } else if (!strcasecmp(var, "saydurationm")){
795 if (sscanf(value, "%d", &x) == 1) {
796 vmu->saydurationm = x;
798 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
800 } else if (!strcasecmp(var, "forcename")){
801 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
802 } else if (!strcasecmp(var, "forcegreetings")){
803 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
804 } else if (!strcasecmp(var, "callback")) {
805 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
806 } else if (!strcasecmp(var, "dialout")) {
807 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
808 } else if (!strcasecmp(var, "exitcontext")) {
809 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
810 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
811 if (vmu->maxsecs <= 0) {
812 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
813 vmu->maxsecs = vmmaxsecs;
815 vmu->maxsecs = atoi(value);
817 if (!strcasecmp(var, "maxmessage"))
818 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
819 } else if (!strcasecmp(var, "maxmsg")) {
820 vmu->maxmsg = atoi(value);
821 if (vmu->maxmsg <= 0) {
822 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
823 vmu->maxmsg = MAXMSG;
824 } else if (vmu->maxmsg > MAXMSGLIMIT) {
825 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
826 vmu->maxmsg = MAXMSGLIMIT;
828 } else if (!strcasecmp(var, "backupdeleted")) {
829 if (sscanf(value, "%d", &x) == 1)
830 vmu->maxdeletedmsg = x;
831 else if (ast_true(value))
832 vmu->maxdeletedmsg = MAXMSG;
834 vmu->maxdeletedmsg = 0;
836 if (vmu->maxdeletedmsg < 0) {
837 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
838 vmu->maxdeletedmsg = MAXMSG;
839 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
840 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
841 vmu->maxdeletedmsg = MAXMSGLIMIT;
843 } else if (!strcasecmp(var, "volgain")) {
844 sscanf(value, "%lf", &vmu->volgain);
845 } else if (!strcasecmp(var, "options")) {
846 apply_options(vmu, value);
850 static char *vm_check_password_shell(char *command, char *buf, size_t len)
857 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
860 pid = ast_safe_fork(0);
866 snprintf(buf, len, "FAILURE: Fork failed");
870 read(fds[0], buf, len);
874 AST_DECLARE_APP_ARGS(arg,
877 char *mycmd = ast_strdupa(command);
880 dup2(fds[1], STDOUT_FILENO);
882 ast_close_fds_above_n(STDOUT_FILENO);
884 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
886 execv(arg.v[0], arg.v);
887 printf("FAILURE: %s", strerror(errno));
895 * \brief Check that password meets minimum required length
896 * \param vmu The voicemail user to change the password for.
897 * \param password The password string to check
899 * \return zero on ok, 1 on not ok.
901 static int check_password(struct ast_vm_user *vmu, char *password)
903 /* check minimum length */
904 if (strlen(password) < minpassword)
906 if (!ast_strlen_zero(ext_pass_check_cmd)) {
907 char cmd[255], buf[255];
909 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
911 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
912 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
913 ast_debug(5, "Result: %s\n", buf);
914 if (!strncasecmp(buf, "VALID", 5)) {
915 ast_debug(3, "Passed password check: '%s'\n", buf);
917 } else if (!strncasecmp(buf, "FAILURE", 7)) {
918 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
921 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
930 * \brief Performs a change of the voicemail passowrd in the realtime engine.
931 * \param vmu The voicemail user to change the password for.
932 * \param password The new value to be set to the password for this user.
934 * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
935 * This is called from the (top level) vm_change_password.
937 * \return zero on success, -1 on error.
939 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
942 if (!ast_strlen_zero(vmu->uniqueid)) {
943 if (strlen(password) > 10) {
944 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
946 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, SENTINEL);
948 ast_copy_string(vmu->password, password, sizeof(vmu->password));
959 * \brief Destructively Parse options and apply.
961 static void apply_options(struct ast_vm_user *vmu, const char *options)
966 stringp = ast_strdupa(options);
967 while ((s = strsep(&stringp, "|"))) {
969 if ((var = strsep(&value, "=")) && value) {
970 apply_option(vmu, var, value);
976 * \brief Loads the options specific to a voicemail user.
978 * This is called when a vm_user structure is being set up, such as from load_options.
980 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
982 struct ast_variable *tmp;
985 if (!strcasecmp(tmp->name, "vmsecret")) {
986 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
987 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
988 if (ast_strlen_zero(retval->password))
989 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
990 } else if (!strcasecmp(tmp->name, "uniqueid")) {
991 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
992 } else if (!strcasecmp(tmp->name, "pager")) {
993 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
994 } else if (!strcasecmp(tmp->name, "email")) {
995 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
996 } else if (!strcasecmp(tmp->name, "fullname")) {
997 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
998 } else if (!strcasecmp(tmp->name, "context")) {
999 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
1001 } else if (!strcasecmp(tmp->name, "imapuser")) {
1002 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
1003 } else if (!strcasecmp(tmp->name, "imappassword") || !strcasecmp(tmp->name, "imapsecret")) {
1004 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
1007 apply_option(retval, tmp->name, tmp->value);
1013 * \brief Determines if a DTMF key entered is valid.
1014 * \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.
1016 * Tests the character entered against the set of valid DTMF characters.
1017 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1019 static int is_valid_dtmf(const char *key)
1022 char *local_key = ast_strdupa(key);
1024 for (i = 0; i < strlen(key); ++i) {
1025 if (!strchr(VALID_DTMF, *local_key)) {
1026 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1035 * \brief Finds a voicemail user from the realtime engine.
1040 * 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.
1042 * \return The ast_vm_user structure for the user that was found.
1044 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1046 struct ast_variable *var;
1047 struct ast_vm_user *retval;
1049 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1051 ast_set_flag(retval, VM_ALLOCED);
1053 memset(retval, 0, sizeof(*retval));
1055 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1056 populate_defaults(retval);
1057 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1058 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1060 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1062 apply_options_full(retval, var);
1063 ast_variables_destroy(var);
1074 * \brief Finds a voicemail user from the users file or the realtime engine.
1079 * \return The ast_vm_user structure for the user that was found.
1081 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1083 /* This function could be made to generate one from a database, too */
1084 struct ast_vm_user *vmu=NULL, *cur;
1085 AST_LIST_LOCK(&users);
1087 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1088 context = "default";
1090 AST_LIST_TRAVERSE(&users, cur, list) {
1091 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1093 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1097 /* Make a copy, so that on a reload, we have no race */
1098 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1099 memcpy(vmu, cur, sizeof(*vmu));
1100 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1101 AST_LIST_NEXT(vmu, list) = NULL;
1104 vmu = find_user_realtime(ivm, context, mailbox);
1105 AST_LIST_UNLOCK(&users);
1110 * \brief Resets a user password to a specified password.
1115 * This does the actual change password work, called by the vm_change_password() function.
1117 * \return zero on success, -1 on error.
1119 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1121 /* This function could be made to generate one from a database, too */
1122 struct ast_vm_user *cur;
1124 AST_LIST_LOCK(&users);
1125 AST_LIST_TRAVERSE(&users, cur, list) {
1126 if ((!context || !strcasecmp(context, cur->context)) &&
1127 (!strcasecmp(mailbox, cur->mailbox)))
1131 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1134 AST_LIST_UNLOCK(&users);
1139 * \brief The handler for the change password option.
1140 * \param vmu The voicemail user to work with.
1141 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1142 * 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.
1143 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1145 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1147 struct ast_config *cfg=NULL;
1148 struct ast_variable *var=NULL;
1149 struct ast_category *cat=NULL;
1150 char *category=NULL, *value=NULL, *new=NULL;
1151 const char *tmp=NULL;
1152 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1153 if (!change_password_realtime(vmu, newpassword))
1156 /* check voicemail.conf */
1157 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1158 while ((category = ast_category_browse(cfg, category))) {
1159 if (!strcasecmp(category, vmu->context)) {
1160 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1161 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1164 value = strstr(tmp,",");
1166 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1169 new = alloca((strlen(value)+strlen(newpassword)+1));
1170 sprintf(new,"%s%s", newpassword, value);
1171 if (!(cat = ast_category_get(cfg, category))) {
1172 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1175 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1178 /* save the results */
1179 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1180 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1181 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1185 /* check users.conf and update the password stored for the mailbox*/
1186 /* if no vmsecret entry exists create one. */
1187 if ((cfg = ast_config_load("users.conf", config_flags)) && cfg != CONFIG_STATUS_FILEINVALID) {
1188 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1189 while ((category = ast_category_browse(cfg, category))) {
1190 ast_debug(4, "users.conf: %s\n", category);
1191 if (!strcasecmp(category, vmu->mailbox)) {
1192 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1193 ast_debug(3, "looks like we need to make vmsecret!\n");
1194 var = ast_variable_new("vmsecret", newpassword, "");
1196 new = alloca(strlen(newpassword)+1);
1197 sprintf(new, "%s", newpassword);
1198 if (!(cat = ast_category_get(cfg, category))) {
1199 ast_debug(4, "failed to get category!\n");
1203 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1205 ast_variable_append(cat, var);
1208 /* save the results and clean things up */
1209 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1210 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1211 config_text_file_save("users.conf", cfg, "AppVoicemail");
1215 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1218 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
1219 if (!ast_safe_system(buf)) {
1220 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1221 /* Reset the password in memory, too */
1222 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1227 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1228 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1229 * \param len The length of the path string that was written out.
1231 * The path is constructed as
1232 * VM_SPOOL_DIRcontext/ext/folder
1234 * \return zero on success, -1 on error.
1236 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1238 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1242 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1243 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1244 * \param len The length of the path string that was written out.
1246 * The path is constructed as
1247 * VM_SPOOL_DIRcontext/ext/folder
1249 * \return zero on success, -1 on error.
1251 static int make_file(char *dest, const int len, const char *dir, const int num)
1253 return snprintf(dest, len, "%s/msg%04d", dir, num);
1256 /* same as mkstemp, but return a FILE * */
1257 static FILE *vm_mkftemp(char *template)
1260 int pfd = mkstemp(template);
1261 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1263 p = fdopen(pfd, "w+");
1272 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1273 * \param dest String. base directory.
1274 * \param len Length of dest.
1275 * \param context String. Ignored if is null or empty string.
1276 * \param ext String. Ignored if is null or empty string.
1277 * \param folder String. Ignored if is null or empty string.
1278 * \return -1 on failure, 0 on success.
1280 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1282 mode_t mode = VOICEMAIL_DIR_MODE;
1285 make_dir(dest, len, context, ext, folder);
1286 if ((res = ast_mkdir(dest, mode))) {
1287 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1293 static const char *mbox(int id)
1295 static const char *msgs[] = {
1313 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
1316 static void free_user(struct ast_vm_user *vmu)
1318 if (ast_test_flag(vmu, VM_ALLOCED))
1322 /* All IMAP-specific functions should go in this block. This
1323 * keeps them from being spread out all over the code */
1325 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1328 struct vm_state *vms;
1329 unsigned long messageNum;
1331 /* Greetings aren't stored in IMAP, so we can't delete them there */
1336 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1337 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);
1341 /* find real message number based on msgnum */
1342 /* this may be an index into vms->msgArray based on the msgnum. */
1343 messageNum = vms->msgArray[msgnum];
1344 if (messageNum == 0) {
1345 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1348 if (option_debug > 2)
1349 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1350 /* delete message */
1351 snprintf (arg, sizeof(arg), "%lu",messageNum);
1352 mail_setflag (vms->mailstream,arg,"\\DELETED");
1355 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1357 struct vm_state *vms_p;
1358 char *file, *filename;
1363 /* This function is only used for retrieval of IMAP greetings
1364 * regular messages are not retrieved this way, nor are greetings
1365 * if they are stored locally*/
1366 if (msgnum > -1 || !imapgreetings) {
1369 file = strrchr(ast_strdupa(dir), '/');
1373 ast_debug (1, "Failed to procure file name from directory passed.\n");
1378 /* check if someone is accessing this box right now... */
1379 if (!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) ||!(vms_p = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1380 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1384 /* Greetings will never have a prepended message */
1385 *vms_p->introfn = '\0';
1387 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1388 if (!vms_p->mailstream) {
1389 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1393 /*XXX Yuck, this could probably be done a lot better */
1394 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1395 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1396 /* We have the body, now we extract the file name of the first attachment. */
1397 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1398 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1400 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1403 filename = strsep(&attachment, ".");
1404 if (!strcmp(filename, file)) {
1405 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1406 vms_p->msgArray[vms_p->curmsg] = i + 1;
1407 save_body(body, vms_p, "2", attachment, 0);
1415 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1418 char *header_content;
1419 char *attachedfilefmt;
1421 struct vm_state *vms;
1422 char text_file[PATH_MAX];
1423 FILE *text_file_ptr;
1425 struct ast_vm_user *vmu;
1427 if (!(vmu = find_user(NULL, context, mailbox))) {
1428 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1433 if (imapgreetings) {
1434 res = imap_retrieve_greeting(dir, msgnum, vmu);
1442 /* Before anything can happen, we need a vm_state so that we can
1443 * actually access the imap server through the vms->mailstream
1445 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1446 /* This should not happen. If it does, then I guess we'd
1447 * need to create the vm_state, extract which mailbox to
1448 * open, and then set up the msgArray so that the correct
1449 * IMAP message could be accessed. If I have seen correctly
1450 * though, the vms should be obtainable from the vmstates list
1451 * and should have its msgArray properly set up.
1453 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1458 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1459 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1461 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1462 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1467 if (option_debug > 2)
1468 ast_log (LOG_DEBUG,"Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1469 if (vms->msgArray[msgnum] == 0) {
1470 ast_log (LOG_WARNING,"Trying to access unknown message\n");
1475 /* This will only work for new messages... */
1476 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1477 /* empty string means no valid header */
1478 if (ast_strlen_zero(header_content)) {
1479 ast_log (LOG_ERROR,"Could not fetch header for message number %ld\n",vms->msgArray[msgnum]);
1484 mail_fetchstructure (vms->mailstream,vms->msgArray[msgnum],&body);
1486 /* We have the body, now we extract the file name of the first attachment. */
1487 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1488 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1490 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1495 /* Find the format of the attached file */
1497 strsep(&attachedfilefmt, ".");
1498 if (!attachedfilefmt) {
1499 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1504 save_body(body, vms, "2", attachedfilefmt, 0);
1505 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1506 *vms->introfn = '\0';
1509 /* Get info from headers!! */
1510 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1512 if (!(text_file_ptr = fopen(text_file, "w"))) {
1513 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1516 fprintf(text_file_ptr, "%s\n", "[message]");
1518 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1519 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1520 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1521 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1522 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1523 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1524 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1525 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1526 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1527 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1528 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1529 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1530 get_header_by_tag(header_content, "X-Asterisk-VM-Flag:", buf, sizeof(buf));
1531 fprintf(text_file_ptr, "flag=%s\n", S_OR(buf, ""));
1532 fclose(text_file_ptr);
1539 static int folder_int(const char *folder)
1541 /*assume a NULL folder means INBOX*/
1545 if (!strcasecmp(folder, imapfolder))
1547 if (!strcasecmp(folder, "INBOX"))
1550 else if (!strcasecmp(folder, "Old"))
1552 else if (!strcasecmp(folder, "Work"))
1554 else if (!strcasecmp(folder, "Family"))
1556 else if (!strcasecmp(folder, "Friends"))
1558 else if (!strcasecmp(folder, "Cust1"))
1560 else if (!strcasecmp(folder, "Cust2"))
1562 else if (!strcasecmp(folder, "Cust3"))
1564 else if (!strcasecmp(folder, "Cust4"))
1566 else if (!strcasecmp(folder, "Cust5"))
1568 else /*assume they meant INBOX if folder is not found otherwise*/
1573 * \brief Gets the number of messages that exist in a mailbox folder.
1578 * This method is used when IMAP backend is used.
1579 * \return The number of messages in this mailbox folder (zero or more).
1581 static int messagecount(const char *context, const char *mailbox, const char *folder)
1586 struct ast_vm_user *vmu, vmus;
1587 struct vm_state *vms_p;
1589 int fold = folder_int(folder);
1592 if (ast_strlen_zero(mailbox))
1595 /* We have to get the user before we can open the stream! */
1596 vmu = find_user(&vmus, context, mailbox);
1598 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1601 /* No IMAP account available */
1602 if (vmu->imapuser[0] == '\0') {
1603 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1608 /* No IMAP account available */
1609 if (vmu->imapuser[0] == '\0') {
1610 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1615 /* check if someone is accessing this box right now... */
1616 vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
1618 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1621 ast_debug(3, "Returning before search - user is logged in\n");
1622 if (fold == 0) { /* INBOX */
1623 return vms_p->newmessages;
1625 if (fold == 1) { /* Old messages */
1626 return vms_p->oldmessages;
1628 if (fold == 11) {/*Urgent messages*/
1629 return vms_p->urgentmessages;
1633 /* add one if not there... */
1634 vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
1636 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1639 /* If URGENT, then look at INBOX */
1646 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
1647 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
1650 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
1651 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
1652 vms_p->mailstream = NIL; /* save for access from interactive entry point */
1653 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
1655 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
1656 init_vm_state(vms_p);
1657 vmstate_insert(vms_p);
1659 ret = init_mailstream(vms_p, fold);
1660 if (!vms_p->mailstream) {
1661 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1665 pgm = mail_newsearchpgm ();
1666 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
1672 /* In the special case where fold is 1 (old messages) we have to do things a bit
1673 * differently. Old messages are stored in the INBOX but are marked as "seen"
1679 /* look for urgent messages */
1687 vms_p->vmArrayIndex = 0;
1688 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1689 if (fold == 0 && urgent == 0)
1690 vms_p->newmessages = vms_p->vmArrayIndex;
1692 vms_p->oldmessages = vms_p->vmArrayIndex;
1693 if (fold == 0 && urgent == 1)
1694 vms_p->urgentmessages = vms_p->vmArrayIndex;
1695 /*Freeing the searchpgm also frees the searchhdr*/
1696 mail_free_searchpgm(&pgm);
1698 return vms_p->vmArrayIndex;
1700 mail_ping(vms_p->mailstream);
1705 static int imap_store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum, struct ast_channel *chan, struct ast_vm_user *vmu, char *fmt, int duration, struct vm_state *vms, const char *flag)
1707 char *myserveremail = serveremail;
1709 char introfn[PATH_MAX];
1713 char tmp[80] = "/tmp/astmail-XXXXXX";
1718 int ret; /* for better error checking */
1719 char *imap_flags = NIL;
1721 /* Set urgent flag for IMAP message */
1722 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1723 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1724 imap_flags="\\FLAGGED";
1727 /* Attach only the first format */
1728 fmt = ast_strdupa(fmt);
1730 strsep(&stringp, "|");
1732 if (!ast_strlen_zero(vmu->serveremail))
1733 myserveremail = vmu->serveremail;
1736 make_file(fn, sizeof(fn), dir, msgnum);
1738 ast_copy_string (fn, dir, sizeof(fn));
1740 snprintf(introfn, sizeof(introfn), "%sintro", fn);
1741 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1745 if (ast_strlen_zero(vmu->email)) {
1746 /* We need the vmu->email to be set when we call make_email_file, but
1747 * if we keep it set, a duplicate e-mail will be created. So at the end
1748 * of this function, we will revert back to an empty string if tempcopy
1751 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1755 if (!strcmp(fmt, "wav49"))
1757 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1759 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1761 if (!(p = vm_mkftemp(tmp))) {
1762 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1764 *(vmu->email) = '\0';
1768 if (msgnum < 0 && imapgreetings) {
1769 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1770 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1773 imap_delete_old_greeting(fn, vms);
1776 make_email_file(p, myserveremail, vmu, msgnum, vmu->context, vmu->mailbox, S_OR(chan->cid.cid_num, NULL), S_OR(chan->cid.cid_name, NULL), fn, introfn, fmt, duration, 1, chan, NULL, 1, flag);
1777 /* read mail file to memory */
1780 if (!(buf = ast_malloc(len + 1))) {
1781 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1784 *(vmu->email) = '\0';
1787 fread(buf, len, 1, p);
1788 ((char *)buf)[len] = '\0';
1789 INIT(&str, mail_string, buf, len);
1790 ret = init_mailstream(vms, NEW_FOLDER);
1792 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1793 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1794 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1799 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n",mailbox);
1805 ast_debug(3, "%s stored\n", fn);
1808 *(vmu->email) = '\0';
1815 * \brief Gets the number of messages that exist in the inbox folder.
1816 * \param mailbox_context
1817 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1818 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1819 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1821 * This method is used when IMAP backend is used.
1822 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1824 * \return zero on success, -1 on error.
1827 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1829 char tmp[PATH_MAX] = "";
1841 ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
1842 /* If no mailbox, return immediately */
1843 if (ast_strlen_zero(mailbox_context))
1846 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1847 context = strchr(tmp, '@');
1848 if (strchr(mailbox_context, ',')) {
1849 int tmpnew, tmpold, tmpurgent;
1850 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1852 while ((cur = strsep(&mb, ", "))) {
1853 if (!ast_strlen_zero(cur)) {
1854 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
1862 *urgentmsgs += tmpurgent;
1873 context = "default";
1874 mailboxnc = (char *)mailbox_context;
1877 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
1881 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
1885 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
1891 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
1893 return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
1897 * \brief Determines if the given folder has messages.
1898 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
1899 * \param folder the folder to look in
1901 * This function is used when the mailbox is stored in an IMAP back end.
1902 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
1903 * \return 1 if the folder has one or more messages. zero otherwise.
1906 static int has_voicemail(const char *mailbox, const char *folder)
1908 char tmp[256], *tmp2, *box, *context;
1909 ast_copy_string(tmp, mailbox, sizeof(tmp));
1911 if (strchr(tmp2, ',')) {
1912 while ((box = strsep(&tmp2, ","))) {
1913 if (!ast_strlen_zero(box)) {
1914 if (has_voicemail(box, folder))
1919 if ((context= strchr(tmp, '@')))
1922 context = "default";
1923 return messagecount(context, tmp, folder) ? 1 : 0;
1927 * \brief Copies a message from one mailbox to another.
1937 * This works with IMAP storage based mailboxes.
1939 * \return zero on success, -1 on error.
1941 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)
1943 struct vm_state *sendvms = NULL, *destvms = NULL;
1944 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
1945 if (msgnum >= recip->maxmsg) {
1946 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
1949 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
1950 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
1953 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
1954 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
1957 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
1958 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
1960 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
1964 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
1966 char tmp[256], *t = tmp;
1967 size_t left = sizeof(tmp);
1969 if (box == OLD_FOLDER) {
1970 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
1972 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
1975 if (box == NEW_FOLDER) {
1976 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
1978 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
1981 /* Build up server information */
1982 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
1984 /* Add authentication user if present */
1985 if (!ast_strlen_zero(authuser))
1986 ast_build_string(&t, &left, "/authuser=%s", authuser);
1988 /* Add flags if present */
1989 if (!ast_strlen_zero(imapflags))
1990 ast_build_string(&t, &left, "/%s", imapflags);
1992 /* End with username */
1993 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
1994 if (box == NEW_FOLDER || box == OLD_FOLDER)
1995 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
1996 else if (box == GREETINGS_FOLDER)
1997 snprintf(spec, len, "%s%s", tmp, greetingfolder);
1998 else { /* Other folders such as Friends, Family, etc... */
1999 if (!ast_strlen_zero(imapparentfolder)) {
2000 /* imapparentfolder would typically be set to INBOX */
2001 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2003 snprintf(spec, len, "%s%s", tmp, mbox(box));
2008 static int init_mailstream(struct vm_state *vms, int box)
2010 MAILSTREAM *stream = NIL;
2015 ast_log (LOG_ERROR,"vm_state is NULL!\n");
2018 if (option_debug > 2)
2019 ast_log (LOG_DEBUG,"vm_state user is:%s\n",vms->imapuser);
2020 if (vms->mailstream == NIL || !vms->mailstream) {
2022 ast_log (LOG_DEBUG,"mailstream not set.\n");
2024 stream = vms->mailstream;
2026 /* debug = T; user wants protocol telemetry? */
2027 debug = NIL; /* NO protocol telemetry? */
2029 if (delimiter == '\0') { /* did not probe the server yet */
2031 #ifdef USE_SYSTEM_IMAP
2032 #include <imap/linkage.c>
2033 #elif defined(USE_SYSTEM_CCLIENT)
2034 #include <c-client/linkage.c>
2036 #include "linkage.c"
2038 /* Connect to INBOX first to get folders delimiter */
2039 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2040 ast_mutex_lock(&vms->lock);
2041 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2042 ast_mutex_unlock(&vms->lock);
2043 if (stream == NIL) {
2044 ast_log (LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2047 get_mailbox_delimiter(stream);
2048 /* update delimiter in imapfolder */
2049 for (cp = imapfolder; *cp; cp++)
2053 /* Now connect to the target folder */
2054 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2055 if (option_debug > 2)
2056 ast_log (LOG_DEBUG,"Before mail_open, server: %s, box:%d\n", tmp, box);
2057 ast_mutex_lock(&vms->lock);
2058 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2059 ast_mutex_unlock(&vms->lock);
2060 if (vms->mailstream == NIL) {
2067 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2071 int ret, urgent = 0;
2073 /* If Urgent, then look at INBOX */
2079 ast_copy_string(vms->imapuser,vmu->imapuser, sizeof(vms->imapuser));
2080 ast_debug(3,"Before init_mailstream, user is %s\n",vmu->imapuser);
2082 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2083 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2087 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2091 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2092 check_quota(vms,(char *)mbox(box));
2095 pgm = mail_newsearchpgm();
2097 /* Check IMAP folder for Asterisk messages only... */
2098 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", vmu->mailbox);
2103 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2104 if (box == NEW_FOLDER && urgent == 1) {
2109 } else if (box == NEW_FOLDER && urgent == 0) {
2114 } else if (box == OLD_FOLDER) {
2119 ast_debug(3,"Before mail_search_full, user is %s\n",vmu->imapuser);
2121 vms->vmArrayIndex = 0;
2122 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2123 vms->lastmsg = vms->vmArrayIndex - 1;
2124 mail_free_searchpgm(&pgm);
2129 static void write_file(char *filename, char *buffer, unsigned long len)
2133 output = fopen (filename, "w");
2134 fwrite (buffer, len, 1, output);
2138 static void update_messages_by_imapuser(const char *user, unsigned long number)
2140 struct vmstate *vlist = NULL;
2142 AST_LIST_LOCK(&vmstates);
2143 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2145 ast_debug(3, "error: vms is NULL for %s\n", user);
2148 if (!vlist->vms->imapuser) {
2149 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2152 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vlist->vms->vmArrayIndex, vlist->vms->interactive);
2153 vlist->vms->msgArray[vlist->vms->vmArrayIndex++] = number;
2155 AST_LIST_UNLOCK(&vmstates);
2158 void mm_searched(MAILSTREAM *stream, unsigned long number)
2160 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2162 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2165 update_messages_by_imapuser(user, number);
2168 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2170 struct ast_variable *var;
2171 struct ast_vm_user *vmu;
2173 vmu = ast_calloc(1, sizeof *vmu);
2176 ast_set_flag(vmu, VM_ALLOCED);
2177 populate_defaults(vmu);
2179 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2181 apply_options_full(vmu, var);
2182 ast_variables_destroy(var);
2190 /* Interfaces to C-client */
2192 void mm_exists(MAILSTREAM * stream, unsigned long number)
2194 /* mail_ping will callback here if new mail! */
2195 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2196 if (number == 0) return;
2201 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2203 /* mail_ping will callback here if expunged mail! */
2204 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2205 if (number == 0) return;
2210 void mm_flags(MAILSTREAM * stream, unsigned long number)
2212 /* mail_ping will callback here if read mail! */
2213 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2214 if (number == 0) return;
2219 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2221 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2222 mm_log (string, errflg);
2226 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2228 if (delimiter == '\0') {
2232 ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2233 if (attributes & LATT_NOINFERIORS)
2234 ast_debug(5, "no inferiors\n");
2235 if (attributes & LATT_NOSELECT)
2236 ast_debug(5, "no select\n");
2237 if (attributes & LATT_MARKED)
2238 ast_debug(5, "marked\n");
2239 if (attributes & LATT_UNMARKED)
2240 ast_debug(5, "unmarked\n");
2244 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2246 ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2247 if (attributes & LATT_NOINFERIORS)
2248 ast_debug(5, "no inferiors\n");
2249 if (attributes & LATT_NOSELECT)
2250 ast_debug(5, "no select\n");
2251 if (attributes & LATT_MARKED)
2252 ast_debug(5, "marked\n");
2253 if (attributes & LATT_UNMARKED)
2254 ast_debug(5, "unmarked\n");
2258 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2260 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2261 if (status->flags & SA_MESSAGES)
2262 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2263 if (status->flags & SA_RECENT)
2264 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2265 if (status->flags & SA_UNSEEN)
2266 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2267 if (status->flags & SA_UIDVALIDITY)
2268 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2269 if (status->flags & SA_UIDNEXT)
2270 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2271 ast_log(AST_LOG_NOTICE, "\n");
2275 void mm_log(char *string, long errflg)
2277 switch ((short) errflg) {
2279 ast_debug(1,"IMAP Info: %s\n", string);
2283 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2286 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2292 void mm_dlog(char *string)
2294 ast_log(AST_LOG_NOTICE, "%s\n", string);
2298 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2300 struct ast_vm_user *vmu;
2302 ast_debug(4, "Entering callback mm_login\n");
2304 ast_copy_string(user, mb->user, MAILTMPLEN);
2306 /* We should only do this when necessary */
2307 if (!ast_strlen_zero(authpassword)) {
2308 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2310 AST_LIST_TRAVERSE(&users, vmu, list) {
2311 if (!strcasecmp(mb->user, vmu->imapuser)) {
2312 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2317 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2318 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2326 void mm_critical(MAILSTREAM * stream)
2331 void mm_nocritical(MAILSTREAM * stream)
2336 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2338 kill (getpid (), SIGSTOP);
2343 void mm_fatal(char *string)
2345 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2348 /* C-client callback to handle quota */
2349 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2351 struct vm_state *vms;
2352 char *mailbox = stream->mailbox, *user;
2353 char buf[1024] = "";
2354 unsigned long usage = 0, limit = 0;
2357 usage = pquota->usage;
2358 limit = pquota->limit;
2359 pquota = pquota->next;
2362 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 2))) {
2363 ast_log(AST_LOG_ERROR, "No state found.\n");
2367 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2369 vms->quota_usage = usage;
2370 vms->quota_limit = limit;
2373 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2375 char *start, *eol_pnt;
2378 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2381 taglen = strlen(tag) + 1;
2385 if (!(start = strstr(header, tag)))
2388 /* Since we can be called multiple times we should clear our buffer */
2389 memset(buf, 0, len);
2391 ast_copy_string(buf, start+taglen, len);
2392 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2397 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2399 char *start, *quote, *eol_pnt;
2401 if (ast_strlen_zero(mailbox))
2404 if (!(start = strstr(mailbox, "/user=")))
2407 ast_copy_string(buf, start+6, len);
2409 if (!(quote = strchr(buf, '\"'))) {
2410 if (!(eol_pnt = strchr(buf, '/')))
2411 eol_pnt = strchr(buf,'}');
2415 eol_pnt = strchr(buf+1,'\"');
2421 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2423 struct vm_state *vms_p;
2425 if (option_debug > 4)
2426 ast_log(AST_LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
2427 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2429 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2430 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2431 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2432 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2433 if (option_debug > 4)
2434 ast_log(AST_LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2436 /* set mailbox to INBOX! */
2437 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2438 init_vm_state(vms_p);
2439 vmstate_insert(vms_p);
2443 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive)
2445 struct vmstate *vlist = NULL;
2447 AST_LIST_LOCK(&vmstates);
2448 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2450 ast_debug(3, "error: vms is NULL for %s\n", user);
2453 if (!vlist->vms->imapuser) {
2454 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2458 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2459 AST_LIST_UNLOCK(&vmstates);
2463 AST_LIST_UNLOCK(&vmstates);
2465 ast_debug(3, "%s not found in vmstates\n", user);
2470 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2473 struct vmstate *vlist = NULL;
2474 const char *local_context = S_OR(context, "default");
2476 AST_LIST_LOCK(&vmstates);
2477 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2479 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2482 if (!vlist->vms->username || !vlist->vms->context) {
2483 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2487 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);
2489 if (!strcmp(vlist->vms->username,mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2490 ast_debug(3, "Found it!\n");
2491 AST_LIST_UNLOCK(&vmstates);
2495 AST_LIST_UNLOCK(&vmstates);
2497 ast_debug(3, "%s not found in vmstates\n", mailbox);
2502 static void vmstate_insert(struct vm_state *vms)
2505 struct vm_state *altvms;
2507 /* If interactive, it probably already exists, and we should
2508 use the one we already have since it is more up to date.
2509 We can compare the username to find the duplicate */
2510 if (vms->interactive == 1) {
2511 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2513 ast_debug(3, "Duplicate mailbox %s, copying message info...\n",vms->username);
2514 vms->newmessages = altvms->newmessages;
2515 vms->oldmessages = altvms->oldmessages;
2516 vms->vmArrayIndex = altvms->vmArrayIndex;
2517 vms->lastmsg = altvms->lastmsg;
2518 vms->curmsg = altvms->curmsg;
2519 /* get a pointer to the persistent store */
2520 vms->persist_vms = altvms;
2521 /* Reuse the mailstream? */
2522 vms->mailstream = altvms->mailstream;
2523 /* vms->mailstream = NIL; */
2527 if (!(v = ast_calloc(1, sizeof(*v))))
2532 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
2534 AST_LIST_LOCK(&vmstates);
2535 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2536 AST_LIST_UNLOCK(&vmstates);
2539 static void vmstate_delete(struct vm_state *vms)
2541 struct vmstate *vc = NULL;
2542 struct vm_state *altvms = NULL;
2544 /* If interactive, we should copy pertinent info
2545 back to the persistent state (to make update immediate) */
2546 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2547 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2548 altvms->newmessages = vms->newmessages;
2549 altvms->oldmessages = vms->oldmessages;
2550 altvms->updated = 1;
2553 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2555 AST_LIST_LOCK(&vmstates);
2556 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2557 if (vc->vms == vms) {
2558 AST_LIST_REMOVE_CURRENT(list);
2562 AST_LIST_TRAVERSE_SAFE_END
2563 AST_LIST_UNLOCK(&vmstates);
2566 ast_mutex_destroy(&vc->vms->lock);
2570 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2573 static void set_update(MAILSTREAM * stream)
2575 struct vm_state *vms;
2576 char *mailbox = stream->mailbox, *user;
2577 char buf[1024] = "";
2579 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2580 if (user && option_debug > 2)
2581 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2585 ast_debug(3, "User %s mailbox set for update.\n", user);
2587 vms->updated = 1; /* Set updated flag since mailbox changed */
2590 static void init_vm_state(struct vm_state *vms)
2593 vms->vmArrayIndex = 0;
2594 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2595 vms->msgArray[x] = 0;
2597 ast_mutex_init(&vms->lock);
2600 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
2604 char *fn = is_intro ? vms->introfn : vms->fn;
2606 unsigned long newlen;
2609 if (!body || body == NIL)
2612 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2613 if (body_content != NIL) {
2614 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2615 /* ast_debug(1,body_content); */
2616 body_decoded = rfc822_base64((unsigned char *)body_content, len, &newlen);
2617 /* If the body of the file is empty, return an error */
2621 write_file(filename, (char *) body_decoded, newlen);
2623 ast_debug(5, "Body of message is NULL.\n");
2630 * \brief Get delimiter via mm_list callback
2633 * Determines the delimiter character that is used by the underlying IMAP based mail store.
2635 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2637 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2638 mail_list(stream, tmp, "*");
2642 * \brief Check Quota for user
2643 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2644 * \param mailbox the mailbox to check the quota for.
2646 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2648 static void check_quota(struct vm_state *vms, char *mailbox) {
2649 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2650 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2651 if (vms && vms->mailstream != NULL) {
2652 imap_getquotaroot(vms->mailstream, mailbox);
2654 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2658 #endif /* IMAP_STORAGE */
2660 /*! \brief Lock file path
2661 only return failure if ast_lock_path returns 'timeout',
2662 not if the path does not exist or any other reason
2664 static int vm_lock_path(const char *path)
2666 switch (ast_lock_path(path)) {
2667 case AST_LOCK_TIMEOUT:
2676 struct generic_prepare_struct {
2682 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2684 struct generic_prepare_struct *gps = data;
2688 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2689 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2690 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2693 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
2694 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2695 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2696 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2699 for (i = 0; i < gps->argc; i++)
2700 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2706 * \brief Retrieves a file from an ODBC data store.
2707 * \param dir the path to the file to be retreived.
2708 * \param msgnum the message number, such as within a mailbox folder.
2710 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2711 * The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
2713 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2714 * The output is the message information file with the name msgnum and the extension .txt
2715 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2717 * \return 0 on success, -1 on error.
2719 static int retrieve_file(char *dir, int msgnum)
2725 void *fdm = MAP_FAILED;
2726 SQLSMALLINT colcount=0;
2733 SQLSMALLINT datatype;
2734 SQLSMALLINT decimaldigits;
2735 SQLSMALLINT nullable;
2741 char full_fn[PATH_MAX];
2743 char *argv[] = { dir, msgnums };
2744 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2746 struct odbc_obj *obj;
2747 obj = ast_odbc_request_obj(odbc_database, 0);
2749 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2750 c = strchr(fmt, '|');
2753 if (!strcasecmp(fmt, "wav49"))
2755 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
2757 make_file(fn, sizeof(fn), dir, msgnum);
2759 ast_copy_string(fn, dir, sizeof(fn));
2761 /* Create the information file */
2762 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2764 if (!(f = fopen(full_fn, "w+"))) {
2765 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2769 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2770 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2771 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2773 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2774 ast_odbc_release_obj(obj);
2777 res = SQLFetch(stmt);
2778 if (res == SQL_NO_DATA) {
2779 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2780 ast_odbc_release_obj(obj);
2782 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2783 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2784 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2785 ast_odbc_release_obj(obj);
2788 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2790 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2791 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2792 ast_odbc_release_obj(obj);
2795 res = SQLNumResultCols(stmt, &colcount);
2796 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2797 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2798 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2799 ast_odbc_release_obj(obj);
2803 fprintf(f, "[message]\n");
2804 for (x=0;x<colcount;x++) {
2806 collen = sizeof(coltitle);
2807 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
2808 &datatype, &colsize, &decimaldigits, &nullable);
2809 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2810 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
2811 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2812 ast_odbc_release_obj(obj);
2815 if (!strcasecmp(coltitle, "recording")) {
2817 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
2821 lseek(fd, fdlen - 1, SEEK_SET);
2822 if (write(fd, tmp, 1) != 1) {
2827 /* Read out in small chunks */
2828 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
2829 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
2830 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
2831 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2832 ast_odbc_release_obj(obj);
2835 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
2836 munmap(fdm, CHUNKSIZE);
2837 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2838 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2840 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2841 ast_odbc_release_obj(obj);
2846 truncate(full_fn, fdlen);
2849 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2850 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2851 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
2852 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2853 ast_odbc_release_obj(obj);
2856 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
2857 fprintf(f, "%s=%s\n", coltitle, rowdata);
2860 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2861 ast_odbc_release_obj(obj);
2863 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2873 * \brief Determines the highest message number in use for a given user and mailbox folder.
2875 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
2877 * This method is used when mailboxes are stored in an ODBC back end.
2878 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
2880 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
2882 static int last_message_index(struct ast_vm_user *vmu, char *dir)
2889 char *argv[] = { dir };
2890 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
2892 struct odbc_obj *obj;
2893 obj = ast_odbc_request_obj(odbc_database, 0);
2895 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
2896 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2898 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2899 ast_odbc_release_obj(obj);
2902 res = SQLFetch(stmt);
2903 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2904 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2905 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2906 ast_odbc_release_obj(obj);
2909 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2910 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2911 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2912 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2913 ast_odbc_release_obj(obj);
2916 if (sscanf(rowdata, "%d", &x) != 1)
2917 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
2918 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2919 ast_odbc_release_obj(obj);
2921 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2927 * \brief Determines if the specified message exists.
2928 * \param dir the folder the mailbox folder to look for messages.
2929 * \param msgnum the message index to query for.
2931 * This method is used when mailboxes are stored in an ODBC back end.
2933 * \return greater than zero if the message exists, zero when the message does not exist or on error.
2935 static int message_exists(char *dir, int msgnum)
2943 char *argv[] = { dir, msgnums };
2944 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2946 struct odbc_obj *obj;
2947 obj = ast_odbc_request_obj(odbc_database, 0);
2949 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
2950 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2951 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2953 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2954 ast_odbc_release_obj(obj);
2957 res = SQLFetch(stmt);
2958 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2959 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2960 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2961 ast_odbc_release_obj(obj);
2964 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2965 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2966 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2967 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2968 ast_odbc_release_obj(obj);
2971 if (sscanf(rowdata, "%d", &x) != 1)
2972 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
2973 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2974 ast_odbc_release_obj(obj);
2976 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2982 * \brief returns the one-based count for messages.
2984 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
2986 * This method is used when mailboxes are stored in an ODBC back end.
2987 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
2988 * one-based messages.
2989 * This method just calls last_message_index and returns +1 of its value.
2991 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
2993 static int count_messages(struct ast_vm_user *vmu, char *dir)
2995 return last_message_index(vmu, dir) + 1;
2999 * \brief Deletes a message from the mailbox folder.
3000 * \param sdir The mailbox folder to work in.
3001 * \param smsg The message index to be deleted.
3003 * This method is used when mailboxes are stored in an ODBC back end.
3004 * The specified message is directly deleted from the database 'voicemessages' table.
3006 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3008 static void delete_file(char *sdir, int smsg)
3013 char *argv[] = { sdir, msgnums };
3014 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3016 struct odbc_obj *obj;
3017 obj = ast_odbc_request_obj(odbc_database, 0);
3019 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3020 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3021 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3023 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3025 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3026 ast_odbc_release_obj(obj);
3028 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3033 * \brief Copies a voicemail from one mailbox to another.
3034 * \param sdir the folder for which to look for the message to be copied.
3035 * \param smsg the index of the message to be copied.
3036 * \param ddir the destination folder to copy the message into.
3037 * \param dmsg the index to be used for the copied message.
3038 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3039 * \param dmailboxcontext The context for the destination user.
3041 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3043 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3049 struct odbc_obj *obj;
3050 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3051 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3053 delete_file(ddir, dmsg);
3054 obj = ast_odbc_request_obj(odbc_database, 0);
3056 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3057 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3058 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, context, macrocontext, callerid, origtime, duration, recording, mailboxuser, mailboxcontext, flag) SELECT ?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?",odbc_table,odbc_table);
3059 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3061 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3063 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3064 ast_odbc_release_obj(obj);
3066 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3070 struct insert_data {
3077 const char *context;
3078 const char *macrocontext;
3079 const char *callerid;
3080 const char *origtime;
3081 const char *duration;
3083 char *mailboxcontext;
3084 const char *category;
3088 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3090 struct insert_data *data = vdata;
3094 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3095 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3096 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3097 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3101 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *)data->dir, 0, NULL);
3102 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *)data->msgnums, 0, NULL);
3103 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *)data->data, data->datalen, &data->indlen);
3104 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *)data->context, 0, NULL);
3105 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *)data->macrocontext, 0, NULL);
3106 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *)data->callerid, 0, NULL);
3107 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *)data->origtime, 0, NULL);
3108 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *)data->duration, 0, NULL);
3109 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *)data->mailboxuser, 0, NULL);
3110 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *)data->mailboxcontext, 0, NULL);
3111 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *)data->flag, 0, NULL);
3112 if (!ast_strlen_zero(data->category)) {
3113 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *)data->category, 0, NULL);
3115 res = SQLExecDirect(stmt, (unsigned char *)data->sql, SQL_NTS);
3116 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3117 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3118 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3126 * \brief Stores a voicemail into the database.
3127 * \param dir the folder the mailbox folder to store the message.
3128 * \param mailboxuser the user owning the mailbox folder.
3129 * \param mailboxcontext
3130 * \param msgnum the message index for the message to be stored.
3132 * This method is used when mailboxes are stored in an ODBC back end.
3133 * The message sound file and information file is looked up on the file system.
3134 * A SQL query is invoked to store the message into the (MySQL) database.
3136 * \return the zero on success -1 on error.
3138 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3142 void *fdm = MAP_FAILED;
3148 char full_fn[PATH_MAX];
3151 struct ast_config *cfg=NULL;
3152 struct odbc_obj *obj;
3153 struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext };
3154 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3156 delete_file(dir, msgnum);
3157 if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3158 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3163 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3164 c = strchr(fmt, '|');
3167 if (!strcasecmp(fmt, "wav49"))
3169 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
3171 make_file(fn, sizeof(fn), dir, msgnum);
3173 ast_copy_string(fn, dir, sizeof(fn));
3174 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3175 cfg = ast_config_load(full_fn, config_flags);
3176 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3177 fd = open(full_fn, O_RDWR);
3179 ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3183 if (cfg && cfg != CONFIG_STATUS_FILEINVALID) {
3184 if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3187 if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3188 idata.macrocontext = "";
3190 if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {