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</depend>
52 <conflict>IMAP_STORAGE</conflict>
53 <defaultenabled>no</defaultenabled>
55 <member name="IMAP_STORAGE" displayname="Storage of Voicemail using IMAP4">
56 <depend>imap_tk</depend>
57 <conflict>ODBC_STORAGE</conflict>
59 <defaultenabled>no</defaultenabled>
64 /* It is important to include the IMAP_STORAGE related headers
65 * before asterisk.h since asterisk.h includes logger.h. logger.h
66 * and c-client.h have conflicting definitions for AST_LOG_WARNING and
67 * AST_LOG_DEBUG, so it's important that we use Asterisk's definitions
68 * here instead of the c-client's
74 #ifdef USE_SYSTEM_IMAP
75 #include <imap/c-client.h>
76 #include <imap/imap4r1.h>
77 #include <imap/linkage.h>
78 #elif defined (USE_SYSTEM_CCLIENT)
79 #include <c-client/c-client.h>
80 #include <c-client/imap4r1.h>
81 #include <c-client/linkage.h>
91 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
93 #include "asterisk/paths.h" /* use ast_config_AST_SPOOL_DIR */
100 #include "asterisk/lock.h"
101 #include "asterisk/file.h"
102 #include "asterisk/channel.h"
103 #include "asterisk/pbx.h"
104 #include "asterisk/config.h"
105 #include "asterisk/say.h"
106 #include "asterisk/module.h"
107 #include "asterisk/adsi.h"
108 #include "asterisk/app.h"
109 #include "asterisk/manager.h"
110 #include "asterisk/dsp.h"
111 #include "asterisk/localtime.h"
112 #include "asterisk/cli.h"
113 #include "asterisk/utils.h"
114 #include "asterisk/stringfields.h"
115 #include "asterisk/smdi.h"
116 #include "asterisk/event.h"
117 #include "asterisk/taskprocessor.h"
120 #include "asterisk/res_odbc.h"
124 static char imapserver[48];
125 static char imapport[8];
126 static char imapflags[128];
127 static char imapfolder[64];
128 static char imapparentfolder[64] = "\0";
129 static char greetingfolder[64];
130 static char authuser[32];
131 static char authpassword[42];
133 static int expungeonhangup = 1;
134 static int imapgreetings = 0;
135 static char delimiter = '\0';
140 /* Forward declarations for IMAP */
141 static int init_mailstream(struct vm_state *vms, int box);
142 static void write_file(char *filename, char *buffer, unsigned long len);
143 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len);
144 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu);
145 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len);
146 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive);
147 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive);
148 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu);
149 static void vmstate_insert(struct vm_state *vms);
150 static void vmstate_delete(struct vm_state *vms);
151 static void set_update(MAILSTREAM * stream);
152 static void init_vm_state(struct vm_state *vms);
153 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro);
154 static void get_mailbox_delimiter(MAILSTREAM *stream);
155 static void mm_parsequota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota);
156 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int target);
157 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);
158 static void update_messages_by_imapuser(const char *user, unsigned long number);
160 static int imap_remove_file (char *dir, int msgnum);
161 static int imap_retrieve_file (const char *dir, const int msgnum, const char *mailbox, const char *context);
162 static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
163 static void check_quota(struct vm_state *vms, char *mailbox);
164 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
166 struct vm_state *vms;
167 AST_LIST_ENTRY(vmstate) list;
170 static AST_LIST_HEAD_STATIC(vmstates, vmstate);
174 #define SMDI_MWI_WAIT_TIMEOUT 1000 /* 1 second */
176 #define COMMAND_TIMEOUT 5000
177 /* Don't modify these here; set your umask at runtime instead */
178 #define VOICEMAIL_DIR_MODE 0777
179 #define VOICEMAIL_FILE_MODE 0666
180 #define CHUNKSIZE 65536
182 #define VOICEMAIL_CONFIG "voicemail.conf"
183 #define ASTERISK_USERNAME "asterisk"
185 /* Define fast-forward, pause, restart, and reverse keys
186 while listening to a voicemail message - these are
187 strings, not characters */
188 #define DEFAULT_LISTEN_CONTROL_FORWARD_KEY "#"
189 #define DEFAULT_LISTEN_CONTROL_REVERSE_KEY "*"
190 #define DEFAULT_LISTEN_CONTROL_PAUSE_KEY "0"
191 #define DEFAULT_LISTEN_CONTROL_RESTART_KEY "2"
192 #define DEFAULT_LISTEN_CONTROL_STOP_KEY "13456789"
193 #define VALID_DTMF "1234567890*#" /* Yes ABCD are valid dtmf but what phones have those? */
195 /* Default mail command to mail voicemail. Change it with the
196 mailcmd= command in voicemail.conf */
197 #define SENDMAIL "/usr/sbin/sendmail -t"
199 #define INTRO "vm-intro"
202 #define MAXMSGLIMIT 9999
204 #define MINPASSWORD 0 /*!< Default minimum mailbox password length */
206 #define BASELINELEN 72
207 #define BASEMAXINLINE 256
210 #define MAX_DATETIME_FORMAT 512
211 #define MAX_NUM_CID_CONTEXTS 10
213 #define VM_REVIEW (1 << 0) /*!< After recording, permit the caller to review the recording before saving */
214 #define VM_OPERATOR (1 << 1) /*!< Allow 0 to be pressed to go to 'o' extension */
215 #define VM_SAYCID (1 << 2) /*!< Repeat the CallerID info during envelope playback */
216 #define VM_SVMAIL (1 << 3) /*!< Allow the user to compose a new VM from within VoicemailMain */
217 #define VM_ENVELOPE (1 << 4) /*!< Play the envelope information (who-from, time received, etc.) */
218 #define VM_SAYDURATION (1 << 5) /*!< Play the length of the message during envelope playback */
219 #define VM_SKIPAFTERCMD (1 << 6) /*!< After deletion, assume caller wants to go to the next message */
220 #define VM_FORCENAME (1 << 7) /*!< Have new users record their name */
221 #define VM_FORCEGREET (1 << 8) /*!< Have new users record their greetings */
222 #define VM_PBXSKIP (1 << 9) /*!< Skip the [PBX] preamble in the Subject line of emails */
223 #define VM_DIRECFORWARD (1 << 10) /*!< Permit caller to use the Directory app for selecting to which mailbox to forward a VM */
224 #define VM_ATTACH (1 << 11) /*!< Attach message to voicemail notifications? */
225 #define VM_DELETE (1 << 12) /*!< Delete message after sending notification */
226 #define VM_ALLOCED (1 << 13) /*!< Structure was malloc'ed, instead of placed in a return (usually static) buffer */
227 #define VM_SEARCH (1 << 14) /*!< Search all contexts for a matching mailbox */
228 #define VM_TEMPGREETWARN (1 << 15) /*!< Remind user tempgreeting is set */
229 #define VM_MOVEHEARD (1 << 16) /*!< Move a "heard" message to Old after listening to it */
230 #define VM_MESSAGEWRAP (1 << 17) /*!< Wrap around from the last message to the first, and vice-versa */
231 #define ERROR_LOCK_PATH -100
244 OPT_SILENT = (1 << 0),
245 OPT_BUSY_GREETING = (1 << 1),
246 OPT_UNAVAIL_GREETING = (1 << 2),
247 OPT_RECORDGAIN = (1 << 3),
248 OPT_PREPEND_MAILBOX = (1 << 4),
249 OPT_AUTOPLAY = (1 << 6),
250 OPT_DTMFEXIT = (1 << 7),
251 OPT_MESSAGE_Urgent = (1 << 8),
252 OPT_MESSAGE_PRIORITY = (1 << 9)
256 OPT_ARG_RECORDGAIN = 0,
257 OPT_ARG_PLAYFOLDER = 1,
258 OPT_ARG_DTMFEXIT = 2,
259 /* This *must* be the last value in this enum! */
260 OPT_ARG_ARRAY_SIZE = 3,
263 AST_APP_OPTIONS(vm_app_options, {
264 AST_APP_OPTION('s', OPT_SILENT),
265 AST_APP_OPTION('b', OPT_BUSY_GREETING),
266 AST_APP_OPTION('u', OPT_UNAVAIL_GREETING),
267 AST_APP_OPTION_ARG('g', OPT_RECORDGAIN, OPT_ARG_RECORDGAIN),
268 AST_APP_OPTION_ARG('d', OPT_DTMFEXIT, OPT_ARG_DTMFEXIT),
269 AST_APP_OPTION('p', OPT_PREPEND_MAILBOX),
270 AST_APP_OPTION_ARG('a', OPT_AUTOPLAY, OPT_ARG_PLAYFOLDER),
271 AST_APP_OPTION('U', OPT_MESSAGE_Urgent),
272 AST_APP_OPTION('P', OPT_MESSAGE_PRIORITY)
275 static int load_config(int reload);
277 /*! \page vmlang Voicemail Language Syntaxes Supported
279 \par Syntaxes supported, not really language codes.
286 \arg \b pt - Portuguese
287 \arg \b pt_BR - Portuguese (Brazil)
289 \arg \b no - Norwegian
291 \arg \b tw - Chinese (Taiwan)
292 \arg \b ua - Ukrainian
294 German requires the following additional soundfile:
295 \arg \b 1F einE (feminine)
297 Spanish requires the following additional soundfile:
298 \arg \b 1M un (masculine)
300 Dutch, Portuguese & Spanish require the following additional soundfiles:
301 \arg \b vm-INBOXs singular of 'new'
302 \arg \b vm-Olds singular of 'old/heard/read'
305 \arg \b vm-INBOX nieuwe (nl)
306 \arg \b vm-Old oude (nl)
309 \arg \b vm-new-a 'new', feminine singular accusative
310 \arg \b vm-new-e 'new', feminine plural accusative
311 \arg \b vm-new-ych 'new', feminine plural genitive
312 \arg \b vm-old-a 'old', feminine singular accusative
313 \arg \b vm-old-e 'old', feminine plural accusative
314 \arg \b vm-old-ych 'old', feminine plural genitive
315 \arg \b digits/1-a 'one', not always same as 'digits/1'
316 \arg \b digits/2-ie 'two', not always same as 'digits/2'
319 \arg \b vm-nytt singular of 'new'
320 \arg \b vm-nya plural of 'new'
321 \arg \b vm-gammalt singular of 'old'
322 \arg \b vm-gamla plural of 'old'
323 \arg \b digits/ett 'one', not always same as 'digits/1'
326 \arg \b vm-ny singular of 'new'
327 \arg \b vm-nye plural of 'new'
328 \arg \b vm-gammel singular of 'old'
329 \arg \b vm-gamle plural of 'old'
337 Ukrainian requires the following additional soundfile:
338 \arg \b vm-nove 'nove'
339 \arg \b vm-stare 'stare'
340 \arg \b digits/ua/1e 'odne'
342 Italian requires the following additional soundfile:
346 \arg \b vm-nuovi new plural
347 \arg \b vm-vecchio old
348 \arg \b vm-vecchi old plural
350 Chinese (Taiwan) requires the following additional soundfile:
351 \arg \b vm-tong A class-word for call (tong1)
352 \arg \b vm-ri A class-word for day (ri4)
353 \arg \b vm-you You (ni3)
354 \arg \b vm-haveno Have no (mei2 you3)
355 \arg \b vm-have Have (you3)
356 \arg \b vm-listen To listen (yao4 ting1)
359 \note Don't use vm-INBOX or vm-Old, because they are the name of the INBOX and Old folders,
360 spelled among others when you have to change folder. For the above reasons, vm-INBOX
361 and vm-Old are spelled plural, to make them sound more as folder name than an adjective.
370 unsigned char iobuf[BASEMAXINLINE];
373 /*! Structure for linked list of users
374 * Use ast_vm_user_destroy() to free one of these structures. */
376 char context[AST_MAX_CONTEXT]; /*!< Voicemail context */
377 char mailbox[AST_MAX_EXTENSION]; /*!< Mailbox id, unique within vm context */
378 char password[80]; /*!< Secret pin code, numbers only */
379 char fullname[80]; /*!< Full name, for directory app */
380 char email[80]; /*!< E-mail address */
381 char pager[80]; /*!< E-mail address to pager (no attachment) */
382 char serveremail[80]; /*!< From: Mail address */
383 char mailcmd[160]; /*!< Configurable mail command */
384 char language[MAX_LANGUAGE]; /*!< Config: Language setting */
385 char zonetag[80]; /*!< Time zone */
388 char uniqueid[80]; /*!< Unique integer identifier */
390 char attachfmt[20]; /*!< Attachment format */
391 unsigned int flags; /*!< VM_ flags */
393 int maxmsg; /*!< Maximum number of msgs per folder for this mailbox */
394 int maxdeletedmsg; /*!< Maximum number of deleted msgs saved for this mailbox */
395 int maxsecs; /*!< Maximum number of seconds per message for this mailbox */
397 char imapuser[80]; /*!< IMAP server login */
398 char imappassword[80]; /*!< IMAP server password if authpassword not defined */
400 double volgain; /*!< Volume gain for voicemails sent via email */
401 AST_LIST_ENTRY(ast_vm_user) list;
404 /*! Voicemail time zones */
406 AST_LIST_ENTRY(vm_zone) list;
409 char msg_format[512];
412 #define VMSTATE_MAX_MSG_ARRAY 256
414 /*! Voicemail mailbox state */
419 char curdir[PATH_MAX];
420 char vmbox[PATH_MAX];
422 char intro[PATH_MAX];
434 int updated; /*!< decremented on each mail check until 1 -allows delay */
435 long msgArray[VMSTATE_MAX_MSG_ARRAY];
436 MAILSTREAM *mailstream;
438 char imapuser[80]; /*!< IMAP server login */
440 char introfn[PATH_MAX]; /*!< Name of prepended file */
441 unsigned int quota_limit;
442 unsigned int quota_usage;
443 struct vm_state *persist_vms;
448 static char odbc_database[80];
449 static char odbc_table[80];
450 #define RETRIEVE(a,b,c,d) retrieve_file(a,b)
451 #define DISPOSE(a,b) remove_file(a,b)
452 #define STORE(a,b,c,d,e,f,g,h,i,j) store_file(a,b,c,d)
453 #define EXISTS(a,b,c,d) (message_exists(a,b))
454 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(a,b,c,d,e,f))
455 #define COPY(a,b,c,d,e,f,g,h) (copy_file(a,b,c,d,e,f))
456 #define DELETE(a,b,c,d) (delete_file(a,b))
459 #define DISPOSE(a,b) (imap_remove_file(a,b))
460 #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))
461 #define RETRIEVE(a,b,c,d) imap_retrieve_file(a,b,c,d)
462 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
463 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
464 #define COPY(a,b,c,d,e,f,g,h) (copy_file(g,h));
465 #define DELETE(a,b,c,d) (vm_imap_delete(b,d))
467 #define RETRIEVE(a,b,c,d)
469 #define STORE(a,b,c,d,e,f,g,h,i,j)
470 #define EXISTS(a,b,c,d) (ast_fileexists(c,NULL,d) > 0)
471 #define RENAME(a,b,c,d,e,f,g,h) (rename_file(g,h));
472 #define COPY(a,b,c,d,e,f,g,h) (copy_plain_file(g,h));
473 #define DELETE(a,b,c,d) (vm_delete(c))
477 static char VM_SPOOL_DIR[PATH_MAX];
479 static char ext_pass_cmd[128];
480 static char ext_pass_check_cmd[128];
484 #define PWDCHANGE_INTERNAL (1 << 1)
485 #define PWDCHANGE_EXTERNAL (1 << 2)
486 static int pwdchange = PWDCHANGE_INTERNAL;
489 #define tdesc "Comedian Mail (Voicemail System) with ODBC Storage"
492 # define tdesc "Comedian Mail (Voicemail System) with IMAP Storage"
494 # define tdesc "Comedian Mail (Voicemail System)"
498 static char userscontext[AST_MAX_EXTENSION] = "default";
500 static char *addesc = "Comedian Mail";
502 static char *synopsis_vm = "Leave a Voicemail message";
504 static char *descrip_vm =
505 " VoiceMail(mailbox[@context][&mailbox[@context]][...][,options]): This\n"
506 "application allows the calling party to leave a message for the specified\n"
507 "list of mailboxes. When multiple mailboxes are specified, the greeting will\n"
508 "be taken from the first mailbox specified. Dialplan execution will stop if the\n"
509 "specified mailbox does not exist.\n"
510 " The Voicemail application will exit if any of the following DTMF digits are\n"
512 " 0 - Jump to the 'o' extension in the current dialplan context.\n"
513 " * - Jump to the 'a' extension in the current dialplan context.\n"
514 " This application will set the following channel variable upon completion:\n"
515 " VMSTATUS - This indicates the status of the execution of the VoiceMail\n"
516 " application. The possible values are:\n"
517 " SUCCESS | USEREXIT | FAILED\n\n"
519 " b - Play the 'busy' greeting to the calling party.\n"
520 " d([c]) - Accept digits for a new extension in context c, if played during\n"
521 " the greeting. Context defaults to the current context.\n"
522 " g(#) - Use the specified amount of gain when recording the voicemail\n"
523 " message. The units are whole-number decibels (dB).\n"
524 " Only works on supported technologies, which is DAHDI only.\n"
525 " s - Skip the playback of instructions for leaving a message to the\n"
527 " u - Play the 'unavailable' greeting.\n"
528 " U - Mark message as Urgent.\n"
529 " P - Mark message as PRIORITY.\n";
531 static char *synopsis_vmain = "Check Voicemail messages";
533 static char *descrip_vmain =
534 " VoiceMailMain([mailbox][@context][,options]): This application allows the\n"
535 "calling party to check voicemail messages. A specific mailbox, and optional\n"
536 "corresponding context, may be specified. If a mailbox is not provided, the\n"
537 "calling party will be prompted to enter one. If a context is not specified,\n"
538 "the 'default' context will be used.\n\n"
540 " p - Consider the mailbox parameter as a prefix to the mailbox that\n"
541 " is entered by the caller.\n"
542 " g(#) - Use the specified amount of gain when recording a voicemail\n"
543 " message. The units are whole-number decibels (dB).\n"
544 " s - Skip checking the passcode for the mailbox.\n"
545 " a(#) - Skip folder prompt and go directly to folder specified.\n"
546 " Defaults to INBOX\n";
548 static char *synopsis_vm_box_exists =
549 "Check to see if Voicemail mailbox exists";
551 static char *descrip_vm_box_exists =
552 " MailboxExists(mailbox[@context][,options]): Check to see if the specified\n"
553 "mailbox exists. If no voicemail context is specified, the 'default' context\n"
555 " This application will set the following channel variable upon completion:\n"
556 " VMBOXEXISTSSTATUS - This will contain the status of the execution of the\n"
557 " MailboxExists application. Possible values include:\n"
558 " SUCCESS | FAILED\n\n"
559 " Options: (none)\n";
561 static char *synopsis_vmauthenticate = "Authenticate with Voicemail passwords";
563 static char *descrip_vmauthenticate =
564 " VMAuthenticate([mailbox][@context][,options]): This application behaves the\n"
565 "same way as the Authenticate application, but the passwords are taken from\n"
567 " If the mailbox is specified, only that mailbox's password will be considered\n"
568 "valid. If the mailbox is not specified, the channel variable AUTH_MAILBOX will\n"
569 "be set with the authenticated mailbox.\n\n"
571 " s - Skip playing the initial prompts.\n";
573 /* Leave a message */
574 static char *app = "VoiceMail";
576 /* Check mail, control, etc */
577 static char *app2 = "VoiceMailMain";
579 static char *app3 = "MailboxExists";
580 static char *app4 = "VMAuthenticate";
582 static AST_LIST_HEAD_STATIC(users, ast_vm_user);
583 static AST_LIST_HEAD_STATIC(zones, vm_zone);
584 static int maxsilence;
586 static int maxdeletedmsg;
587 static int silencethreshold = 128;
588 static char serveremail[80];
589 static char mailcmd[160]; /* Configurable mail cmd */
590 static char externnotify[160];
591 static struct ast_smdi_interface *smdi_iface = NULL;
592 static char vmfmts[80];
593 static double volgain;
594 static int vmminsecs;
595 static int vmmaxsecs;
598 static int maxlogins;
599 static int minpassword;
601 /*! Poll mailboxes for changes since there is something external to
602 * app_voicemail that may change them. */
603 static unsigned int poll_mailboxes;
605 /*! Polling frequency */
606 static unsigned int poll_freq;
607 /*! By default, poll every 30 seconds */
608 #define DEFAULT_POLL_FREQ 30
610 AST_MUTEX_DEFINE_STATIC(poll_lock);
611 static ast_cond_t poll_cond = PTHREAD_COND_INITIALIZER;
612 static pthread_t poll_thread = AST_PTHREADT_NULL;
613 static unsigned char poll_thread_run;
615 /*! Subscription to ... MWI event subscriptions */
616 static struct ast_event_sub *mwi_sub_sub;
617 /*! Subscription to ... MWI event un-subscriptions */
618 static struct ast_event_sub *mwi_unsub_sub;
621 * \brief An MWI subscription
623 * This is so we can keep track of which mailboxes are subscribed to.
624 * This way, we know which mailboxes to poll when the pollmailboxes
625 * option is being used.
628 AST_RWLIST_ENTRY(mwi_sub) entry;
636 struct mwi_sub_task {
642 static struct ast_taskprocessor *mwi_subscription_tps;
644 static AST_RWLIST_HEAD_STATIC(mwi_subs, mwi_sub);
646 /* custom audio control prompts for voicemail playback */
647 static char listen_control_forward_key[12];
648 static char listen_control_reverse_key[12];
649 static char listen_control_pause_key[12];
650 static char listen_control_restart_key[12];
651 static char listen_control_stop_key[12];
653 /* custom password sounds */
654 static char vm_password[80] = "vm-password";
655 static char vm_newpassword[80] = "vm-newpassword";
656 static char vm_passchanged[80] = "vm-passchanged";
657 static char vm_reenterpassword[80] = "vm-reenterpassword";
658 static char vm_mismatch[80] = "vm-mismatch";
659 static char vm_invalid_password[80] = "vm-invalid-password";
661 static struct ast_flags globalflags = {0};
663 static int saydurationminfo;
665 static char dialcontext[AST_MAX_CONTEXT] = "";
666 static char callcontext[AST_MAX_CONTEXT] = "";
667 static char exitcontext[AST_MAX_CONTEXT] = "";
669 static char cidinternalcontexts[MAX_NUM_CID_CONTEXTS][64];
672 static char *emailbody = NULL;
673 static char *emailsubject = NULL;
674 static char *pagerbody = NULL;
675 static char *pagersubject = NULL;
676 static char fromstring[100];
677 static char pagerfromstring[100];
678 static char charset[32] = "ISO-8859-1";
680 static unsigned char adsifdn[4] = "\x00\x00\x00\x0F";
681 static unsigned char adsisec[4] = "\x9B\xDB\xF7\xAC";
682 static int adsiver = 1;
683 static char emaildateformat[32] = "%A, %B %d, %Y at %r";
685 /* Forward declarations - generic */
686 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
687 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);
688 static int dialout(struct ast_channel *chan, struct ast_vm_user *vmu, char *num, char *outgoing_context);
689 static int play_record_review(struct ast_channel *chan, char *playfile, char *recordfile, int maxtime,
690 char *fmt, int outsidecaller, struct ast_vm_user *vmu, int *duration, const char *unlockdir,
691 signed char record_gain, struct vm_state *vms, char *flag);
692 static int vm_tempgreeting(struct ast_channel *chan, struct ast_vm_user *vmu, struct vm_state *vms, char *fmtc, signed char record_gain);
693 static int vm_play_folder_name(struct ast_channel *chan, char *mbox);
694 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);
695 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);
696 static void apply_options(struct ast_vm_user *vmu, const char *options);
697 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);
698 static int is_valid_dtmf(const char *key);
700 #if !(defined(ODBC_STORAGE) || defined(IMAP_STORAGE))
701 static int __has_voicemail(const char *context, const char *mailbox, const char *folder, int shortcircuit);
704 static char *strip_control(const char *input, char *buf, size_t buflen)
707 for (; *input; input++) {
712 if (bufptr == buf + buflen - 1) {
722 * \brief Sets default voicemail system options to a voicemail user.
724 * This applies select global settings to a newly created (dynamic) instance of a voicemail user.
725 * - all the globalflags
726 * - the saydurationminfo
730 * - vmmaxsecs, vmmaxmsg, maxdeletedmsg
733 static void populate_defaults(struct ast_vm_user *vmu)
735 ast_copy_flags(vmu, (&globalflags), AST_FLAGS_ALL);
736 if (saydurationminfo)
737 vmu->saydurationm = saydurationminfo;
738 ast_copy_string(vmu->callback, callcontext, sizeof(vmu->callback));
739 ast_copy_string(vmu->dialout, dialcontext, sizeof(vmu->dialout));
740 ast_copy_string(vmu->exit, exitcontext, sizeof(vmu->exit));
742 vmu->maxsecs = vmmaxsecs;
744 vmu->maxmsg = maxmsg;
746 vmu->maxdeletedmsg = maxdeletedmsg;
747 vmu->volgain = volgain;
751 * \brief Sets a a specific property value.
752 * \param vmu The voicemail user object to work with.
753 * \param var The name of the property to be set.
754 * \param value The value to be set to the property.
756 * The property name must be one of the understood properties. See the source for details.
758 static void apply_option(struct ast_vm_user *vmu, const char *var, const char *value)
761 if (!strcasecmp(var, "attach")) {
762 ast_set2_flag(vmu, ast_true(value), VM_ATTACH);
763 } else if (!strcasecmp(var, "attachfmt")) {
764 ast_copy_string(vmu->attachfmt, value, sizeof(vmu->attachfmt));
765 } else if (!strcasecmp(var, "serveremail")) {
766 ast_copy_string(vmu->serveremail, value, sizeof(vmu->serveremail));
767 } else if (!strcasecmp(var, "language")) {
768 ast_copy_string(vmu->language, value, sizeof(vmu->language));
769 } else if (!strcasecmp(var, "tz")) {
770 ast_copy_string(vmu->zonetag, value, sizeof(vmu->zonetag));
772 } else if (!strcasecmp(var, "imapuser")) {
773 ast_copy_string(vmu->imapuser, value, sizeof(vmu->imapuser));
774 } else if (!strcasecmp(var, "imappassword")) {
775 ast_copy_string(vmu->imappassword, value, sizeof(vmu->imappassword));
777 } else if (!strcasecmp(var, "delete") || !strcasecmp(var, "deletevoicemail")) {
778 ast_set2_flag(vmu, ast_true(value), VM_DELETE);
779 } else if (!strcasecmp(var, "saycid")){
780 ast_set2_flag(vmu, ast_true(value), VM_SAYCID);
781 } else if (!strcasecmp(var,"sendvoicemail")){
782 ast_set2_flag(vmu, ast_true(value), VM_SVMAIL);
783 } else if (!strcasecmp(var, "review")){
784 ast_set2_flag(vmu, ast_true(value), VM_REVIEW);
785 } else if (!strcasecmp(var, "tempgreetwarn")){
786 ast_set2_flag(vmu, ast_true(value), VM_TEMPGREETWARN);
787 } else if (!strcasecmp(var, "messagewrap")){
788 ast_set2_flag(vmu, ast_true(value), VM_MESSAGEWRAP);
789 } else if (!strcasecmp(var, "operator")) {
790 ast_set2_flag(vmu, ast_true(value), VM_OPERATOR);
791 } else if (!strcasecmp(var, "envelope")){
792 ast_set2_flag(vmu, ast_true(value), VM_ENVELOPE);
793 } else if (!strcasecmp(var, "moveheard")){
794 ast_set2_flag(vmu, ast_true(value), VM_MOVEHEARD);
795 } else if (!strcasecmp(var, "sayduration")){
796 ast_set2_flag(vmu, ast_true(value), VM_SAYDURATION);
797 } else if (!strcasecmp(var, "saydurationm")){
798 if (sscanf(value, "%d", &x) == 1) {
799 vmu->saydurationm = x;
801 ast_log(AST_LOG_WARNING, "Invalid min duration for say duration\n");
803 } else if (!strcasecmp(var, "forcename")){
804 ast_set2_flag(vmu, ast_true(value), VM_FORCENAME);
805 } else if (!strcasecmp(var, "forcegreetings")){
806 ast_set2_flag(vmu, ast_true(value), VM_FORCEGREET);
807 } else if (!strcasecmp(var, "callback")) {
808 ast_copy_string(vmu->callback, value, sizeof(vmu->callback));
809 } else if (!strcasecmp(var, "dialout")) {
810 ast_copy_string(vmu->dialout, value, sizeof(vmu->dialout));
811 } else if (!strcasecmp(var, "exitcontext")) {
812 ast_copy_string(vmu->exit, value, sizeof(vmu->exit));
813 } else if (!strcasecmp(var, "maxmessage") || !strcasecmp(var, "maxsecs")) {
814 if (vmu->maxsecs <= 0) {
815 ast_log(AST_LOG_WARNING, "Invalid max message length of %s. Using global value %d\n", value, vmmaxsecs);
816 vmu->maxsecs = vmmaxsecs;
818 vmu->maxsecs = atoi(value);
820 if (!strcasecmp(var, "maxmessage"))
821 ast_log(AST_LOG_WARNING, "Option 'maxmessage' has been deprecated in favor of 'maxsecs'. Please make that change in your voicemail config.\n");
822 } else if (!strcasecmp(var, "maxmsg")) {
823 vmu->maxmsg = atoi(value);
824 if (vmu->maxmsg <= 0) {
825 ast_log(AST_LOG_WARNING, "Invalid number of messages per folder maxmsg=%s. Using default value %d\n", value, MAXMSG);
826 vmu->maxmsg = MAXMSG;
827 } else if (vmu->maxmsg > MAXMSGLIMIT) {
828 ast_log(AST_LOG_WARNING, "Maximum number of messages per folder is %d. Cannot accept value maxmsg=%s\n", MAXMSGLIMIT, value);
829 vmu->maxmsg = MAXMSGLIMIT;
831 } else if (!strcasecmp(var, "backupdeleted")) {
832 if (sscanf(value, "%d", &x) == 1)
833 vmu->maxdeletedmsg = x;
834 else if (ast_true(value))
835 vmu->maxdeletedmsg = MAXMSG;
837 vmu->maxdeletedmsg = 0;
839 if (vmu->maxdeletedmsg < 0) {
840 ast_log(AST_LOG_WARNING, "Invalid number of deleted messages saved per mailbox backupdeleted=%s. Using default value %d\n", value, MAXMSG);
841 vmu->maxdeletedmsg = MAXMSG;
842 } else if (vmu->maxdeletedmsg > MAXMSGLIMIT) {
843 ast_log(AST_LOG_WARNING, "Maximum number of deleted messages saved per mailbox is %d. Cannot accept value backupdeleted=%s\n", MAXMSGLIMIT, value);
844 vmu->maxdeletedmsg = MAXMSGLIMIT;
846 } else if (!strcasecmp(var, "volgain")) {
847 sscanf(value, "%lf", &vmu->volgain);
848 } else if (!strcasecmp(var, "options")) {
849 apply_options(vmu, value);
853 static char *vm_check_password_shell(char *command, char *buf, size_t len)
860 snprintf(buf, len, "FAILURE: Pipe failed: %s", strerror(errno));
863 pid = ast_safe_fork(0);
869 snprintf(buf, len, "FAILURE: Fork failed");
873 read(fds[0], buf, len);
877 AST_DECLARE_APP_ARGS(arg,
880 char *mycmd = ast_strdupa(command);
883 dup2(fds[1], STDOUT_FILENO);
885 ast_close_fds_above_n(STDOUT_FILENO);
887 AST_NONSTANDARD_APP_ARGS(arg, mycmd, ' ');
889 execv(arg.v[0], arg.v);
890 printf("FAILURE: %s", strerror(errno));
898 * \brief Check that password meets minimum required length
899 * \param vmu The voicemail user to change the password for.
900 * \param password The password string to check
902 * \return zero on ok, 1 on not ok.
904 static int check_password(struct ast_vm_user *vmu, char *password)
906 /* check minimum length */
907 if (strlen(password) < minpassword)
909 if (!ast_strlen_zero(ext_pass_check_cmd)) {
910 char cmd[255], buf[255];
912 ast_log(AST_LOG_DEBUG, "Verify password policies for %s\n", password);
914 snprintf(cmd, sizeof(cmd), "%s %s %s %s %s", ext_pass_check_cmd, vmu->mailbox, vmu->context, vmu->password, password);
915 if (vm_check_password_shell(cmd, buf, sizeof(buf))) {
916 ast_debug(5, "Result: %s\n", buf);
917 if (!strncasecmp(buf, "VALID", 5)) {
918 ast_debug(3, "Passed password check: '%s'\n", buf);
920 } else if (!strncasecmp(buf, "FAILURE", 7)) {
921 ast_log(AST_LOG_WARNING, "Unable to execute password validation script: '%s'.\n", buf);
924 ast_log(AST_LOG_NOTICE, "Password doesn't match policies for user %s %s\n", vmu->mailbox, password);
933 * \brief Performs a change of the voicemail passowrd in the realtime engine.
934 * \param vmu The voicemail user to change the password for.
935 * \param password The new value to be set to the password for this user.
937 * This only works if the voicemail user has a unique id, and if there is a realtime engine configured.
938 * This is called from the (top level) vm_change_password.
940 * \return zero on success, -1 on error.
942 static int change_password_realtime(struct ast_vm_user *vmu, const char *password)
945 if (!ast_strlen_zero(vmu->uniqueid)) {
946 if (strlen(password) > 10) {
947 ast_realtime_require_field("voicemail", "password", RQ_CHAR, strlen(password), SENTINEL);
949 res = ast_update_realtime("voicemail", "uniqueid", vmu->uniqueid, "password", password, SENTINEL);
951 ast_copy_string(vmu->password, password, sizeof(vmu->password));
962 * \brief Destructively Parse options and apply.
964 static void apply_options(struct ast_vm_user *vmu, const char *options)
969 stringp = ast_strdupa(options);
970 while ((s = strsep(&stringp, "|"))) {
972 if ((var = strsep(&value, "=")) && value) {
973 apply_option(vmu, var, value);
979 * \brief Loads the options specific to a voicemail user.
981 * This is called when a vm_user structure is being set up, such as from load_options.
983 static void apply_options_full(struct ast_vm_user *retval, struct ast_variable *var)
985 struct ast_variable *tmp;
988 if (!strcasecmp(tmp->name, "vmsecret")) {
989 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
990 } else if (!strcasecmp(tmp->name, "secret") || !strcasecmp(tmp->name, "password")) { /* don't overwrite vmsecret if it exists */
991 if (ast_strlen_zero(retval->password))
992 ast_copy_string(retval->password, tmp->value, sizeof(retval->password));
993 } else if (!strcasecmp(tmp->name, "uniqueid")) {
994 ast_copy_string(retval->uniqueid, tmp->value, sizeof(retval->uniqueid));
995 } else if (!strcasecmp(tmp->name, "pager")) {
996 ast_copy_string(retval->pager, tmp->value, sizeof(retval->pager));
997 } else if (!strcasecmp(tmp->name, "email")) {
998 ast_copy_string(retval->email, tmp->value, sizeof(retval->email));
999 } else if (!strcasecmp(tmp->name, "fullname")) {
1000 ast_copy_string(retval->fullname, tmp->value, sizeof(retval->fullname));
1001 } else if (!strcasecmp(tmp->name, "context")) {
1002 ast_copy_string(retval->context, tmp->value, sizeof(retval->context));
1004 } else if (!strcasecmp(tmp->name, "imapuser")) {
1005 ast_copy_string(retval->imapuser, tmp->value, sizeof(retval->imapuser));
1006 } else if (!strcasecmp(tmp->name, "imappassword")) {
1007 ast_copy_string(retval->imappassword, tmp->value, sizeof(retval->imappassword));
1010 apply_option(retval, tmp->name, tmp->value);
1016 * \brief Determines if a DTMF key entered is valid.
1017 * \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.
1019 * Tests the character entered against the set of valid DTMF characters.
1020 * \return 1 if the character entered is a valid DTMF digit, 0 if the character is invalid.
1022 static int is_valid_dtmf(const char *key)
1025 char *local_key = ast_strdupa(key);
1027 for (i = 0; i < strlen(key); ++i) {
1028 if (!strchr(VALID_DTMF, *local_key)) {
1029 ast_log(AST_LOG_WARNING, "Invalid DTMF key \"%c\" used in voicemail configuration file\n", *local_key);
1038 * \brief Finds a voicemail user from the realtime engine.
1043 * 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.
1045 * \return The ast_vm_user structure for the user that was found.
1047 static struct ast_vm_user *find_user_realtime(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1049 struct ast_variable *var;
1050 struct ast_vm_user *retval;
1052 if ((retval = (ivm ? ivm : ast_calloc(1, sizeof(*retval))))) {
1054 ast_set_flag(retval, VM_ALLOCED);
1056 memset(retval, 0, sizeof(*retval));
1058 ast_copy_string(retval->mailbox, mailbox, sizeof(retval->mailbox));
1059 populate_defaults(retval);
1060 if (!context && ast_test_flag((&globalflags), VM_SEARCH))
1061 var = ast_load_realtime("voicemail", "mailbox", mailbox, SENTINEL);
1063 var = ast_load_realtime("voicemail", "mailbox", mailbox, "context", context, SENTINEL);
1065 apply_options_full(retval, var);
1066 ast_variables_destroy(var);
1077 * \brief Finds a voicemail user from the users file or the realtime engine.
1082 * \return The ast_vm_user structure for the user that was found.
1084 static struct ast_vm_user *find_user(struct ast_vm_user *ivm, const char *context, const char *mailbox)
1086 /* This function could be made to generate one from a database, too */
1087 struct ast_vm_user *vmu=NULL, *cur;
1088 AST_LIST_LOCK(&users);
1090 if (!context && !ast_test_flag((&globalflags), VM_SEARCH))
1091 context = "default";
1093 AST_LIST_TRAVERSE(&users, cur, list) {
1094 if (ast_test_flag((&globalflags), VM_SEARCH) && !strcasecmp(mailbox, cur->mailbox))
1096 if (context && (!strcasecmp(context, cur->context)) && (!strcasecmp(mailbox, cur->mailbox)))
1100 /* Make a copy, so that on a reload, we have no race */
1101 if ((vmu = (ivm ? ivm : ast_malloc(sizeof(*vmu))))) {
1102 memcpy(vmu, cur, sizeof(*vmu));
1103 ast_set2_flag(vmu, !ivm, VM_ALLOCED);
1104 AST_LIST_NEXT(vmu, list) = NULL;
1107 vmu = find_user_realtime(ivm, context, mailbox);
1108 AST_LIST_UNLOCK(&users);
1113 * \brief Resets a user password to a specified password.
1118 * This does the actual change password work, called by the vm_change_password() function.
1120 * \return zero on success, -1 on error.
1122 static int reset_user_pw(const char *context, const char *mailbox, const char *newpass)
1124 /* This function could be made to generate one from a database, too */
1125 struct ast_vm_user *cur;
1127 AST_LIST_LOCK(&users);
1128 AST_LIST_TRAVERSE(&users, cur, list) {
1129 if ((!context || !strcasecmp(context, cur->context)) &&
1130 (!strcasecmp(mailbox, cur->mailbox)))
1134 ast_copy_string(cur->password, newpass, sizeof(cur->password));
1137 AST_LIST_UNLOCK(&users);
1142 * \brief The handler for the change password option.
1143 * \param vmu The voicemail user to work with.
1144 * \param newpassword The new password (that has been gathered from the appropriate prompting).
1145 * 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.
1146 * It is also called when the user wants to change their password from menu option '5' on the mailbox options menu.
1148 static void vm_change_password(struct ast_vm_user *vmu, const char *newpassword)
1150 struct ast_config *cfg=NULL;
1151 struct ast_variable *var=NULL;
1152 struct ast_category *cat=NULL;
1153 char *category=NULL, *value=NULL, *new=NULL;
1154 const char *tmp=NULL;
1155 struct ast_flags config_flags = { CONFIG_FLAG_WITHCOMMENTS };
1156 if (!change_password_realtime(vmu, newpassword))
1159 /* check voicemail.conf */
1160 if ((cfg = ast_config_load(VOICEMAIL_CONFIG, config_flags))) {
1161 while ((category = ast_category_browse(cfg, category))) {
1162 if (!strcasecmp(category, vmu->context)) {
1163 if (!(tmp = ast_variable_retrieve(cfg, category, vmu->mailbox))) {
1164 ast_log(AST_LOG_WARNING, "We could not find the mailbox.\n");
1167 value = strstr(tmp,",");
1169 ast_log(AST_LOG_WARNING, "variable has bad format.\n");
1172 new = alloca((strlen(value)+strlen(newpassword)+1));
1173 sprintf(new,"%s%s", newpassword, value);
1174 if (!(cat = ast_category_get(cfg, category))) {
1175 ast_log(AST_LOG_WARNING, "Failed to get category structure.\n");
1178 ast_variable_update(cat, vmu->mailbox, new, NULL, 0);
1181 /* save the results */
1182 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1183 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1184 config_text_file_save(VOICEMAIL_CONFIG, cfg, "AppVoicemail");
1188 /* check users.conf and update the password stored for the mailbox*/
1189 /* if no vmsecret entry exists create one. */
1190 if ((cfg = ast_config_load("users.conf", config_flags))) {
1191 ast_debug(4, "we are looking for %s\n", vmu->mailbox);
1192 while ((category = ast_category_browse(cfg, category))) {
1193 ast_debug(4, "users.conf: %s\n", category);
1194 if (!strcasecmp(category, vmu->mailbox)) {
1195 if (!(tmp = ast_variable_retrieve(cfg, category, "vmsecret"))) {
1196 ast_debug(3, "looks like we need to make vmsecret!\n");
1197 var = ast_variable_new("vmsecret", newpassword, "");
1199 new = alloca(strlen(newpassword)+1);
1200 sprintf(new, "%s", newpassword);
1201 if (!(cat = ast_category_get(cfg, category))) {
1202 ast_debug(4, "failed to get category!\n");
1206 ast_variable_update(cat, "vmsecret", new, NULL, 0);
1208 ast_variable_append(cat, var);
1211 /* save the results and clean things up */
1212 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1213 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1214 config_text_file_save("users.conf", cfg, "AppVoicemail");
1218 static void vm_change_password_shell(struct ast_vm_user *vmu, char *newpassword)
1221 snprintf(buf,255,"%s %s %s %s",ext_pass_cmd,vmu->context,vmu->mailbox,newpassword);
1222 if (!ast_safe_system(buf)) {
1223 ast_copy_string(vmu->password, newpassword, sizeof(vmu->password));
1224 /* Reset the password in memory, too */
1225 reset_user_pw(vmu->context, vmu->mailbox, newpassword);
1230 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1231 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1232 * \param len The length of the path string that was written out.
1234 * The path is constructed as
1235 * VM_SPOOL_DIRcontext/ext/folder
1237 * \return zero on success, -1 on error.
1239 static int make_dir(char *dest, int len, const char *context, const char *ext, const char *folder)
1241 return snprintf(dest, len, "%s%s/%s/%s", VM_SPOOL_DIR, context, ext, folder);
1245 * \brief Creates a file system path expression for a folder within the voicemail data folder and the appropriate context.
1246 * \param dest The variable to hold the output generated path expression. This buffer should be of size PATH_MAX.
1247 * \param len The length of the path string that was written out.
1249 * The path is constructed as
1250 * VM_SPOOL_DIRcontext/ext/folder
1252 * \return zero on success, -1 on error.
1254 static int make_file(char *dest, const int len, const char *dir, const int num)
1256 return snprintf(dest, len, "%s/msg%04d", dir, num);
1259 /* same as mkstemp, but return a FILE * */
1260 static FILE *vm_mkftemp(char *template)
1263 int pfd = mkstemp(template);
1264 chmod(template, VOICEMAIL_FILE_MODE & ~my_umask);
1266 p = fdopen(pfd, "w+");
1275 /*! \brief basically mkdir -p $dest/$context/$ext/$folder
1276 * \param dest String. base directory.
1277 * \param len Length of dest.
1278 * \param context String. Ignored if is null or empty string.
1279 * \param ext String. Ignored if is null or empty string.
1280 * \param folder String. Ignored if is null or empty string.
1281 * \return -1 on failure, 0 on success.
1283 static int create_dirpath(char *dest, int len, const char *context, const char *ext, const char *folder)
1285 mode_t mode = VOICEMAIL_DIR_MODE;
1288 make_dir(dest, len, context, ext, folder);
1289 if ((res = ast_mkdir(dest, mode))) {
1290 ast_log(AST_LOG_WARNING, "ast_mkdir '%s' failed: %s\n", dest, strerror(res));
1296 static const char *mbox(int id)
1298 static const char *msgs[] = {
1316 return (id >= 0 && id < (sizeof(msgs)/sizeof(msgs[0]))) ? msgs[id] : "Unknown";
1319 static void free_user(struct ast_vm_user *vmu)
1321 if (ast_test_flag(vmu, VM_ALLOCED))
1325 /* All IMAP-specific functions should go in this block. This
1326 * keeps them from being spread out all over the code */
1328 static void vm_imap_delete(int msgnum, struct ast_vm_user *vmu)
1331 struct vm_state *vms;
1332 unsigned long messageNum;
1334 /* Greetings aren't stored in IMAP, so we can't delete them there */
1339 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1340 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);
1344 /* find real message number based on msgnum */
1345 /* this may be an index into vms->msgArray based on the msgnum. */
1346 messageNum = vms->msgArray[msgnum];
1347 if (messageNum == 0) {
1348 ast_log(LOG_WARNING, "msgnum %d, mailbox message %lu is zero.\n",msgnum,messageNum);
1351 if (option_debug > 2)
1352 ast_log(LOG_DEBUG, "deleting msgnum %d, which is mailbox message %lu\n",msgnum,messageNum);
1353 /* delete message */
1354 snprintf (arg, sizeof(arg), "%lu",messageNum);
1355 mail_setflag (vms->mailstream,arg,"\\DELETED");
1358 static int imap_retrieve_greeting (const char *dir, const int msgnum, struct ast_vm_user *vmu)
1360 struct vm_state *vms_p;
1361 char *file, *filename;
1366 /* This function is only used for retrieval of IMAP greetings
1367 * regular messages are not retrieved this way, nor are greetings
1368 * if they are stored locally*/
1369 if (msgnum > -1 || !imapgreetings) {
1372 file = strrchr(ast_strdupa(dir), '/');
1376 ast_debug (1, "Failed to procure file name from directory passed.\n");
1381 /* check if someone is accessing this box right now... */
1382 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))) {
1383 ast_log(AST_LOG_ERROR, "Voicemail state not found!\n");
1387 /* Greetings will never have a prepended message */
1388 *vms_p->introfn = '\0';
1390 ret = init_mailstream(vms_p, GREETINGS_FOLDER);
1391 if (!vms_p->mailstream) {
1392 ast_log(AST_LOG_ERROR, "IMAP mailstream is NULL\n");
1396 /*XXX Yuck, this could probably be done a lot better */
1397 for (i = 0; i < vms_p->mailstream->nmsgs; i++) {
1398 mail_fetchstructure(vms_p->mailstream, i + 1, &body);
1399 /* We have the body, now we extract the file name of the first attachment. */
1400 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1401 attachment = ast_strdupa(body->nested.part->next->body.parameter->value);
1403 ast_log(AST_LOG_ERROR, "There is no file attached to this IMAP message.\n");
1406 filename = strsep(&attachment, ".");
1407 if (!strcmp(filename, file)) {
1408 ast_copy_string(vms_p->fn, dir, sizeof(vms_p->fn));
1409 vms_p->msgArray[vms_p->curmsg] = i + 1;
1410 save_body(body, vms_p, "2", attachment, 0);
1418 static int imap_retrieve_file(const char *dir, const int msgnum, const char *mailbox, const char *context)
1421 char *header_content;
1422 char *attachedfilefmt;
1424 struct vm_state *vms;
1425 char text_file[PATH_MAX];
1426 FILE *text_file_ptr;
1428 struct ast_vm_user *vmu;
1430 if (!(vmu = find_user(NULL, context, mailbox))) {
1431 ast_log(LOG_WARNING, "Couldn't find user with mailbox %s@%s\n", mailbox, context);
1436 if (imapgreetings) {
1437 res = imap_retrieve_greeting(dir, msgnum, vmu);
1445 /* Before anything can happen, we need a vm_state so that we can
1446 * actually access the imap server through the vms->mailstream
1448 if (!(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 1)) && !(vms = get_vm_state_by_mailbox(vmu->mailbox, vmu->context, 0))) {
1449 /* This should not happen. If it does, then I guess we'd
1450 * need to create the vm_state, extract which mailbox to
1451 * open, and then set up the msgArray so that the correct
1452 * IMAP message could be accessed. If I have seen correctly
1453 * though, the vms should be obtainable from the vmstates list
1454 * and should have its msgArray properly set up.
1456 ast_log(LOG_ERROR, "Couldn't find a vm_state for mailbox %s!!! Oh no!\n", vmu->mailbox);
1461 make_file(vms->fn, sizeof(vms->fn), dir, msgnum);
1462 snprintf(vms->introfn, sizeof(vms->introfn), "%sintro", vms->fn);
1464 /* Don't try to retrieve a message from IMAP if it already is on the file system */
1465 if (ast_fileexists(vms->fn, NULL, NULL) > 0) {
1470 if (option_debug > 2)
1471 ast_log (LOG_DEBUG,"Before mail_fetchheaders, curmsg is: %d, imap messages is %lu\n", msgnum, vms->msgArray[msgnum]);
1472 if (vms->msgArray[msgnum] == 0) {
1473 ast_log (LOG_WARNING,"Trying to access unknown message\n");
1478 /* This will only work for new messages... */
1479 header_content = mail_fetchheader (vms->mailstream, vms->msgArray[msgnum]);
1480 /* empty string means no valid header */
1481 if (ast_strlen_zero(header_content)) {
1482 ast_log (LOG_ERROR,"Could not fetch header for message number %ld\n",vms->msgArray[msgnum]);
1487 mail_fetchstructure (vms->mailstream,vms->msgArray[msgnum],&body);
1489 /* We have the body, now we extract the file name of the first attachment. */
1490 if (body->nested.part && body->nested.part->next && body->nested.part->next->body.parameter->value) {
1491 attachedfilefmt = ast_strdupa(body->nested.part->next->body.parameter->value);
1493 ast_log(LOG_ERROR, "There is no file attached to this IMAP message.\n");
1498 /* Find the format of the attached file */
1500 strsep(&attachedfilefmt, ".");
1501 if (!attachedfilefmt) {
1502 ast_log(LOG_ERROR, "File format could not be obtained from IMAP message attachment\n");
1507 save_body(body, vms, "2", attachedfilefmt, 0);
1508 if (save_body(body, vms, "3", attachedfilefmt, 1)) {
1509 *vms->introfn = '\0';
1512 /* Get info from headers!! */
1513 snprintf(text_file, sizeof(text_file), "%s.%s", vms->fn, "txt");
1515 if (!(text_file_ptr = fopen(text_file, "w"))) {
1516 ast_log(LOG_WARNING, "Unable to open/create file %s: %s\n", text_file, strerror(errno));
1519 fprintf(text_file_ptr, "%s\n", "[message]");
1521 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Num:", buf, sizeof(buf));
1522 fprintf(text_file_ptr, "callerid=\"%s\" ", S_OR(buf, ""));
1523 get_header_by_tag(header_content, "X-Asterisk-VM-Caller-ID-Name:", buf, sizeof(buf));
1524 fprintf(text_file_ptr, "<%s>\n", S_OR(buf, ""));
1525 get_header_by_tag(header_content, "X-Asterisk-VM-Context:", buf, sizeof(buf));
1526 fprintf(text_file_ptr, "context=%s\n", S_OR(buf, ""));
1527 get_header_by_tag(header_content, "X-Asterisk-VM-Orig-time:", buf, sizeof(buf));
1528 fprintf(text_file_ptr, "origtime=%s\n", S_OR(buf, ""));
1529 get_header_by_tag(header_content, "X-Asterisk-VM-Duration:", buf, sizeof(buf));
1530 fprintf(text_file_ptr, "duration=%s\n", S_OR(buf, ""));
1531 get_header_by_tag(header_content, "X-Asterisk-VM-Category:", buf, sizeof(buf));
1532 fprintf(text_file_ptr, "category=%s\n", S_OR(buf, ""));
1533 fclose(text_file_ptr);
1540 static int folder_int(const char *folder)
1542 /*assume a NULL folder means INBOX*/
1546 if (!strcasecmp(folder, imapfolder))
1548 if (!strcasecmp(folder, "INBOX"))
1551 else if (!strcasecmp(folder, "Old"))
1553 else if (!strcasecmp(folder, "Work"))
1555 else if (!strcasecmp(folder, "Family"))
1557 else if (!strcasecmp(folder, "Friends"))
1559 else if (!strcasecmp(folder, "Cust1"))
1561 else if (!strcasecmp(folder, "Cust2"))
1563 else if (!strcasecmp(folder, "Cust3"))
1565 else if (!strcasecmp(folder, "Cust4"))
1567 else if (!strcasecmp(folder, "Cust5"))
1569 else /*assume they meant INBOX if folder is not found otherwise*/
1574 * \brief Gets the number of messages that exist in a mailbox folder.
1579 * This method is used when IMAP backend is used.
1580 * \return The number of messages in this mailbox folder (zero or more).
1582 static int messagecount(const char *context, const char *mailbox, const char *folder)
1587 struct ast_vm_user *vmu, vmus;
1588 struct vm_state *vms_p;
1590 int fold = folder_int(folder);
1593 if (ast_strlen_zero(mailbox))
1596 /* We have to get the user before we can open the stream! */
1597 vmu = find_user(&vmus, context, mailbox);
1599 ast_log(AST_LOG_ERROR, "Couldn't find mailbox %s in context %s\n", mailbox, context);
1602 /* No IMAP account available */
1603 if (vmu->imapuser[0] == '\0') {
1604 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1609 /* No IMAP account available */
1610 if (vmu->imapuser[0] == '\0') {
1611 ast_log(AST_LOG_WARNING, "IMAP user not set for mailbox %s\n", vmu->mailbox);
1616 /* check if someone is accessing this box right now... */
1617 vms_p = get_vm_state_by_imapuser(vmu->imapuser,1);
1619 vms_p = get_vm_state_by_mailbox(mailbox, context, 1);
1622 ast_debug(3, "Returning before search - user is logged in\n");
1623 if (fold == 0) { /* INBOX */
1624 return vms_p->newmessages;
1626 if (fold == 1) { /* Old messages */
1627 return vms_p->oldmessages;
1629 if (fold == 11) {/*Urgent messages*/
1630 return vms_p->urgentmessages;
1634 /* add one if not there... */
1635 vms_p = get_vm_state_by_imapuser(vmu->imapuser,0);
1637 vms_p = get_vm_state_by_mailbox(mailbox, context, 0);
1640 /* If URGENT, then look at INBOX */
1647 ast_debug(3,"Adding new vmstate for %s\n",vmu->imapuser);
1648 if (!(vms_p = ast_calloc(1, sizeof(*vms_p)))) {
1651 ast_copy_string(vms_p->imapuser,vmu->imapuser, sizeof(vms_p->imapuser));
1652 ast_copy_string(vms_p->username, mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
1653 vms_p->mailstream = NIL; /* save for access from interactive entry point */
1654 ast_debug(3, "Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
1656 ast_copy_string(vms_p->curbox, mbox(fold), sizeof(vms_p->curbox));
1657 init_vm_state(vms_p);
1658 vmstate_insert(vms_p);
1660 ret = init_mailstream(vms_p, fold);
1661 if (!vms_p->mailstream) {
1662 ast_log(AST_LOG_ERROR, "Houston we have a problem - IMAP mailstream is NULL\n");
1666 pgm = mail_newsearchpgm ();
1667 hdr = mail_newsearchheader ("X-Asterisk-VM-Extension", (char *)mailbox);
1673 /* In the special case where fold is 1 (old messages) we have to do things a bit
1674 * differently. Old messages are stored in the INBOX but are marked as "seen"
1680 /* look for urgent messages */
1688 vms_p->vmArrayIndex = 0;
1689 mail_search_full (vms_p->mailstream, NULL, pgm, NIL);
1690 if (fold == 0 && urgent == 0)
1691 vms_p->newmessages = vms_p->vmArrayIndex;
1693 vms_p->oldmessages = vms_p->vmArrayIndex;
1694 if (fold == 0 && urgent == 1)
1695 vms_p->urgentmessages = vms_p->vmArrayIndex;
1696 /*Freeing the searchpgm also frees the searchhdr*/
1697 mail_free_searchpgm(&pgm);
1699 return vms_p->vmArrayIndex;
1701 mail_ping(vms_p->mailstream);
1706 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)
1708 char *myserveremail = serveremail;
1710 char introfn[PATH_MAX];
1714 char tmp[80] = "/tmp/astmail-XXXXXX";
1719 int ret; /* for better error checking */
1720 char *imap_flags = NIL;
1722 /* Set urgent flag for IMAP message */
1723 if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) {
1724 ast_debug(3, "Setting message flag \\\\FLAGGED.\n");
1725 imap_flags="\\FLAGGED";
1728 /* Attach only the first format */
1729 fmt = ast_strdupa(fmt);
1731 strsep(&stringp, "|");
1733 if (!ast_strlen_zero(vmu->serveremail))
1734 myserveremail = vmu->serveremail;
1737 make_file(fn, sizeof(fn), dir, msgnum);
1739 ast_copy_string (fn, dir, sizeof(fn));
1741 snprintf(introfn, sizeof(introfn), "%sintro", fn);
1742 if (ast_fileexists(introfn, NULL, NULL) <= 0) {
1746 if (ast_strlen_zero(vmu->email)) {
1747 /* We need the vmu->email to be set when we call make_email_file, but
1748 * if we keep it set, a duplicate e-mail will be created. So at the end
1749 * of this function, we will revert back to an empty string if tempcopy
1752 ast_copy_string(vmu->email, vmu->imapuser, sizeof(vmu->email));
1756 if (!strcmp(fmt, "wav49"))
1758 ast_debug(3, "Storing file '%s', format '%s'\n", fn, fmt);
1760 /* Make a temporary file instead of piping directly to sendmail, in case the mail
1762 if (!(p = vm_mkftemp(tmp))) {
1763 ast_log(AST_LOG_WARNING, "Unable to store '%s' (can't create temporary file)\n", fn);
1765 *(vmu->email) = '\0';
1769 if (msgnum < 0 && imapgreetings) {
1770 if ((ret = init_mailstream(vms, GREETINGS_FOLDER))) {
1771 ast_log(AST_LOG_WARNING, "Unable to open mailstream.\n");
1774 imap_delete_old_greeting(fn, vms);
1777 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);
1778 /* read mail file to memory */
1781 if (!(buf = ast_malloc(len + 1))) {
1782 ast_log(AST_LOG_ERROR, "Can't allocate %ld bytes to read message\n", len + 1);
1785 *(vmu->email) = '\0';
1788 fread(buf, len, 1, p);
1789 ((char *)buf)[len] = '\0';
1790 INIT(&str, mail_string, buf, len);
1791 ret = init_mailstream(vms, NEW_FOLDER);
1793 imap_mailbox_name(mailbox, sizeof(mailbox), vms, NEW_FOLDER, 1);
1794 if(!mail_append_full(vms->mailstream, mailbox, imap_flags, NIL, &str))
1795 ast_log(LOG_ERROR, "Error while sending the message to %s\n", mailbox);
1800 ast_log(LOG_ERROR, "Could not initialize mailstream for %s\n",mailbox);
1806 ast_debug(3, "%s stored\n", fn);
1809 *(vmu->email) = '\0';
1816 * \brief Gets the number of messages that exist in the inbox folder.
1817 * \param mailbox_context
1818 * \param newmsgs The variable that is updated with the count of new messages within this inbox.
1819 * \param oldmsgs The variable that is updated with the count of old messages within this inbox.
1820 * \param urgentmsgs The variable that is updated with the count of urgent messages within this inbox.
1822 * This method is used when IMAP backend is used.
1823 * Simultaneously determines the count of new,old, and urgent messages. The total messages would then be the sum of these three.
1825 * \return zero on success, -1 on error.
1828 static int inboxcount2(const char *mailbox_context, int *urgentmsgs, int *newmsgs, int *oldmsgs)
1830 char tmp[PATH_MAX] = "";
1842 ast_debug(3,"Mailbox is set to %s\n",mailbox_context);
1843 /* If no mailbox, return immediately */
1844 if (ast_strlen_zero(mailbox_context))
1847 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1848 context = strchr(tmp, '@');
1849 if (strchr(mailbox_context, ',')) {
1850 int tmpnew, tmpold, tmpurgent;
1851 ast_copy_string(tmp, mailbox_context, sizeof(tmp));
1853 while ((cur = strsep(&mb, ", "))) {
1854 if (!ast_strlen_zero(cur)) {
1855 if (inboxcount2(cur, urgentmsgs ? &tmpurgent : NULL, newmsgs ? &tmpnew : NULL, oldmsgs ? &tmpold : NULL))
1863 *urgentmsgs += tmpurgent;
1874 context = "default";
1875 mailboxnc = (char *)mailbox_context;
1878 if ((*newmsgs = messagecount(context, mailboxnc, imapfolder)) < 0)
1882 if ((*oldmsgs = messagecount(context, mailboxnc, "Old")) < 0)
1886 if((*urgentmsgs = messagecount(context, mailboxnc, "Urgent")) < 0)
1892 static int inboxcount(const char *mailbox_context, int *newmsgs, int *oldmsgs)
1894 return inboxcount2(mailbox_context, NULL, newmsgs, oldmsgs);
1898 * \brief Determines if the given folder has messages.
1899 * \param mailbox The @ delimited string for user@context. If no context is found, uses 'default' for the context.
1900 * \param folder the folder to look in
1902 * This function is used when the mailbox is stored in an IMAP back end.
1903 * This invokes the messagecount(). Here we are interested in the presence of messages (> 0) only, not the actual count.
1904 * \return 1 if the folder has one or more messages. zero otherwise.
1907 static int has_voicemail(const char *mailbox, const char *folder)
1909 char tmp[256], *tmp2, *box, *context;
1910 ast_copy_string(tmp, mailbox, sizeof(tmp));
1912 if (strchr(tmp2, ',')) {
1913 while ((box = strsep(&tmp2, ","))) {
1914 if (!ast_strlen_zero(box)) {
1915 if (has_voicemail(box, folder))
1920 if ((context= strchr(tmp, '@')))
1923 context = "default";
1924 return messagecount(context, tmp, folder) ? 1 : 0;
1928 * \brief Copies a message from one mailbox to another.
1938 * This works with IMAP storage based mailboxes.
1940 * \return zero on success, -1 on error.
1942 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)
1944 struct vm_state *sendvms = NULL, *destvms = NULL;
1945 char messagestring[10]; /*I guess this could be a problem if someone has more than 999999999 messages...*/
1946 if (msgnum >= recip->maxmsg) {
1947 ast_log(LOG_WARNING, "Unable to copy mail, mailbox %s is full\n", recip->mailbox);
1950 if (!(sendvms = get_vm_state_by_imapuser(vmu->imapuser, 0))) {
1951 ast_log(LOG_ERROR, "Couldn't get vm_state for originator's mailbox!!\n");
1954 if (!(destvms = get_vm_state_by_imapuser(recip->imapuser, 0))) {
1955 ast_log(LOG_ERROR, "Couldn't get vm_state for destination mailbox!\n");
1958 snprintf(messagestring, sizeof(messagestring), "%ld", sendvms->msgArray[msgnum]);
1959 if ((mail_copy(sendvms->mailstream, messagestring, (char *) mbox(imbox)) == T))
1961 ast_log(LOG_WARNING, "Unable to copy message from mailbox %s to mailbox %s\n", vmu->mailbox, recip->mailbox);
1965 static void imap_mailbox_name(char *spec, size_t len, struct vm_state *vms, int box, int use_folder)
1967 char tmp[256], *t = tmp;
1968 size_t left = sizeof(tmp);
1970 if (box == OLD_FOLDER) {
1971 ast_copy_string(vms->curbox, mbox(NEW_FOLDER), sizeof(vms->curbox));
1973 ast_copy_string(vms->curbox, mbox(box), sizeof(vms->curbox));
1976 if (box == NEW_FOLDER) {
1977 ast_copy_string(vms->vmbox, "vm-INBOX", sizeof(vms->vmbox));
1979 snprintf(vms->vmbox, sizeof(vms->vmbox), "vm-%s", mbox(box));
1982 /* Build up server information */
1983 ast_build_string(&t, &left, "{%s:%s/imap", imapserver, imapport);
1985 /* Add authentication user if present */
1986 if (!ast_strlen_zero(authuser))
1987 ast_build_string(&t, &left, "/authuser=%s", authuser);
1989 /* Add flags if present */
1990 if (!ast_strlen_zero(imapflags))
1991 ast_build_string(&t, &left, "/%s", imapflags);
1993 /* End with username */
1994 ast_build_string(&t, &left, "/user=%s}", vms->imapuser);
1995 if (box == NEW_FOLDER || box == OLD_FOLDER)
1996 snprintf(spec, len, "%s%s", tmp, use_folder? imapfolder: "INBOX");
1997 else if (box == GREETINGS_FOLDER)
1998 snprintf(spec, len, "%s%s", tmp, greetingfolder);
1999 else { /* Other folders such as Friends, Family, etc... */
2000 if (!ast_strlen_zero(imapparentfolder)) {
2001 /* imapparentfolder would typically be set to INBOX */
2002 snprintf(spec, len, "%s%s%c%s", tmp, imapparentfolder, delimiter, mbox(box));
2004 snprintf(spec, len, "%s%s", tmp, mbox(box));
2009 static int init_mailstream(struct vm_state *vms, int box)
2011 MAILSTREAM *stream = NIL;
2016 ast_log (LOG_ERROR,"vm_state is NULL!\n");
2019 if (option_debug > 2)
2020 ast_log (LOG_DEBUG,"vm_state user is:%s\n",vms->imapuser);
2021 if (vms->mailstream == NIL || !vms->mailstream) {
2023 ast_log (LOG_DEBUG,"mailstream not set.\n");
2025 stream = vms->mailstream;
2027 /* debug = T; user wants protocol telemetry? */
2028 debug = NIL; /* NO protocol telemetry? */
2030 if (delimiter == '\0') { /* did not probe the server yet */
2032 #ifdef USE_SYSTEM_IMAP
2033 #include <imap/linkage.c>
2034 #elif defined(USE_SYSTEM_CCLIENT)
2035 #include <c-client/linkage.c>
2037 #include "linkage.c"
2039 /* Connect to INBOX first to get folders delimiter */
2040 imap_mailbox_name(tmp, sizeof(tmp), vms, 0, 1);
2041 ast_mutex_lock(&vms->lock);
2042 stream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2043 ast_mutex_unlock(&vms->lock);
2044 if (stream == NIL) {
2045 ast_log (LOG_ERROR, "Can't connect to imap server %s\n", tmp);
2048 get_mailbox_delimiter(stream);
2049 /* update delimiter in imapfolder */
2050 for (cp = imapfolder; *cp; cp++)
2054 /* Now connect to the target folder */
2055 imap_mailbox_name(tmp, sizeof(tmp), vms, box, 1);
2056 if (option_debug > 2)
2057 ast_log (LOG_DEBUG,"Before mail_open, server: %s, box:%d\n", tmp, box);
2058 ast_mutex_lock(&vms->lock);
2059 vms->mailstream = mail_open (stream, tmp, debug ? OP_DEBUG : NIL);
2060 ast_mutex_unlock(&vms->lock);
2061 if (vms->mailstream == NIL) {
2068 static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box)
2072 int ret, urgent = 0;
2074 /* If Urgent, then look at INBOX */
2080 ast_copy_string(vms->imapuser,vmu->imapuser, sizeof(vms->imapuser));
2081 ast_debug(3,"Before init_mailstream, user is %s\n",vmu->imapuser);
2083 if ((ret = init_mailstream(vms, box)) || !vms->mailstream) {
2084 ast_log(AST_LOG_ERROR, "Could not initialize mailstream\n");
2088 create_dirpath(vms->curdir, sizeof(vms->curdir), vmu->context, vms->username, vms->curbox);
2092 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mbox(box));
2093 check_quota(vms,(char *)mbox(box));
2096 pgm = mail_newsearchpgm();
2098 /* Check IMAP folder for Asterisk messages only... */
2099 hdr = mail_newsearchheader("X-Asterisk-VM-Extension", vmu->mailbox);
2104 /* if box = NEW_FOLDER, check for new, if box = OLD_FOLDER, check for read */
2105 if (box == NEW_FOLDER && urgent == 1) {
2110 } else if (box == NEW_FOLDER && urgent == 0) {
2115 } else if (box == OLD_FOLDER) {
2120 ast_debug(3,"Before mail_search_full, user is %s\n",vmu->imapuser);
2122 vms->vmArrayIndex = 0;
2123 mail_search_full (vms->mailstream, NULL, pgm, NIL);
2124 vms->lastmsg = vms->vmArrayIndex - 1;
2125 mail_free_searchpgm(&pgm);
2130 static void write_file(char *filename, char *buffer, unsigned long len)
2134 output = fopen (filename, "w");
2135 fwrite (buffer, len, 1, output);
2139 static void update_messages_by_imapuser(const char *user, unsigned long number)
2141 struct vmstate *vlist = NULL;
2143 AST_LIST_LOCK(&vmstates);
2144 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2146 ast_debug(3, "error: vms is NULL for %s\n", user);
2149 if (!vlist->vms->imapuser) {
2150 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2153 ast_debug(3, "saving mailbox message number %lu as message %d. Interactive set to %d\n", number, vlist->vms->vmArrayIndex, vlist->vms->interactive);
2154 vlist->vms->msgArray[vlist->vms->vmArrayIndex++] = number;
2156 AST_LIST_UNLOCK(&vmstates);
2159 void mm_searched(MAILSTREAM *stream, unsigned long number)
2161 char *mailbox = stream->mailbox, buf[1024] = "", *user;
2163 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))))
2166 update_messages_by_imapuser(user, number);
2169 static struct ast_vm_user *find_user_realtime_imapuser(const char *imapuser)
2171 struct ast_variable *var;
2172 struct ast_vm_user *vmu;
2174 vmu = ast_calloc(1, sizeof *vmu);
2177 ast_set_flag(vmu, VM_ALLOCED);
2178 populate_defaults(vmu);
2180 var = ast_load_realtime("voicemail", "imapuser", imapuser, NULL);
2182 apply_options_full(vmu, var);
2183 ast_variables_destroy(var);
2191 /* Interfaces to C-client */
2193 void mm_exists(MAILSTREAM * stream, unsigned long number)
2195 /* mail_ping will callback here if new mail! */
2196 ast_debug(4, "Entering EXISTS callback for message %ld\n", number);
2197 if (number == 0) return;
2202 void mm_expunged(MAILSTREAM * stream, unsigned long number)
2204 /* mail_ping will callback here if expunged mail! */
2205 ast_debug(4, "Entering EXPUNGE callback for message %ld\n", number);
2206 if (number == 0) return;
2211 void mm_flags(MAILSTREAM * stream, unsigned long number)
2213 /* mail_ping will callback here if read mail! */
2214 ast_debug(4, "Entering FLAGS callback for message %ld\n", number);
2215 if (number == 0) return;
2220 void mm_notify(MAILSTREAM * stream, char *string, long errflg)
2222 ast_debug(5, "Entering NOTIFY callback, errflag is %ld, string is %s\n", errflg, string);
2223 mm_log (string, errflg);
2227 void mm_list(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2229 if (delimiter == '\0') {
2233 ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2234 if (attributes & LATT_NOINFERIORS)
2235 ast_debug(5, "no inferiors\n");
2236 if (attributes & LATT_NOSELECT)
2237 ast_debug(5, "no select\n");
2238 if (attributes & LATT_MARKED)
2239 ast_debug(5, "marked\n");
2240 if (attributes & LATT_UNMARKED)
2241 ast_debug(5, "unmarked\n");
2245 void mm_lsub(MAILSTREAM * stream, int delim, char *mailbox, long attributes)
2247 ast_debug(5, "Delimiter set to %c and mailbox %s\n",delim, mailbox);
2248 if (attributes & LATT_NOINFERIORS)
2249 ast_debug(5, "no inferiors\n");
2250 if (attributes & LATT_NOSELECT)
2251 ast_debug(5, "no select\n");
2252 if (attributes & LATT_MARKED)
2253 ast_debug(5, "marked\n");
2254 if (attributes & LATT_UNMARKED)
2255 ast_debug(5, "unmarked\n");
2259 void mm_status(MAILSTREAM * stream, char *mailbox, MAILSTATUS * status)
2261 ast_log(AST_LOG_NOTICE, " Mailbox %s", mailbox);
2262 if (status->flags & SA_MESSAGES)
2263 ast_log(AST_LOG_NOTICE, ", %lu messages", status->messages);
2264 if (status->flags & SA_RECENT)
2265 ast_log(AST_LOG_NOTICE, ", %lu recent", status->recent);
2266 if (status->flags & SA_UNSEEN)
2267 ast_log(AST_LOG_NOTICE, ", %lu unseen", status->unseen);
2268 if (status->flags & SA_UIDVALIDITY)
2269 ast_log(AST_LOG_NOTICE, ", %lu UID validity", status->uidvalidity);
2270 if (status->flags & SA_UIDNEXT)
2271 ast_log(AST_LOG_NOTICE, ", %lu next UID", status->uidnext);
2272 ast_log(AST_LOG_NOTICE, "\n");
2276 void mm_log(char *string, long errflg)
2278 switch ((short) errflg) {
2280 ast_debug(1,"IMAP Info: %s\n", string);
2284 ast_log(AST_LOG_WARNING, "IMAP Warning: %s\n", string);
2287 ast_log(AST_LOG_ERROR, "IMAP Error: %s\n", string);
2293 void mm_dlog(char *string)
2295 ast_log(AST_LOG_NOTICE, "%s\n", string);
2299 void mm_login(NETMBX * mb, char *user, char *pwd, long trial)
2301 struct ast_vm_user *vmu;
2303 ast_debug(4, "Entering callback mm_login\n");
2305 ast_copy_string(user, mb->user, MAILTMPLEN);
2307 /* We should only do this when necessary */
2308 if (!ast_strlen_zero(authpassword)) {
2309 ast_copy_string(pwd, authpassword, MAILTMPLEN);
2311 AST_LIST_TRAVERSE(&users, vmu, list) {
2312 if (!strcasecmp(mb->user, vmu->imapuser)) {
2313 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2318 if ((vmu = find_user_realtime_imapuser(mb->user))) {
2319 ast_copy_string(pwd, vmu->imappassword, MAILTMPLEN);
2327 void mm_critical(MAILSTREAM * stream)
2332 void mm_nocritical(MAILSTREAM * stream)
2337 long mm_diskerror(MAILSTREAM * stream, long errcode, long serious)
2339 kill (getpid (), SIGSTOP);
2344 void mm_fatal(char *string)
2346 ast_log(AST_LOG_ERROR, "IMAP access FATAL error: %s\n", string);
2349 /* C-client callback to handle quota */
2350 static void mm_parsequota(MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2352 struct vm_state *vms;
2353 char *mailbox = stream->mailbox, *user;
2354 char buf[1024] = "";
2355 unsigned long usage = 0, limit = 0;
2358 usage = pquota->usage;
2359 limit = pquota->limit;
2360 pquota = pquota->next;
2363 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 2))) {
2364 ast_log(AST_LOG_ERROR, "No state found.\n");
2368 ast_debug(3, "User %s usage is %lu, limit is %lu\n", user, usage, limit);
2370 vms->quota_usage = usage;
2371 vms->quota_limit = limit;
2374 static char *get_header_by_tag(char *header, char *tag, char *buf, size_t len)
2376 char *start, *eol_pnt;
2379 if (ast_strlen_zero(header) || ast_strlen_zero(tag))
2382 taglen = strlen(tag) + 1;
2386 if (!(start = strstr(header, tag)))
2389 /* Since we can be called multiple times we should clear our buffer */
2390 memset(buf, 0, len);
2392 ast_copy_string(buf, start+taglen, len);
2393 if ((eol_pnt = strchr(buf,'\r')) || (eol_pnt = strchr(buf,'\n')))
2398 static char *get_user_by_mailbox(char *mailbox, char *buf, size_t len)
2400 char *start, *quote, *eol_pnt;
2402 if (ast_strlen_zero(mailbox))
2405 if (!(start = strstr(mailbox, "/user=")))
2408 ast_copy_string(buf, start+6, len);
2410 if (!(quote = strchr(buf, '\"'))) {
2411 if (!(eol_pnt = strchr(buf, '/')))
2412 eol_pnt = strchr(buf,'}');
2416 eol_pnt = strchr(buf+1,'\"');
2422 static struct vm_state *create_vm_state_from_user(struct ast_vm_user *vmu)
2424 struct vm_state *vms_p;
2426 if (option_debug > 4)
2427 ast_log(AST_LOG_DEBUG,"Adding new vmstate for %s\n",vmu->imapuser);
2428 if (!(vms_p = ast_calloc(1, sizeof(*vms_p))))
2430 ast_copy_string(vms_p->imapuser, vmu->imapuser, sizeof(vms_p->imapuser));
2431 ast_copy_string(vms_p->username, vmu->mailbox, sizeof(vms_p->username)); /* save for access from interactive entry point */
2432 ast_copy_string(vms_p->context, vmu->context, sizeof(vms_p->context));
2433 vms_p->mailstream = NIL; /* save for access from interactive entry point */
2434 if (option_debug > 4)
2435 ast_log(AST_LOG_DEBUG,"Copied %s to %s\n",vmu->imapuser,vms_p->imapuser);
2437 /* set mailbox to INBOX! */
2438 ast_copy_string(vms_p->curbox, mbox(0), sizeof(vms_p->curbox));
2439 init_vm_state(vms_p);
2440 vmstate_insert(vms_p);
2444 static struct vm_state *get_vm_state_by_imapuser(char *user, int interactive)
2446 struct vmstate *vlist = NULL;
2448 AST_LIST_LOCK(&vmstates);
2449 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2451 ast_debug(3, "error: vms is NULL for %s\n", user);
2454 if (!vlist->vms->imapuser) {
2455 ast_debug(3, "error: imapuser is NULL for %s\n", user);
2459 if (!strcmp(vlist->vms->imapuser, user) && (interactive == 2 || vlist->vms->interactive == interactive)) {
2460 AST_LIST_UNLOCK(&vmstates);
2464 AST_LIST_UNLOCK(&vmstates);
2466 ast_debug(3, "%s not found in vmstates\n", user);
2471 static struct vm_state *get_vm_state_by_mailbox(const char *mailbox, const char *context, int interactive)
2474 struct vmstate *vlist = NULL;
2475 const char *local_context = S_OR(context, "default");
2477 AST_LIST_LOCK(&vmstates);
2478 AST_LIST_TRAVERSE(&vmstates, vlist, list) {
2480 ast_debug(3, "error: vms is NULL for %s\n", mailbox);
2483 if (!vlist->vms->username || !vlist->vms->context) {
2484 ast_debug(3, "error: username is NULL for %s\n", mailbox);
2488 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);
2490 if (!strcmp(vlist->vms->username,mailbox) && !strcmp(vlist->vms->context, local_context) && vlist->vms->interactive == interactive) {
2491 ast_debug(3, "Found it!\n");
2492 AST_LIST_UNLOCK(&vmstates);
2496 AST_LIST_UNLOCK(&vmstates);
2498 ast_debug(3, "%s not found in vmstates\n", mailbox);
2503 static void vmstate_insert(struct vm_state *vms)
2506 struct vm_state *altvms;
2508 /* If interactive, it probably already exists, and we should
2509 use the one we already have since it is more up to date.
2510 We can compare the username to find the duplicate */
2511 if (vms->interactive == 1) {
2512 altvms = get_vm_state_by_mailbox(vms->username, vms->context, 0);
2514 ast_debug(3, "Duplicate mailbox %s, copying message info...\n",vms->username);
2515 vms->newmessages = altvms->newmessages;
2516 vms->oldmessages = altvms->oldmessages;
2517 vms->vmArrayIndex = altvms->vmArrayIndex;
2518 vms->lastmsg = altvms->lastmsg;
2519 vms->curmsg = altvms->curmsg;
2520 /* get a pointer to the persistent store */
2521 vms->persist_vms = altvms;
2522 /* Reuse the mailstream? */
2523 vms->mailstream = altvms->mailstream;
2524 /* vms->mailstream = NIL; */
2528 if (!(v = ast_calloc(1, sizeof(*v))))
2533 ast_debug(3, "Inserting vm_state for user:%s, mailbox %s\n",vms->imapuser,vms->username);
2535 AST_LIST_LOCK(&vmstates);
2536 AST_LIST_INSERT_TAIL(&vmstates, v, list);
2537 AST_LIST_UNLOCK(&vmstates);
2540 static void vmstate_delete(struct vm_state *vms)
2542 struct vmstate *vc = NULL;
2543 struct vm_state *altvms = NULL;
2545 /* If interactive, we should copy pertinent info
2546 back to the persistent state (to make update immediate) */
2547 if (vms->interactive == 1 && (altvms = vms->persist_vms)) {
2548 ast_debug(3, "Duplicate mailbox %s, copying message info...\n", vms->username);
2549 altvms->newmessages = vms->newmessages;
2550 altvms->oldmessages = vms->oldmessages;
2551 altvms->updated = 1;
2554 ast_debug(3, "Removing vm_state for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2556 AST_LIST_LOCK(&vmstates);
2557 AST_LIST_TRAVERSE_SAFE_BEGIN(&vmstates, vc, list) {
2558 if (vc->vms == vms) {
2559 AST_LIST_REMOVE_CURRENT(list);
2563 AST_LIST_TRAVERSE_SAFE_END
2564 AST_LIST_UNLOCK(&vmstates);
2567 ast_mutex_destroy(&vc->vms->lock);
2571 ast_log(AST_LOG_ERROR, "No vmstate found for user:%s, mailbox %s\n", vms->imapuser, vms->username);
2574 static void set_update(MAILSTREAM * stream)
2576 struct vm_state *vms;
2577 char *mailbox = stream->mailbox, *user;
2578 char buf[1024] = "";
2580 if (!(user = get_user_by_mailbox(mailbox, buf, sizeof(buf))) || !(vms = get_vm_state_by_imapuser(user, 0))) {
2581 if (user && option_debug > 2)
2582 ast_log(AST_LOG_WARNING, "User %s mailbox not found for update.\n", user);
2586 ast_debug(3, "User %s mailbox set for update.\n", user);
2588 vms->updated = 1; /* Set updated flag since mailbox changed */
2591 static void init_vm_state(struct vm_state *vms)
2594 vms->vmArrayIndex = 0;
2595 for (x = 0; x < VMSTATE_MAX_MSG_ARRAY; x++) {
2596 vms->msgArray[x] = 0;
2598 ast_mutex_init(&vms->lock);
2601 static int save_body(BODY *body, struct vm_state *vms, char *section, char *format, int is_intro)
2605 char *fn = is_intro ? vms->introfn : vms->fn;
2607 unsigned long newlen;
2610 if (!body || body == NIL)
2613 body_content = mail_fetchbody(vms->mailstream, vms->msgArray[vms->curmsg], section, &len);
2614 if (body_content != NIL) {
2615 snprintf(filename, sizeof(filename), "%s.%s", fn, format);
2616 /* ast_debug(1,body_content); */
2617 body_decoded = rfc822_base64((unsigned char *)body_content, len, &newlen);
2618 /* If the body of the file is empty, return an error */
2622 write_file(filename, (char *) body_decoded, newlen);
2624 ast_debug(5, "Body of message is NULL.\n");
2631 * \brief Get delimiter via mm_list callback
2634 * Determines the delimiter character that is used by the underlying IMAP based mail store.
2636 static void get_mailbox_delimiter(MAILSTREAM *stream) {
2638 snprintf(tmp, sizeof(tmp), "{%s}", imapserver);
2639 mail_list(stream, tmp, "*");
2643 * \brief Check Quota for user
2644 * \param vms a pointer to a vm_state struct, will use the mailstream property of this.
2645 * \param mailbox the mailbox to check the quota for.
2647 * Calls imap_getquotaroot, which will populate its results into the vm_state vms input structure.
2649 static void check_quota(struct vm_state *vms, char *mailbox) {
2650 mail_parameters(NULL, SET_QUOTA, (void *) mm_parsequota);
2651 ast_debug(3, "Mailbox name set to: %s, about to check quotas\n", mailbox);
2652 if (vms && vms->mailstream != NULL) {
2653 imap_getquotaroot(vms->mailstream, mailbox);
2655 ast_log(AST_LOG_WARNING, "Mailstream not available for mailbox: %s\n", mailbox);
2659 #endif /* IMAP_STORAGE */
2661 /*! \brief Lock file path
2662 only return failure if ast_lock_path returns 'timeout',
2663 not if the path does not exist or any other reason
2665 static int vm_lock_path(const char *path)
2667 switch (ast_lock_path(path)) {
2668 case AST_LOCK_TIMEOUT:
2677 struct generic_prepare_struct {
2683 static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
2685 struct generic_prepare_struct *gps = data;
2689 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
2690 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2691 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
2694 res = SQLPrepare(stmt, (unsigned char *)gps->sql, SQL_NTS);
2695 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2696 ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
2697 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2700 for (i = 0; i < gps->argc; i++)
2701 SQLBindParameter(stmt, i + 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(gps->argv[i]), 0, gps->argv[i], 0, NULL);
2707 * \brief Retrieves a file from an ODBC data store.
2708 * \param dir the path to the file to be retreived.
2709 * \param msgnum the message number, such as within a mailbox folder.
2711 * This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
2712 * 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.
2714 * The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
2715 * The output is the message information file with the name msgnum and the extension .txt
2716 * and the message file with the extension of its format, in the directory with base file name of the msgnum.
2718 * \return 0 on success, -1 on error.
2720 static int retrieve_file(char *dir, int msgnum)
2726 void *fdm = MAP_FAILED;
2727 SQLSMALLINT colcount=0;
2734 SQLSMALLINT datatype;
2735 SQLSMALLINT decimaldigits;
2736 SQLSMALLINT nullable;
2742 char full_fn[PATH_MAX];
2744 char *argv[] = { dir, msgnums };
2745 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2747 struct odbc_obj *obj;
2748 obj = ast_odbc_request_obj(odbc_database, 0);
2750 ast_copy_string(fmt, vmfmts, sizeof(fmt));
2751 c = strchr(fmt, '|');
2754 if (!strcasecmp(fmt, "wav49"))
2756 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
2758 make_file(fn, sizeof(fn), dir, msgnum);
2760 ast_copy_string(fn, dir, sizeof(fn));
2762 /* Create the information file */
2763 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
2765 if (!(f = fopen(full_fn, "w+"))) {
2766 ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
2770 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
2771 snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2772 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2774 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2775 ast_odbc_release_obj(obj);
2778 res = SQLFetch(stmt);
2779 if (res == SQL_NO_DATA) {
2780 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2781 ast_odbc_release_obj(obj);
2783 } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2784 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2785 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2786 ast_odbc_release_obj(obj);
2789 fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
2791 ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
2792 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2793 ast_odbc_release_obj(obj);
2796 res = SQLNumResultCols(stmt, &colcount);
2797 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2798 ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
2799 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2800 ast_odbc_release_obj(obj);
2804 fprintf(f, "[message]\n");
2805 for (x=0;x<colcount;x++) {
2807 collen = sizeof(coltitle);
2808 res = SQLDescribeCol(stmt, x + 1, (unsigned char *)coltitle, sizeof(coltitle), &collen,
2809 &datatype, &colsize, &decimaldigits, &nullable);
2810 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2811 ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
2812 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2813 ast_odbc_release_obj(obj);
2816 if (!strcasecmp(coltitle, "recording")) {
2818 res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
2822 lseek(fd, fdlen - 1, SEEK_SET);
2823 if (write(fd, tmp, 1) != 1) {
2828 /* Read out in small chunks */
2829 for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
2830 if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
2831 ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
2832 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2833 ast_odbc_release_obj(obj);
2836 res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
2837 munmap(fdm, CHUNKSIZE);
2838 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2839 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2841 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
2842 ast_odbc_release_obj(obj);
2847 truncate(full_fn, fdlen);
2850 res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2851 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2852 ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
2853 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2854 ast_odbc_release_obj(obj);
2857 if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
2858 fprintf(f, "%s=%s\n", coltitle, rowdata);
2861 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2862 ast_odbc_release_obj(obj);
2864 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2874 * \brief Determines the highest message number in use for a given user and mailbox folder.
2876 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
2878 * This method is used when mailboxes are stored in an ODBC back end.
2879 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
2881 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
2883 static int last_message_index(struct ast_vm_user *vmu, char *dir)
2890 char *argv[] = { dir };
2891 struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
2893 struct odbc_obj *obj;
2894 obj = ast_odbc_request_obj(odbc_database, 0);
2896 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?",odbc_table);
2897 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2899 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2900 ast_odbc_release_obj(obj);
2903 res = SQLFetch(stmt);
2904 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2905 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2906 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2907 ast_odbc_release_obj(obj);
2910 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2911 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2912 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2913 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2914 ast_odbc_release_obj(obj);
2917 if (sscanf(rowdata, "%d", &x) != 1)
2918 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
2919 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2920 ast_odbc_release_obj(obj);
2922 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2928 * \brief Determines if the specified message exists.
2929 * \param dir the folder the mailbox folder to look for messages.
2930 * \param msgnum the message index to query for.
2932 * This method is used when mailboxes are stored in an ODBC back end.
2934 * \return greater than zero if the message exists, zero when the message does not exist or on error.
2936 static int message_exists(char *dir, int msgnum)
2944 char *argv[] = { dir, msgnums };
2945 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
2947 struct odbc_obj *obj;
2948 obj = ast_odbc_request_obj(odbc_database, 0);
2950 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
2951 snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?",odbc_table);
2952 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
2954 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
2955 ast_odbc_release_obj(obj);
2958 res = SQLFetch(stmt);
2959 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2960 ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
2961 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2962 ast_odbc_release_obj(obj);
2965 res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
2966 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
2967 ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
2968 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2969 ast_odbc_release_obj(obj);
2972 if (sscanf(rowdata, "%d", &x) != 1)
2973 ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
2974 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
2975 ast_odbc_release_obj(obj);
2977 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
2983 * \brief returns the one-based count for messages.
2985 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
2987 * This method is used when mailboxes are stored in an ODBC back end.
2988 * The message index is zero-based, the first message will be index 0. For convenient display it is good to have the
2989 * one-based messages.
2990 * This method just calls last_message_index and returns +1 of its value.
2992 * \return the value greater than zero on success to indicate the one-based count of messages, less than zero on error.
2994 static int count_messages(struct ast_vm_user *vmu, char *dir)
2996 return last_message_index(vmu, dir) + 1;
3000 * \brief Deletes a message from the mailbox folder.
3001 * \param sdir The mailbox folder to work in.
3002 * \param smsg The message index to be deleted.
3004 * This method is used when mailboxes are stored in an ODBC back end.
3005 * The specified message is directly deleted from the database 'voicemessages' table.
3007 * \return the value greater than zero on success to indicate the number of messages, less than zero on error.
3009 static void delete_file(char *sdir, int smsg)
3014 char *argv[] = { sdir, msgnums };
3015 struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
3017 struct odbc_obj *obj;
3018 obj = ast_odbc_request_obj(odbc_database, 0);
3020 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3021 snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?",odbc_table);
3022 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3024 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3026 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3027 ast_odbc_release_obj(obj);
3029 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3034 * \brief Copies a voicemail from one mailbox to another.
3035 * \param sdir the folder for which to look for the message to be copied.
3036 * \param smsg the index of the message to be copied.
3037 * \param ddir the destination folder to copy the message into.
3038 * \param dmsg the index to be used for the copied message.
3039 * \param dmailboxuser The user who owns the mailbox tha contains the destination folder.
3040 * \param dmailboxcontext The context for the destination user.
3042 * This method is used for the COPY macro when mailboxes are stored in an ODBC back end.
3044 static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailboxuser, char *dmailboxcontext)
3050 struct odbc_obj *obj;
3051 char *argv[] = { ddir, msgnumd, dmailboxuser, dmailboxcontext, sdir, msgnums };
3052 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3054 delete_file(ddir, dmsg);
3055 obj = ast_odbc_request_obj(odbc_database, 0);
3057 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3058 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3059 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);
3060 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3062 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
3064 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3065 ast_odbc_release_obj(obj);
3067 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3071 struct insert_data {
3078 const char *context;
3079 const char *macrocontext;
3080 const char *callerid;
3081 const char *origtime;
3082 const char *duration;
3084 char *mailboxcontext;
3085 const char *category;
3089 static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
3091 struct insert_data *data = vdata;
3095 res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
3096 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3097 ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
3098 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3102 SQLBindParameter(stmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->dir), 0, (void *)data->dir, 0, NULL);
3103 SQLBindParameter(stmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->msgnums), 0, (void *)data->msgnums, 0, NULL);
3104 SQLBindParameter(stmt, 3, SQL_PARAM_INPUT, SQL_C_BINARY, SQL_LONGVARBINARY, data->datalen, 0, (void *)data->data, data->datalen, &data->indlen);
3105 SQLBindParameter(stmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->context), 0, (void *)data->context, 0, NULL);
3106 SQLBindParameter(stmt, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->macrocontext), 0, (void *)data->macrocontext, 0, NULL);
3107 SQLBindParameter(stmt, 6, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->callerid), 0, (void *)data->callerid, 0, NULL);
3108 SQLBindParameter(stmt, 7, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->origtime), 0, (void *)data->origtime, 0, NULL);
3109 SQLBindParameter(stmt, 8, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->duration), 0, (void *)data->duration, 0, NULL);
3110 SQLBindParameter(stmt, 9, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxuser), 0, (void *)data->mailboxuser, 0, NULL);
3111 SQLBindParameter(stmt, 10, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->mailboxcontext), 0, (void *)data->mailboxcontext, 0, NULL);
3112 SQLBindParameter(stmt, 11, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->flag), 0, (void *)data->flag, 0, NULL);
3113 if (!ast_strlen_zero(data->category)) {
3114 SQLBindParameter(stmt, 12, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *)data->category, 0, NULL);
3116 res = SQLExecDirect(stmt, (unsigned char *)data->sql, SQL_NTS);
3117 if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
3118 ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
3119 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3127 * \brief Stores a voicemail into the database.
3128 * \param dir the folder the mailbox folder to store the message.
3129 * \param mailboxuser the user owning the mailbox folder.
3130 * \param mailboxcontext
3131 * \param msgnum the message index for the message to be stored.
3133 * This method is used when mailboxes are stored in an ODBC back end.
3134 * The message sound file and information file is looked up on the file system.
3135 * A SQL query is invoked to store the message into the (MySQL) database.
3137 * \return the zero on success -1 on error.
3139 static int store_file(char *dir, char *mailboxuser, char *mailboxcontext, int msgnum)
3143 void *fdm = MAP_FAILED;
3149 char full_fn[PATH_MAX];
3152 struct ast_config *cfg=NULL;
3153 struct odbc_obj *obj;
3154 struct insert_data idata = { .sql = sql, .msgnums = msgnums, .dir = dir, .mailboxuser = mailboxuser, .mailboxcontext = mailboxcontext };
3155 struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
3157 delete_file(dir, msgnum);
3158 if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
3159 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3164 ast_copy_string(fmt, vmfmts, sizeof(fmt));
3165 c = strchr(fmt, '|');
3168 if (!strcasecmp(fmt, "wav49"))
3170 snprintf(msgnums, sizeof(msgnums),"%d", msgnum);
3172 make_file(fn, sizeof(fn), dir, msgnum);
3174 ast_copy_string(fn, dir, sizeof(fn));
3175 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3176 cfg = ast_config_load(full_fn, config_flags);
3177 snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
3178 fd = open(full_fn, O_RDWR);
3180 ast_log(AST_LOG_WARNING, "Open of sound file '%s' failed: %s\n", full_fn, strerror(errno));
3185 if (!(idata.context = ast_variable_retrieve(cfg, "message", "context"))) {
3188 if (!(idata.macrocontext = ast_variable_retrieve(cfg, "message", "macrocontext"))) {
3189 idata.macrocontext = "";
3191 if (!(idata.callerid = ast_variable_retrieve(cfg, "message", "callerid"))) {
3192 idata.callerid = "";
3194 if (!(idata.origtime = ast_variable_retrieve(cfg, "message", "origtime"))) {
3195 idata.origtime = "";
3197 if (!(idata.duration = ast_variable_retrieve(cfg, "message", "duration"))) {
3198 idata.duration = "";
3200 if (!(idata.category = ast_variable_retrieve(cfg, "message", "category"))) {
3201 idata.category = "";
3203 if (!(idata.flag = ast_variable_retrieve(cfg, "message", "flag"))) {
3207 fdlen = lseek(fd, 0, SEEK_END);
3208 lseek(fd, 0, SEEK_SET);
3209 printf("Length is %zd\n", fdlen);
3210 fdm = mmap(NULL, fdlen, PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);
3211 if (fdm == MAP_FAILED) {
3212 ast_log(AST_LOG_WARNING, "Memory map failed!\n");
3217 idata.datalen = idata.indlen = fdlen;
3219 if (!ast_strlen_zero(idata.category))
3220 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
3222 snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag) VALUES (?,?,?,?,?,?,?,?,?,?,?)",odbc_table);
3224 if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
3225 SQLFreeHandle (SQL_HANDLE_STMT, stmt);
3227 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3232 ast_odbc_release_obj(obj);
3235 ast_config_destroy(cfg);
3236 if (fdm != MAP_FAILED)
3244 * \brief Renames a message in a mailbox folder.
3245 * \param sdir The folder of the message to be renamed.
3246 * \param smsg The index of the message to be renamed.
3247 * \param mailboxuser The user to become the owner of the message after it is renamed. Usually this will be the same as the original owner.
3248 * \param mailboxcontext The context to be set for the message. Usually this will be the same as the original context.
3249 * \param ddir The destination folder for the message to be renamed into
3250 * \param dmsg The destination message for the message to be renamed.
3252 * This method is used by the RENAME macro when mailboxes are stored in an ODBC back end.
3253 * The is usually used to resequence the messages in the mailbox, such as to delete messag index 0, it would be called successively to slide all the other messages down one index.
3254 * But in theory, because the SQL query performs an update on (dir, msgnum, mailboxuser, mailboxcontext) in the database, it should be possible to have the message relocated to another mailbox or context as well.
3256 static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxcontext, char *ddir, int dmsg)
3262 struct odbc_obj *obj;
3263 char *argv[] = { ddir, msgnumd, mailboxuser, mailboxcontext, sdir, msgnums };
3264 struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
3266 delete_file(ddir, dmsg);
3267 obj = ast_odbc_request_obj(odbc_database, 0);
3269 snprintf(msgnums, sizeof(msgnums), "%d", smsg);
3270 snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
3271 snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?",odbc_table);
3272 stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
3274 ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
3276 SQLFreeHandle(SQL_HANDLE_STMT, stmt);
3277 ast_odbc_release_obj(obj);
3279 ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
3284 * \brief Removes a voicemail message file.
3285 * \param dir the path to the message file.
3286 * \param msgnum the unique number for the message within the mailbox.
3288 * Removes the message content file and the information file.
3289 * This method is used by the DISPOSE macro when mailboxes are stored in an ODBC back end.
3290 * Typical use is to clean up after a RETRIEVE operation.
3291 * Note that this does not remove the message from the mailbox folders, to do that we would use delete_file().
3292 * \return zero on success, -1 on error.
3294 static int remove_file(char *dir, int msgnum)
3297 char full_fn[PATH_MAX];
3301 snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
3302 make_file(fn, sizeof(fn), dir, msgnum);
3304 ast_copy_string(fn, dir, sizeof(fn));
3305 ast_filedelete(fn, NULL);
3306 snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
3311 #ifndef IMAP_STORAGE
3313 * \brief Find all .txt files - even if they are not in sequence from 0000.
3317 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3319 * \return the count of messages, zero or more.
3321 static int count_messages(struct ast_vm_user *vmu, char *dir)
3326 struct dirent *vment = NULL;
3328 if (vm_lock_path(dir))
3329 return ERROR_LOCK_PATH;
3331 if ((vmdir = opendir(dir))) {
3332 while ((vment = readdir(vmdir))) {
3333 if (strlen(vment->d_name) > 7 && !strncmp(vment->d_name + 7, ".txt", 4)) {
3339 ast_unlock_path(dir);
3345 * \brief Renames a message in a mailbox folder.
3346 * \param sfn The path to the mailbox information and data file to be renamed.
3347 * \param dfn The path for where the message data and information files will be renamed to.
3349 * This method is used by the RENAME macro when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3351 static void rename_file(char *sfn, char *dfn)
3353 char stxt[PATH_MAX];
3354 char dtxt[PATH_MAX];
3355 ast_filerename(sfn,dfn,NULL);
3356 snprintf(stxt, sizeof(stxt), "%s.txt", sfn);
3357 snprintf(dtxt, sizeof(dtxt), "%s.txt", dfn);
3358 if (ast_check_realtime("voicemail_data")) {
3359 ast_update_realtime("voicemail_data", "filename", sfn, "filename", dfn, SENTINEL);
3365 * \brief Determines the highest message number in use for a given user and mailbox folder.
3367 * \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
3369 * This method is used when mailboxes are stored on the filesystem. (not ODBC and not IMAP).
3370 * Typical use to set the msgnum would be to take the value returned from this method and add one to it.
3372 * \note Should always be called with a lock already set on dir.
3373 * \return the value of zero or greaterto indicate the last message index in use, -1 to indicate none.
3375 static int last_message_index(struct ast_vm_user *vmu, char *dir)
3378 unsigned char map[MAXMSGLIMIT] = "";
3380 struct dirent *msgdirent;
3383 /* Reading the entire directory into a file map scales better than
3384 * doing a stat repeatedly on a predicted sequence. I suspect this
3385 * is partially due to stat(2) internally doing a readdir(2) itself to
3386 * find each file. */
3387 msgdir = opendir(dir);
3388 while ((msgdirent = readdir(msgdir))) {
3389 if (sscanf(msgdirent->d_name, "msg%d", &msgdirint) == 1 && msgdirint < MAXMSGLIMIT)
3394 for (x = 0; x < vmu->maxmsg; x++) {
3403 * \brief Utility function to copy a file.
3404 * \param infile The path to the file to be copied. The file must be readable, it is opened in read only mode.
3405 * \param outfile The path for which to copy the file to. The directory permissions must allow the creation (or truncation) of the file, and allow for opening the file in write only mode.
3407 * When the compiler option HARDLINK_WHEN_POSSIBLE is set, the copy operation will attempt to use the hard link facility instead of copy the file (to save disk space). If the link operation fails, it falls back to the copy operation.
3408 * The copy operation copies up to 4096 bytes at once.
3410 * \return zero on success, -1 on error.
3412 static int copy(char *infile, char *outfile)
3420 #ifdef HARDLINK_WHEN_POSSIBLE
3421 /* Hard link if possible; saves disk space & is faster */
3422 if (link(infile, outfile)) {
3424 if ((ifd = open(infile, O_RDONLY)) < 0) {
3425 ast_log(AST_LOG_WARNING, "Unable to open %s in read-only mode: %s\n", infile, strerror(errno));
3428 if ((ofd = open(outfile, O_WRONLY | O_TRUNC | O_CREAT, VOICEMAIL_FILE_MODE)) < 0) {
3429 ast_log(AST_LOG_WARNING, "Unable to open %s in write-only mode: %s\n", outfile, strerror(errno));
3434 len = read(ifd, buf, sizeof(buf));
3436 ast_log(AST_LOG_WARNING, "Read failed on %s: %s\n", infile, strerror(errno));
3442 res = write(ofd, buf, len);
3443 if (errno == ENOMEM || errno == ENOSPC || res != len) {
3444 ast_log(AST_LOG_WARNING, "Write failed on %s (%d of %d): %s\n", outfile, res, len, strerror(errno));
3454 #ifdef HARDLINK_WHEN_POSSIBLE
3456 /* Hard link succeeded */
3463 * \brief Copies a voicemail information (envelope) file.
3467 * Every voicemail has the data (.wav) file, and the information file.
3468 * This function performs the file system copying of the information file for a voicemail, handling the internal fields and their values.
3469 * This is used by the COPY macro when not using IMAP storage.
3471 static void copy_plain_file(char *frompath, char *topath)
3473 char frompath2[PATH_MAX], topath2[PATH_MAX];
3474 struct ast_variable *tmp,*var = NULL;
3475 const char *origmailbox = NULL, *context = NULL, *macrocontext = NULL, *exten = NULL, *priority = NULL, *callerchan = NULL, *callerid = NULL, *origdate = NULL, *origtime = NULL, *category = NULL, *duration = NULL;
3476 ast_filecopy(frompath, topath, NULL);
3477 snprintf(frompath2, sizeof(frompath2), "%s.txt", frompath);
3478 snprintf(topath2, sizeof(topath2), "%s.txt", topath);
3479 if (ast_check_realtime("voicemail_data")) {
3480 var = ast_load_realtime("voicemail_data", "filename", frompath, SENTINEL);
3481 /* This cycle converts ast_variable linked list, to va_list list of arguments, may be there is a better way to do it? */
3482 for (tmp = var; tmp; tmp = tmp->next) {
3483 if (!strcasecmp(tmp->name, "origmailbox")) {
3484 origmailbox = tmp->value;
3485 } else if (!strcasecmp(tmp->name, "context")) {
3486 context = tmp->value;
3487 } else if (!strcasecmp(tmp->name, "macrocontext")) {
3488 macrocontext = tmp->value;
3489 } else if (!strcasecmp(tmp->name, "exten")) {
3491 } else if (!strcasecmp(tmp->name, "priority")) {
3492 priority = tmp->value;
3493 } else if (!strcasecmp(tmp->name, "callerchan")) {
3494 callerchan = tmp->value;
3495 } else if (!strcasecmp(tmp->name, "callerid")) {
3496 callerid = tmp->value;
3497 } else if (!strcasecmp(tmp->name, "origdate")) {
3498 origdate = tmp->value;
3499 } else if (!strcasecmp(tmp->name, "origtime")) {
3500 origtime = tmp->value;
3501 } else if (!strcasecmp(tmp->name, "category")) {
3502 category = tmp->value;
3503 } else if (!strcasecmp(tmp->name, "duration")) {
3504 duration = tmp->value;
3507 ast_store_realtime("voicemail_data", "filename", topath, "origmailbox", origmailbox, "context", context, "macrocontext", macrocontext, "exten", exten, "priority", priority, "callerchan", callerchan, "callerid", callerid, "origdate", origdate, "origtime", origtime, "category", category, "duration", duration, SENTINEL);
3509 copy(frompath2, topath2);
3510 ast_variables_destroy(var);
3513 #endif /* #ifndef IMAP_STORAGE */
3514 #endif /* #else of #ifdef ODBC_STORAGE */
3516 #if (!defined(ODBC_STORAGE) && !defined(IMAP_STORAGE))
3518 * \brief Removes the voicemail sound and information file.
3519 * \param file The path to the sound file. This will be the the folder and message index, without the extension.
3521 * This is used by the DELETE macro when voicemails are stored on the file system.
3523 * \return zero on success, -1 on error.
3525 static int vm_delete(char *file)
3530 txtsize = (strlen(file) + 5)*sizeof(char);
3531 txt = alloca(txtsize);
3532 /* Sprintf here would safe because we alloca'd exactly the right length,
3533 * but trying to eliminate all sprintf's anyhow
3535 if (ast_check_realtime("voicemail_data")) {
3536 ast_destroy_realtime("voicemail_data", "filename", file, SENTINEL);
3538 snprintf(txt, txtsize, "%s.txt", file);
3540 return ast_filedelete(file, NULL);
3545 * \brief utility used by inchar(), for base_encode()
3547 static int inbuf(struct baseio *bio, FILE *fi)
3554 if ((l = fread(bio->iobuf,1,BASEMAXINLINE,fi)) <= 0) {
3569 * \brief utility used by base_encode()
3571 static int inchar(struct baseio *bio, FILE *fi)
3573 if (bio->iocp>=bio->iolen) {
3574 if (!inbuf(bio, fi))
3578 return bio->iobuf[bio->iocp++];
3582 * \brief utility used by base_encode()
3584 static int ochar(struct baseio *bio, int c, FILE *so)
3586 if (bio->linelength >= BASELINELEN) {
3587 if (fputs(eol,so) == EOF)
3593 if (putc(((unsigned char)c),so) == EOF)
3602 * \brief Performs a base 64 encode algorithm on the contents of a File
3603 * \param filename The path to the file to be encoded. Must be readable, file is opened in read mode.
3604 * \param so A FILE handle to the output file to receive the base 64 encoded contents of the input file, identified by filename.
3606 * TODO: shouldn't this (and the above 3 support functions) be put into some kind of external utility location, such as funcs/func_base64.c ?
3608 * \return zero on success, -1 on error.
3610 static int base_encode(char *filename, FILE *so)
3612 static const unsigned char dtable[] = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
3613 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
3614 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0',
3615 '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'};
3620 memset(&bio, 0, sizeof(bio));
3621 bio.iocp = BASEMAXINLINE;
3623 if (!(fi = fopen(filename, "rb"))) {
3624 ast_log(AST_LOG_WARNING, "Failed to open file: %s: %s\n", filename, strerror(errno));
3629 unsigned char igroup[3], ogroup[4];
3632 igroup[0]= igroup[1]= igroup[2]= 0;
3634 for (n= 0;n<3;n++) {
3635 if ((c = inchar(&bio, fi)) == EOF) {
3640 igroup[n]= (unsigned char)c;
3644 ogroup[0]= dtable[igroup[0]>>2];
3645 ogroup[1]= dtable[((igroup[0]&3)<<4) | (igroup[1]>>4)];
3646 ogroup[2]= dtable[((igroup[1]&0xF)<<2) | (igroup[2]>>6)];
3647 ogroup[3]= dtable[igroup[2]&0x3F];
3657 ochar(&bio, ogroup[i], so);
3663 if (fputs(eol,so)==EOF)
3669 static void prep_email_sub_vars(struct ast_channel *ast, struct ast_vm_user *vmu, int msgnum, char *context, char *mailbox, char *cidnum, char *cidname, char *dur, char *date, char *passdata, size_t passdatasize, const char *category, const char *flag)
3672 /* Prepare variables for substitution in email body and subject */
3673 pbx_builtin_setvar_helper(ast, "VM_NAME", vmu->fullname);
3674 pbx_builtin_setvar_helper(ast, "VM_DUR", dur);
3675 snprintf(passdata, passdat